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
247 changed files with 14405 additions and 6911 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> </set>
</option> </option>
<option name="resolveModulePerSourceSet" value="false" /> <option name="resolveModulePerSourceSet" value="false" />
<option name="useQualifiedModuleNames" value="true" />
</GradleProjectSettings> </GradleProjectSettings>
</option> </option>
</component> </component>

View File

@ -31,5 +31,10 @@
<option name="name" value="Google" /> <option name="name" value="Google" />
<option name="url" value="https://dl.google.com/dl/android/maven2/" /> <option name="url" value="https://dl.google.com/dl/android/maven2/" />
</remote-repository> </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> </component>
</project> </project>

View File

@ -40,7 +40,7 @@
</value> </value>
</option> </option>
</component> </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" /> <output url="file://$PROJECT_DIR$/build/classes" />
</component> </component>
<component name="ProjectType"> <component name="ProjectType">

View File

@ -1,11 +1,12 @@
<component name="ProjectRunConfigurationManager"> <component name="ProjectRunConfigurationManager">
<configuration default="false" name="app" type="AndroidRunConfigurationType" factoryName="Android App" activateToolWindowBeforeRun="false"> <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" value="true" />
<option name="DEPLOY_APK_FROM_BUNDLE" value="false" /> <option name="DEPLOY_APK_FROM_BUNDLE" value="false" />
<option name="DEPLOY_AS_INSTANT" value="false" /> <option name="DEPLOY_AS_INSTANT" value="false" />
<option name="ARTIFACT_NAME" value="" /> <option name="ARTIFACT_NAME" value="" />
<option name="PM_INSTALL_OPTIONS" value="" /> <option name="PM_INSTALL_OPTIONS" value="" />
<option name="ALL_USERS" value="false" />
<option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" /> <option name="DYNAMIC_FEATURES_DISABLED_LIST" value="" />
<option name="ACTIVITY_EXTRA_FLAGS" value="" /> <option name="ACTIVITY_EXTRA_FLAGS" value="" />
<option name="MODE" value="default_activity" /> <option name="MODE" value="default_activity" />
@ -41,11 +42,16 @@
</Native> </Native>
<Profilers> <Profilers>
<option name="ADVANCED_PROFILING_ENABLED" value="false" /> <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_ENABLED" value="false" />
<option name="STARTUP_CPU_PROFILING_CONFIGURATION_NAME" value="Sample Java Methods" /> <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> </Profilers>
<option name="DEEP_LINK" value="" /> <option name="DEEP_LINK" value="" />
<option name="ACTIVITY_CLASS" value="awais.instagrabber.activities.MainActivity" /> <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"> <method v="2">
<option name="Android.Gradle.BeforeRunTask" enabled="true" /> <option name="Android.Gradle.BeforeRunTask" enabled="true" />
</method> </method>

View File

@ -10,8 +10,8 @@ android {
minSdkVersion 21 minSdkVersion 21
targetSdkVersion 29 targetSdkVersion 29
versionCode 51 versionCode 52
versionName '19.0-a2' versionName '19.0'
multiDexEnabled true multiDexEnabled true
@ -20,6 +20,9 @@ android {
} }
compileOptions { compileOptions {
// Flag to enable support for the new language APIs
coreLibraryDesugaringEnabled true
targetCompatibility JavaVersion.VERSION_1_8 targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility 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' configurations.all {
implementation 'com.google.android.exoplayer:exoplayer:2.11.1' 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:$appcompat_version"
implementation "androidx.appcompat:appcompat-resources:$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.swiperefreshlayout:swiperefreshlayout:1.1.0'
implementation "androidx.viewpager2:viewpager2:1.0.0" implementation "androidx.viewpager2:viewpager2:1.0.0"
implementation "androidx.navigation:navigation-fragment:$nav_version" implementation "androidx.navigation:navigation-fragment:$nav_version"
implementation "androidx.navigation:navigation-ui:$nav_version" implementation "androidx.navigation:navigation-ui:$nav_version"
implementation "androidx.constraintlayout:constraintlayout:2.0.1" implementation "androidx.constraintlayout:constraintlayout:2.0.4"
implementation "androidx.preference:preference:$preference_version" 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 'org.jsoup:jsoup:1.13.1'
implementation 'com.facebook.fresco:fresco:2.3.0' 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:retrofit:2.9.0'
implementation 'com.squareup.retrofit2:converter-scalars:2.9.0' implementation 'com.squareup.retrofit2:converter-scalars:2.9.0'
implementation 'com.squareup.retrofit2:converter-gson: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:taskAffinity="awais.instagrabber.errorreport"
android:theme="@android:style/Theme.DeviceDefault.Dialog" /> android:theme="@android:style/Theme.DeviceDefault.Dialog" />
<activity <activity
android:name=".directdownload.MultiDirectDialog" android:name=".activities.DirectDownload"
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:allowEmbedded="false" android:allowEmbedded="false"
android:allowTaskReparenting="false" android:allowTaskReparenting="false"
android:autoRemoveFromRecents="true" android:autoRemoveFromRecents="true"
@ -100,7 +85,7 @@
android:launchMode="singleTask" android:launchMode="singleTask"
android:lockTaskMode="never" android:lockTaskMode="never"
android:noHistory="false" android:noHistory="false"
android:theme="@style/CompletelyTransparent"> android:theme="@style/Theme.AppCompat.Translucent">
<intent-filter> <intent-filter>
<action android:name="android.intent.action.SEND" /> <action android:name="android.intent.action.SEND" />
<action android:name="android.intent.action.SEARCH" /> <action android:name="android.intent.action.SEARCH" />
@ -138,6 +123,12 @@
android:name="android.support.FILE_PROVIDER_PATHS" android:name="android.support.FILE_PROVIDER_PATHS"
android:resource="@xml/provider_paths" /> android:resource="@xml/provider_paths" />
</provider> </provider>
<service android:name=".services.ActivityCheckerService" />
<service
android:name=".services.ActivityCheckerService"
android:exported="false" />
<service
android:name=".services.DeleteImageIntentService"
android:exported="false" />
</application> </application>
</manifest> </manifest>

View File

@ -32,12 +32,16 @@ public final class InstaGrabberApplication extends Application {
@Override @Override
public void onCreate() { public void onCreate() {
super.onCreate(); super.onCreate();
// final Set<RequestListener> requestListeners = new HashSet<>();
// requestListeners.add(new RequestLoggingListener());
final ImagePipelineConfig imagePipelineConfig = ImagePipelineConfig final ImagePipelineConfig imagePipelineConfig = ImagePipelineConfig
.newBuilder(this) .newBuilder(this)
// .setMainDiskCacheConfig(diskCacheConfig) // .setMainDiskCacheConfig(diskCacheConfig)
// .setRequestListeners(requestListeners)
.setDownsampleEnabled(true) .setDownsampleEnabled(true)
.build(); .build();
Fresco.initialize(this, imagePipelineConfig); Fresco.initialize(this, imagePipelineConfig);
// FLog.setMinimumLoggingLevel(FLog.VERBOSE);
if (BuildConfig.DEBUG) { if (BuildConfig.DEBUG) {
try { 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.graphics.Bitmap;
import android.os.Build; import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View; import android.view.View;
import android.webkit.CookieManager; import android.webkit.CookieManager;
import android.webkit.CookieSyncManager; import android.webkit.CookieSyncManager;
@ -59,7 +60,7 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
@Override @Override
protected void onCreate(@Nullable final Bundle savedInstanceState) { protected void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
loginBinding = ActivityLoginBinding.inflate(getLayoutInflater()); loginBinding = ActivityLoginBinding.inflate(LayoutInflater.from(getApplicationContext()));
setContentView(loginBinding.getRoot()); setContentView(loginBinding.getRoot());
initWebView(); initWebView();
@ -117,12 +118,9 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
webSettings.setDisplayZoomControls(false); webSettings.setDisplayZoomControls(false);
webSettings.setLoadWithOverviewMode(true); webSettings.setLoadWithOverviewMode(true);
webSettings.setUseWideViewPort(true); webSettings.setUseWideViewPort(true);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.JELLY_BEAN) { webSettings.setAllowFileAccessFromFileURLs(true);
webSettings.setAllowFileAccessFromFileURLs(true); webSettings.setAllowUniversalAccessFromFileURLs(true);
webSettings.setAllowUniversalAccessFromFileURLs(true); webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP)
webSettings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
} }
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) { 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.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.SearchView;
import androidx.appcompat.widget.Toolbar; import androidx.appcompat.widget.Toolbar;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.app.NotificationManagerCompat; import androidx.core.app.NotificationManagerCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.lifecycle.LiveData; import androidx.lifecycle.LiveData;
import androidx.navigation.NavBackStackEntry; 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.AppBarLayout;
import com.google.android.material.appbar.CollapsingToolbarLayout; import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections;
import java.util.Deque; import java.util.Deque;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
@ -50,9 +52,12 @@ import java.util.Map;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.adapters.SuggestionsAdapter; import awais.instagrabber.adapters.SuggestionsAdapter;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.asyncs.SuggestionsFetcher; import awais.instagrabber.asyncs.SuggestionsFetcher;
import awais.instagrabber.customviews.helpers.CustomHideBottomViewOnScrollBehavior; import awais.instagrabber.customviews.helpers.CustomHideBottomViewOnScrollBehavior;
import awais.instagrabber.databinding.ActivityMainBinding; 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.fragments.settings.MorePreferencesFragmentDirections;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.IntentModel; import awais.instagrabber.models.IntentModel;
@ -94,9 +99,12 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
R.id.notificationsViewer, R.id.notificationsViewer,
R.id.themePreferencesFragment, R.id.themePreferencesFragment,
R.id.favoritesFragment, 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 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 static final String FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex";
private ActivityMainBinding binding; private ActivityMainBinding binding;
@ -157,10 +165,6 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
bindActivityCheckerService(); bindActivityCheckerService();
} }
getSupportFragmentManager().addOnBackStackChangedListener(this); 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 @Override
@ -222,7 +226,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
@Override @Override
public void onBackPressed() { public void onBackPressed() {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && isTaskRoot() && isBackStackEmpty) { if (isTaskRoot() && isBackStackEmpty) {
finishAfterTransition(); finishAfterTransition();
} else { } else {
super.onBackPressed(); super.onBackPressed();
@ -403,11 +407,14 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
} }
final List<Integer> mainNavList = getMainNavList(main_nav_ids); final List<Integer> mainNavList = getMainNavList(main_nav_ids);
if (setDefaultFromSettings) { if (setDefaultFromSettings) {
final String defaultTabIdString = settingsHelper.getString(Constants.DEFAULT_TAB); final String defaultTabResNameString = settingsHelper.getString(Constants.DEFAULT_TAB);
try { try {
final int defaultNavId = TextUtils.isEmpty(defaultTabIdString) int navId = 0;
? R.navigation.profile_nav_graph if (!TextUtils.isEmpty(defaultTabResNameString)) {
: Integer.parseInt(defaultTabIdString); navId = getResources().getIdentifier(defaultTabResNameString, "navigation", getPackageName());
}
final int defaultNavId = navId <= 0 ? R.navigation.profile_nav_graph
: navId;
final int index = mainNavList.indexOf(defaultNavId); final int index = mainNavList.indexOf(defaultNavId);
if (index >= 0) firstFragmentGraphIndex = index; if (index >= 0) firstFragmentGraphIndex = index;
setBottomNavSelectedItem(defaultNavId); setBottomNavSelectedItem(defaultNavId);
@ -422,8 +429,18 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
R.id.main_nav_host, R.id.main_nav_host,
getIntent(), getIntent(),
firstFragmentGraphIndex); firstFragmentGraphIndex);
navControllerLiveData.observe(this, this::setupNavigation); navControllerLiveData.observe(this, navController -> setupNavigation(binding.toolbar, navController));
currentNavControllerLiveData = navControllerLiveData; 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) { private void setBottomNavSelectedItem(final int navId) {
@ -447,8 +464,11 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
return mainNavList; return mainNavList;
} }
private void setupNavigation(final NavController navController) { private void setupNavigation(final Toolbar toolbar, final NavController navController) {
NavigationUI.setupWithNavController(binding.toolbar, navController); if (navController == null) {
return;
}
NavigationUI.setupWithNavController(toolbar, navController);
navController.addOnDestinationChangedListener((controller, destination, arguments) -> { 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 // 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); binding.appBarLayout.setExpanded(true, true);
@ -572,13 +592,18 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
private void showPostView(@NonNull final IntentModel intentModel) { private void showPostView(@NonNull final IntentModel intentModel) {
final String shortCode = intentModel.getText(); final String shortCode = intentModel.getText();
// Log.d(TAG, "shortCode: " + shortCode); // Log.d(TAG, "shortCode: " + shortCode);
final NavController navController = currentNavControllerLiveData.getValue(); final AlertDialog alertDialog = new AlertDialog.Builder(this)
if (currentNavControllerLiveData == null || navController == null) return; .setCancelable(false)
final Bundle bundle = new Bundle(); .setView(R.layout.dialog_opening_post)
bundle.putStringArray("idOrCodeArray", new String[]{shortCode}); .create();
bundle.putInt("index", 0); alertDialog.show();
bundle.putBoolean("isId", false); new PostFetcher(shortCode, feedModel -> {
navController.navigate(R.id.action_global_postViewFragment, bundle); 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) { private void showLocationView(@NonNull final IntentModel intentModel) {
@ -626,4 +651,31 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
public BottomNavigationView getBottomNavView() { public BottomNavigationView getBottomNavView() {
return binding.bottomNavView; 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.content.Context;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.Filter;
import android.widget.Filterable;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import java.util.ArrayList; 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.comments.ChildCommentViewHolder;
import awais.instagrabber.adapters.viewholder.CommentViewHolder; import awais.instagrabber.adapters.viewholder.comments.ParentCommentViewHolder;
import awais.instagrabber.interfaces.MentionClickListener; import awais.instagrabber.databinding.ItemCommentBinding;
import awais.instagrabber.databinding.ItemCommentSmallBinding;
import awais.instagrabber.models.CommentModel; 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 final Map<Integer, Integer> positionTypeMap = new HashMap<>();
private LayoutInflater layoutInflater;
private final boolean isParent; // private final Filter filter = new Filter() {
private final View.OnClickListener onClickListener; // @NonNull
private final MentionClickListener mentionClickListener; // @Override
private final CommentModel[] commentModels; // protected FilterResults performFiltering(final CharSequence filter) {
private final String[] quantityStrings = new String[2]; // final FilterResults results = new FilterResults();
private final Filter filter = new Filter() { // results.values = commentModels;
@NonNull //
// 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 @Override
protected FilterResults performFiltering(final CharSequence filter) { public boolean areItemsTheSame(@NonNull final CommentModel oldItem, @NonNull final CommentModel newItem) {
final FilterResults results = new FilterResults(); return oldItem.getId().equals(newItem.getId());
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;
} }
@Override @Override
protected void publishResults(final CharSequence constraint, @NonNull final FilterResults results) { public boolean areContentsTheSame(@NonNull final CommentModel oldItem, @NonNull final CommentModel newItem) {
if (results.values instanceof CommentModel[]) { return oldItem.getId().equals(newItem.getId());
filteredCommentModels = (CommentModel[]) results.values;
notifyDataSetChanged();
}
} }
}; };
private final CommentCallback commentCallback;
private CommentModel selected;
private int selectedIndex;
public CommentsAdapter(final CommentModel[] commentModels, public CommentsAdapter(final CommentCallback commentCallback) {
final boolean isParent, super(DIFF_CALLBACK);
final View.OnClickListener onClickListener, this.commentCallback = commentCallback;
final MentionClickListener mentionClickListener) {
super();
this.commentModels = this.filteredCommentModels = commentModels;
this.isParent = isParent;
this.onClickListener = onClickListener;
this.mentionClickListener = mentionClickListener;
}
@Override
public Filter getFilter() {
return filter;
} }
@NonNull @NonNull
@Override @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(); final Context context = parent.getContext();
if (quantityStrings[0] == null) quantityStrings[0] = context.getString(R.string.single_like); final LayoutInflater layoutInflater = LayoutInflater.from(context);
if (quantityStrings[1] == null) quantityStrings[1] = context.getString(R.string.multiple_likes); if (type == TYPE_PARENT) {
if (layoutInflater == null) layoutInflater = LayoutInflater.from(context); final ItemCommentBinding binding = ItemCommentBinding.inflate(layoutInflater, parent, false);
final View view = layoutInflater.inflate(isParent ? R.layout.item_comment return new ParentCommentViewHolder(binding);
: R.layout.item_comment_small, }
parent, final ItemCommentSmallBinding binding = ItemCommentSmallBinding.inflate(layoutInflater, parent, false);
false); return new ChildCommentViewHolder(binding);
return new CommentViewHolder(view,
onClickListener,
mentionClickListener);
} }
@Override @Override
public void onBindViewHolder(@NonNull final CommentViewHolder holder, final int position) { public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
final CommentModel commentModel = filteredCommentModels[position]; final CommentModel commentModel = getItem(position);
if (commentModel != null) { if (commentModel == null) return;
holder.setCommentModel(commentModel); 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()); @Override
holder.setDate(commentModel.getDateTime()); public void submitList(@Nullable final List<CommentModel> list) {
holder.setLiked(commentModel.getLiked()); final List<CommentModel> flatList = flattenList(list);
super.submitList(flatList);
}
final long likes = commentModel.getLikes(); @Override
holder.setLikes(String.format(LocaleUtils.getCurrentLocale(), "%d %s", likes, quantityStrings[likes == 1 ? 0 : 1])); 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(); private List<CommentModel> flattenList(final List<CommentModel> list) {
if (profileModel != null) { if (list == null) {
holder.setUsername(profileModel.getUsername()); return Collections.emptyList();
holder.getProfilePicView().setImageURI(profileModel.getSdProfilePic()); }
} final List<CommentModel> flatList = new ArrayList<>();
if (holder.isParent()) { int lastCommentIndex = -1;
final CommentModel[] childCommentModels = commentModel.getChildCommentModels(); for (final CommentModel parent : list) {
if (childCommentModels != null && childCommentModels.length > 0) lastCommentIndex++;
holder.setChildAdapter(new CommentsAdapter(childCommentModels, false, onClickListener, mentionClickListener)); flatList.add(parent);
else holder.hideChildComments(); 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 @Override
public int getItemCount() { public int getItemViewType(final int position) {
return filteredCommentModels == null ? 0 : filteredCommentModels.length; 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); return new DirectMessageReelShareViewHolder(baseBinding, binding, onClickListener, mentionClickListener);
} }
case MEDIA_SHARE: case MEDIA_SHARE:
case FELIX_SHARE:
case CLIP: { case CLIP: {
final LayoutDmMediaShareBinding binding = LayoutDmMediaShareBinding.inflate(layoutInflater, itemViewParent, false); final LayoutDmMediaShareBinding binding = LayoutDmMediaShareBinding.inflate(layoutInflater, itemViewParent, false);
return new DirectMessageMediaShareViewHolder(baseBinding, binding, onClickListener); 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; // package awais.instagrabber.adapters;
//
import android.content.Context; // import android.content.Context;
import android.view.LayoutInflater; // import android.view.LayoutInflater;
import android.view.View; // import android.view.View;
import android.view.ViewGroup; // import android.view.ViewGroup;
//
import androidx.annotation.NonNull; // import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil; // import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter; // import androidx.recyclerview.widget.ListAdapter;
//
import awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder; // import awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder;
import awais.instagrabber.adapters.viewholder.feed.FeedPhotoViewHolder; // import awais.instagrabber.adapters.viewholder.feed.FeedPhotoViewHolder;
import awais.instagrabber.adapters.viewholder.feed.FeedSliderViewHolder; // import awais.instagrabber.adapters.viewholder.feed.FeedSliderViewHolder;
import awais.instagrabber.adapters.viewholder.feed.FeedVideoViewHolder; // import awais.instagrabber.adapters.viewholder.feed.FeedVideoViewHolder;
import awais.instagrabber.customviews.RamboTextView; // import awais.instagrabber.customviews.RamboTextView;
import awais.instagrabber.databinding.ItemFeedPhotoBinding; // import awais.instagrabber.databinding.ItemFeedPhotoBinding;
import awais.instagrabber.databinding.ItemFeedSliderBinding; // import awais.instagrabber.databinding.ItemFeedSliderBinding;
import awais.instagrabber.databinding.ItemFeedVideoBinding; // import awais.instagrabber.databinding.ItemFeedVideoBinding;
import awais.instagrabber.interfaces.MentionClickListener; // import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.FeedModel; // import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.enums.MediaItemType; // import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Utils; // import awais.instagrabber.utils.Utils;
//
public final class FeedAdapter extends ListAdapter<FeedModel, FeedItemViewHolder> { // public final class FeedAdapter extends ListAdapter<FeedModel, FeedItemViewHolder> {
private static final String TAG = "FeedAdapter"; // private static final String TAG = "FeedAdapter";
private final View.OnClickListener clickListener; // private final View.OnClickListener clickListener;
private final MentionClickListener mentionClickListener; // private final MentionClickListener mentionClickListener;
private final View.OnLongClickListener longClickListener = v -> { // private final View.OnLongClickListener longClickListener = v -> {
final Object tag; // final Object tag;
if (v instanceof RamboTextView && (tag = v.getTag()) instanceof FeedModel) // if (v instanceof RamboTextView && (tag = v.getTag()) instanceof FeedModel)
Utils.copyText(v.getContext(), ((FeedModel) tag).getPostCaption()); // Utils.copyText(v.getContext(), ((FeedModel) tag).getPostCaption());
return true; // return true;
}; // };
//
private static final DiffUtil.ItemCallback<FeedModel> diffCallback = new DiffUtil.ItemCallback<FeedModel>() { // private static final DiffUtil.ItemCallback<FeedModel> diffCallback = new DiffUtil.ItemCallback<FeedModel>() {
@Override // @Override
public boolean areItemsTheSame(@NonNull final FeedModel oldItem, @NonNull final FeedModel newItem) { // public boolean areItemsTheSame(@NonNull final FeedModel oldItem, @NonNull final FeedModel newItem) {
return oldItem.getPostId().equals(newItem.getPostId()); // return oldItem.getPostId().equals(newItem.getPostId());
} // }
//
@Override // @Override
public boolean areContentsTheSame(@NonNull final FeedModel oldItem, @NonNull final FeedModel newItem) { // public boolean areContentsTheSame(@NonNull final FeedModel oldItem, @NonNull final FeedModel newItem) {
return oldItem.getPostId().equals(newItem.getPostId()); // return oldItem.getPostId().equals(newItem.getPostId());
} // }
}; // };
//
public FeedAdapter(final View.OnClickListener clickListener, // public FeedAdapter(final View.OnClickListener clickListener,
final MentionClickListener mentionClickListener) { // final MentionClickListener mentionClickListener) {
super(diffCallback); // super(diffCallback);
// private final static String ellipsize = "… more"; // // private final static String ellipsize = "… more";
this.clickListener = clickListener; // this.clickListener = clickListener;
this.mentionClickListener = mentionClickListener; // this.mentionClickListener = mentionClickListener;
} // }
//
@NonNull // @NonNull
@Override // @Override
public FeedItemViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { // public FeedItemViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final Context context = parent.getContext(); // final Context context = parent.getContext();
final LayoutInflater layoutInflater = LayoutInflater.from(context); // final LayoutInflater layoutInflater = LayoutInflater.from(context);
final MediaItemType type = MediaItemType.valueOf(viewType); // final MediaItemType type = MediaItemType.valueOf(viewType);
switch (type) { // switch (type) {
case MEDIA_TYPE_VIDEO: { // case MEDIA_TYPE_VIDEO: {
final ItemFeedVideoBinding binding = ItemFeedVideoBinding.inflate(layoutInflater, parent, false); // final ItemFeedVideoBinding binding = ItemFeedVideoBinding.inflate(layoutInflater, parent, false);
return new FeedVideoViewHolder(binding, mentionClickListener, clickListener, longClickListener); // return new FeedVideoViewHolder(binding, mentionClickListener, clickListener, longClickListener);
} // }
case MEDIA_TYPE_SLIDER: { // case MEDIA_TYPE_SLIDER: {
final ItemFeedSliderBinding binding = ItemFeedSliderBinding.inflate(layoutInflater, parent, false); // final ItemFeedSliderBinding binding = ItemFeedSliderBinding.inflate(layoutInflater, parent, false);
return new FeedSliderViewHolder(binding, mentionClickListener, clickListener, longClickListener); // return new FeedSliderViewHolder(binding, mentionClickListener, clickListener, longClickListener);
} // }
case MEDIA_TYPE_IMAGE: // case MEDIA_TYPE_IMAGE:
default: { // default: {
final ItemFeedPhotoBinding binding = ItemFeedPhotoBinding.inflate(layoutInflater, parent, false); // final ItemFeedPhotoBinding binding = ItemFeedPhotoBinding.inflate(layoutInflater, parent, false);
return new FeedPhotoViewHolder(binding, mentionClickListener, clickListener, longClickListener); // return new FeedPhotoViewHolder(binding, mentionClickListener, clickListener, longClickListener);
} // }
} // }
} // }
//
@Override // @Override
public void onBindViewHolder(@NonNull final FeedItemViewHolder viewHolder, final int position) { // public void onBindViewHolder(@NonNull final FeedItemViewHolder viewHolder, final int position) {
final FeedModel feedModel = getItem(position); // final FeedModel feedModel = getItem(position);
if (feedModel == null) { // if (feedModel == null) {
return; // return;
} // }
feedModel.setPosition(position); // feedModel.setPosition(position);
viewHolder.bind(feedModel); // viewHolder.bind(feedModel, (feedModel1, view, postImage) -> {});
} // }
//
@Override // @Override
public int getItemViewType(final int position) { // public int getItemViewType(final int position) {
return getItem(position).getItemType().getId(); // return getItem(position).getItemType().getId();
} // }
//
@Override // @Override
public void onViewAttachedToWindow(@NonNull final FeedItemViewHolder holder) { // public void onViewAttachedToWindow(@NonNull final FeedItemViewHolder holder) {
super.onViewAttachedToWindow(holder); // super.onViewAttachedToWindow(holder);
// Log.d(TAG, "attached holder: " + holder); // // Log.d(TAG, "attached holder: " + holder);
if (!(holder instanceof FeedSliderViewHolder)) return; // if (!(holder instanceof FeedSliderViewHolder)) return;
final FeedSliderViewHolder feedSliderViewHolder = (FeedSliderViewHolder) holder; // final FeedSliderViewHolder feedSliderViewHolder = (FeedSliderViewHolder) holder;
feedSliderViewHolder.startPlayingVideo(); // feedSliderViewHolder.startPlayingVideo();
} // }
//
@Override // @Override
public void onViewDetachedFromWindow(@NonNull final FeedItemViewHolder holder) { // public void onViewDetachedFromWindow(@NonNull final FeedItemViewHolder holder) {
super.onViewDetachedFromWindow(holder); // super.onViewDetachedFromWindow(holder);
// Log.d(TAG, "detached holder: " + holder); // // Log.d(TAG, "detached holder: " + holder);
if (!(holder instanceof FeedSliderViewHolder)) return; // if (!(holder instanceof FeedSliderViewHolder)) return;
final FeedSliderViewHolder feedSliderViewHolder = (FeedSliderViewHolder) holder; // final FeedSliderViewHolder feedSliderViewHolder = (FeedSliderViewHolder) holder;
feedSliderViewHolder.stopPlayingVideo(); // 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> { ListAdapter<T, VH> {
private boolean isSelecting; private boolean isSelecting;
private final OnItemClickListener<T> internalOnItemClickListener; private OnItemClickListener<T> internalOnItemClickListener;
private final OnItemLongClickListener<T> internalOnLongItemClickListener; private OnItemLongClickListener<T> internalOnLongItemClickListener;
private final List<T> selectedItems = new ArrayList<>(); private final List<T> selectedItems = new ArrayList<>();

View File

@ -1,77 +1,75 @@
package awais.instagrabber.adapters; // package awais.instagrabber.adapters;
//
import android.view.LayoutInflater; // import android.view.LayoutInflater;
import android.view.View; // import android.view.View;
import android.view.ViewGroup; // import android.view.ViewGroup;
//
import androidx.annotation.NonNull; // import androidx.annotation.NonNull;
import androidx.recyclerview.widget.DiffUtil; // import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter; // import androidx.recyclerview.widget.ListAdapter;
//
import java.util.Arrays; // import awais.instagrabber.adapters.viewholder.PostViewerViewHolder;
// import awais.instagrabber.databinding.ItemFullPostViewBinding;
import awais.instagrabber.adapters.viewholder.PostViewerViewHolder; // import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.databinding.ItemFullPostViewBinding; // import awais.instagrabber.models.ViewerPostModelWrapper;
import awais.instagrabber.interfaces.MentionClickListener; //
import awais.instagrabber.models.ViewerPostModelWrapper; // public class PostViewAdapter extends ListAdapter<ViewerPostModelWrapper, PostViewerViewHolder> {
// private final OnPostViewChildViewClickListener clickListener;
public class PostViewAdapter extends ListAdapter<ViewerPostModelWrapper, PostViewerViewHolder> { // private final OnPostCaptionLongClickListener longClickListener;
private final OnPostViewChildViewClickListener clickListener; // private final MentionClickListener mentionClickListener;
private final OnPostCaptionLongClickListener longClickListener; //
private final MentionClickListener mentionClickListener; // private static final DiffUtil.ItemCallback<ViewerPostModelWrapper> diffCallback = new DiffUtil.ItemCallback<ViewerPostModelWrapper>() {
// @Override
private static final DiffUtil.ItemCallback<ViewerPostModelWrapper> diffCallback = new DiffUtil.ItemCallback<ViewerPostModelWrapper>() { // public boolean areItemsTheSame(@NonNull final ViewerPostModelWrapper oldItem,
@Override // @NonNull final ViewerPostModelWrapper newItem) {
public boolean areItemsTheSame(@NonNull final ViewerPostModelWrapper oldItem, // return oldItem.getPosition() == newItem.getPosition();
@NonNull final ViewerPostModelWrapper newItem) { // }
return oldItem.getPosition() == newItem.getPosition(); //
} // @Override
// public boolean areContentsTheSame(@NonNull final ViewerPostModelWrapper oldItem,
@Override // @NonNull final ViewerPostModelWrapper newItem) {
public boolean areContentsTheSame(@NonNull final ViewerPostModelWrapper oldItem, // return oldItem.getViewerPostModels().equals(newItem.getViewerPostModels());
@NonNull final ViewerPostModelWrapper newItem) { // }
return Arrays.equals(oldItem.getViewerPostModels(), newItem.getViewerPostModels()); // };
} //
}; // public PostViewAdapter(final OnPostViewChildViewClickListener clickListener,
// final OnPostCaptionLongClickListener longClickListener,
public PostViewAdapter(final OnPostViewChildViewClickListener clickListener, // final MentionClickListener mentionClickListener) {
final OnPostCaptionLongClickListener longClickListener, // super(diffCallback);
final MentionClickListener mentionClickListener) { // this.clickListener = clickListener;
super(diffCallback); // this.longClickListener = longClickListener;
this.clickListener = clickListener; // this.mentionClickListener = mentionClickListener;
this.longClickListener = longClickListener; // }
this.mentionClickListener = mentionClickListener; //
} // @Override
// public void onViewDetachedFromWindow(@NonNull final PostViewerViewHolder holder) {
@Override // holder.stopPlayingVideo();
public void onViewDetachedFromWindow(@NonNull final PostViewerViewHolder holder) { // }
holder.stopPlayingVideo(); //
} // @NonNull
// @Override
@NonNull // public PostViewerViewHolder onCreateViewHolder(@NonNull final ViewGroup parent,
@Override // final int viewType) {
public PostViewerViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, // final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
final int viewType) { // final ItemFullPostViewBinding binding = ItemFullPostViewBinding
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); // .inflate(layoutInflater, parent, false);
final ItemFullPostViewBinding binding = ItemFullPostViewBinding // return new PostViewerViewHolder(binding);
.inflate(layoutInflater, parent, false); // }
return new PostViewerViewHolder(binding); //
} // @Override
// public void onBindViewHolder(@NonNull final PostViewerViewHolder holder, final int position) {
@Override // final ViewerPostModelWrapper item = getItem(position);
public void onBindViewHolder(@NonNull final PostViewerViewHolder holder, final int position) { // holder.bind(item, position, clickListener, longClickListener, mentionClickListener);
final ViewerPostModelWrapper item = getItem(position); // }
holder.bind(item, position, clickListener, longClickListener, mentionClickListener); //
} // public interface OnPostViewChildViewClickListener {
// void onClick(View v,
public interface OnPostViewChildViewClickListener { // ViewerPostModelWrapper viewerPostModelWrapper,
void onClick(View v, // int postPosition,
ViewerPostModelWrapper viewerPostModelWrapper, // int childPosition);
int postPosition, // }
int childPosition); //
} // public interface OnPostCaptionLongClickListener {
// void onLongClick(String text);
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.drawee.interfaces.DraweeController;
import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder; import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.ProgressiveMediaSource; 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; if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
player.setVolume(vol); player.setVolume(vol);
player.setPlayWhenReady(Utils.settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS)); player.setPlayWhenReady(Utils.settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS));
final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(new DefaultDataSourceFactory(itemView.getContext(), "instagram")) final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(
.createMediaSource(Uri.parse(item.getDisplayUrl())); new DefaultDataSourceFactory(itemView.getContext(), "instagram")
).createMediaSource(MediaItem.fromUri(item.getDisplayUrl()));
// mediaSource.addEventListener(new Handler(), new MediaSourceEventListener() { // mediaSource.addEventListener(new Handler(), new MediaSourceEventListener() {
// @Override // @Override
// public void onLoadCompleted(final int windowIndex, @Nullable final MediaSource.MediaPeriodId mediaPeriodId, final LoadEventInfo loadEventInfo, final MediaLoadData mediaLoadData) { // 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); // viewerBinding.progressView.setVisibility(View.GONE);
// } // }
// }); // });
player.prepare(mediaSource); player.setMediaSource(mediaSource);
player.prepare();
player.setVolume(vol); player.setVolume(vol);
// viewerBinding.bottomPanel.btnMute.setImageResource(vol == 0f ? R.drawable.ic_volume_up_24 : R.drawable.ic_volume_off_24); // viewerBinding.bottomPanel.btnMute.setImageResource(vol == 0f ? R.drawable.ic_volume_up_24 : R.drawable.ic_volume_off_24);
// viewerBinding.bottomPanel.btnMute.setOnClickListener(onClickListener); // 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 MEDIA_SHARE:
case RAVEN_MEDIA: case RAVEN_MEDIA:
case CLIP: case CLIP:
case FELIX_SHARE:
messageText = context.getString(R.string.direct_messages_sent_media); messageText = context.getString(R.string.direct_messages_sent_media);
break; break;
case ACTION_LOG: 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; text = R.string.mention_notif;
subtext = model.getText(); subtext = model.getText();
break; break;
case TAGGED:
text = R.string.tagged_notif;
break;
case FOLLOW: case FOLLOW:
text = R.string.follow_notif; text = R.string.follow_notif;
break; 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) { public void bind(final ViewerPostModel model, final int position, final View.OnClickListener clickListener) {
if (model == null) return; if (model == null) return;
model.setPosition(position); // model.setPosition(position);
itemView.setTag(model); itemView.setTag(model);
itemView.setOnClickListener(clickListener); itemView.setOnClickListener(clickListener);
binding.selectedView.setVisibility(model.isCurrentSlide() ? View.VISIBLE : View.GONE); binding.selectedView.setVisibility(model.isCurrentSlide() ? View.VISIBLE : View.GONE);
binding.isDownloaded.setVisibility(model.isDownloaded() ? 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 OnItemClickListener<PostModel> clickListener,
final OnItemLongClickListener<PostModel> longClickListener) { final OnItemLongClickListener<PostModel> longClickListener) {
if (postModel == null) return; if (postModel == null) return;
postModel.setPosition(position); // postModel.setPosition(position);
itemView.setOnClickListener(v -> clickListener.onItemClick(postModel, position)); itemView.setOnClickListener(v -> clickListener.onItemClick(postModel, position));
itemView.setOnLongClickListener(v -> longClickListener.onItemLongClick(postModel, position)); itemView.setOnLongClickListener(v -> longClickListener.onItemLongClick(postModel, position));

View File

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

View File

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

View File

@ -26,7 +26,7 @@ public class DirectMessageMediaViewHolder extends DirectMessageItemViewHolder {
super(baseBinding, onClickListener); super(baseBinding, onClickListener);
this.binding = binding; this.binding = binding;
maxHeight = itemView.getResources().getDimensionPixelSize(R.dimen.dm_media_img_max_height); 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()); setItemView(binding.getRoot());
} }

View File

@ -1,92 +1,110 @@
package awais.instagrabber.adapters.viewholder.feed; package awais.instagrabber.adapters.viewholder.feed;
import android.text.SpannableString; import android.text.method.LinkMovementMethod;
import android.text.Spanned; import android.transition.TransitionManager;
import android.view.View; import android.view.View;
import android.widget.RelativeLayout; import android.widget.RelativeLayout;
import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import awais.instagrabber.customviews.CommentMentionClickSpan; import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.customviews.RamboTextView;
import awais.instagrabber.databinding.ItemFeedBottomBinding; import awais.instagrabber.databinding.ItemFeedBottomBinding;
import awais.instagrabber.databinding.ItemFeedTopBinding; import awais.instagrabber.databinding.ItemFeedTopBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import static android.text.TextUtils.TruncateAt.END;
public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder { 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 ItemFeedTopBinding topBinding;
private final ItemFeedBottomBinding bottomBinding; private final ItemFeedBottomBinding bottomBinding;
private final MentionClickListener mentionClickListener; private final FeedAdapterV2.FeedItemCallback feedItemCallback;
public FeedItemViewHolder(@NonNull final View root, public FeedItemViewHolder(@NonNull final View root,
final ItemFeedTopBinding topBinding, final ItemFeedTopBinding topBinding,
final ItemFeedBottomBinding bottomBinding, final ItemFeedBottomBinding bottomBinding,
final MentionClickListener mentionClickListener, final FeedAdapterV2.FeedItemCallback feedItemCallback) {
final View.OnClickListener clickListener,
final View.OnLongClickListener longClickListener) {
super(root); super(root);
this.topBinding = topBinding; this.topBinding = topBinding;
this.bottomBinding = bottomBinding; this.bottomBinding = bottomBinding;
this.mentionClickListener = mentionClickListener; topBinding.title.setMovementMethod(new LinkMovementMethod());
// topBinding.title.setMovementMethod(new LinkMovementMethod()); this.feedItemCallback = feedItemCallback;
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);
} }
public void bind(final FeedModel feedModel) { public void bind(final FeedModel feedModel) {
if (feedModel == null) { if (feedModel == null) {
return; return;
} }
topBinding.viewStoryPost.setTag(feedModel); setupProfilePic(feedModel);
topBinding.ivProfilePic.setTag(feedModel); setupLocation(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));
}
bottomBinding.tvPostDate.setText(feedModel.getPostDate()); bottomBinding.tvPostDate.setText(feedModel.getPostDate());
final long commentsCount = feedModel.getCommentsCount(); setupComments(feedModel);
bottomBinding.commentsCount.setText(String.valueOf(commentsCount)); setupCaption(feedModel);
if (feedModel.getItemType() != MediaItemType.MEDIA_TYPE_SLIDER) {
final String locationName = feedModel.getLocationName(); bottomBinding.btnDownload.setOnClickListener(v -> feedItemCallback.onDownloadClick(feedModel, -1));
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);
}
} }
expandCollapseTextView(bottomBinding.viewerCaption, feedModel.getPostCaption());
bindItem(feedModel); 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)) { if (TextUtils.isEmpty(locationName)) {
topBinding.location.setVisibility(View.GONE); topBinding.location.setVisibility(View.GONE);
topBinding.title.setLayoutParams(new RelativeLayout.LayoutParams( topBinding.title.setLayoutParams(new RelativeLayout.LayoutParams(
@ -98,41 +116,9 @@ public abstract class FeedItemViewHolder extends RecyclerView.ViewHolder {
topBinding.title.setLayoutParams(new RelativeLayout.LayoutParams( topBinding.title.setLayoutParams(new RelativeLayout.LayoutParams(
RelativeLayout.LayoutParams.MATCH_PARENT, RelativeLayout.LayoutParams.WRAP_CONTENT 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); public abstract void bindItem(final FeedModel feedModel);
} }

View File

@ -1,8 +1,9 @@
package awais.instagrabber.adapters.viewholder.feed; package awais.instagrabber.adapters.viewholder.feed;
import android.net.Uri; import android.net.Uri;
import android.view.GestureDetector;
import android.view.MotionEvent;
import android.view.View; import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull; 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.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder; import com.facebook.imagepipeline.request.ImageRequestBuilder;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.databinding.ItemFeedPhotoBinding; import awais.instagrabber.databinding.ItemFeedPhotoBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
public class FeedPhotoViewHolder extends FeedItemViewHolder { public class FeedPhotoViewHolder extends FeedItemViewHolder {
private static final String TAG = "FeedPhotoViewHolder"; private static final String TAG = "FeedPhotoViewHolder";
private final ItemFeedPhotoBinding binding; private final ItemFeedPhotoBinding binding;
private final FeedAdapterV2.FeedItemCallback feedItemCallback;
public FeedPhotoViewHolder(@NonNull final ItemFeedPhotoBinding binding, public FeedPhotoViewHolder(@NonNull final ItemFeedPhotoBinding binding,
final MentionClickListener mentionClickListener, final FeedAdapterV2.FeedItemCallback feedItemCallback) {
final View.OnClickListener clickListener, super(binding.getRoot(), binding.itemFeedTop, binding.itemFeedBottom, feedItemCallback);
final View.OnLongClickListener longClickListener) {
super(binding.getRoot(), binding.itemFeedTop, binding.itemFeedBottom, mentionClickListener, clickListener, longClickListener);
this.binding = binding; this.binding = binding;
binding.itemFeedBottom.videoViewsContainer.setVisibility(View.GONE); this.feedItemCallback = feedItemCallback;
binding.itemFeedBottom.btnMute.setVisibility(View.GONE); binding.itemFeedBottom.tvVideoViews.setVisibility(View.GONE);
// binding.itemFeedBottom.btnMute.setVisibility(View.GONE);
binding.imageViewer.setAllowTouchInterceptionWhileZoomed(false); binding.imageViewer.setAllowTouchInterceptionWhileZoomed(false);
final GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(itemView.getContext().getResources()) final GenericDraweeHierarchy hierarchy = new GenericDraweeHierarchyBuilder(itemView.getContext().getResources())
.setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER) .setActualImageScaleType(ScalingUtils.ScaleType.FIT_CENTER)
@ -44,33 +44,35 @@ public class FeedPhotoViewHolder extends FeedItemViewHolder {
if (feedModel == null) { if (feedModel == null) {
return; return;
} }
final ViewGroup.LayoutParams layoutParams = binding.imageViewer.getLayoutParams(); binding.getRoot().post(() -> {
final int requiredWidth = Utils.displayMetrics.widthPixels; setDimensions(feedModel);
layoutParams.width = feedModel.getImageWidth() == 0 ? requiredWidth : feedModel.getImageWidth(); final String thumbnailUrl = feedModel.getThumbnailUrl();
layoutParams.height = feedModel.getImageHeight() == 0 ? requiredWidth + 1 : feedModel.getImageHeight(); String url = feedModel.getDisplayUrl();
binding.imageViewer.requestLayout(); if (TextUtils.isEmpty(url)) url = thumbnailUrl;
final String thumbnailUrl = feedModel.getThumbnailUrl(); final ImageRequest requestBuilder = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url))
String url = feedModel.getDisplayUrl(); // .setLocalThumbnailPreviewsEnabled(true)
if (TextUtils.isEmpty(url)) url = thumbnailUrl; // .setProgressiveRenderingEnabled(true)
final ImageRequest requestBuilder = ImageRequestBuilder.newBuilderWithSource(Uri.parse(url)) .build();
.setLocalThumbnailPreviewsEnabled(true) binding.imageViewer.setController(Fresco.newDraweeControllerBuilder()
.setProgressiveRenderingEnabled(true) .setImageRequest(requestBuilder)
.build(); .setOldController(binding.imageViewer.getController())
binding.imageViewer.setController(Fresco.newDraweeControllerBuilder() .setLowResImageRequest(ImageRequest.fromUri(thumbnailUrl))
.setImageRequest(requestBuilder) .build());
.setOldController(binding.imageViewer.getController()) binding.imageViewer.setTapListener(new GestureDetector.SimpleOnGestureListener() {
.setLowResImageRequest(ImageRequest.fromUri(thumbnailUrl)) @Override
.build()); public boolean onSingleTapConfirmed(final MotionEvent e) {
// binding.imageViewer.setImageURI(url); if (feedItemCallback != null) {
// final RequestBuilder<Bitmap> thumbnailRequestBuilder = glide feedItemCallback.onPostClick(feedModel, binding.itemFeedTop.ivProfilePic, binding.imageViewer);
// .asBitmap() return true;
// .load(thumbnailUrl) }
// .diskCacheStrategy(DiskCacheStrategy.ALL); return false;
// glide.asBitmap() }
// .load(url) });
// .thumbnail(thumbnailRequestBuilder) });
// .diskCacheStrategy(DiskCacheStrategy.ALL) }
// .into(customTarget);
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; package awais.instagrabber.adapters.viewholder.feed;
import android.content.Context; import android.content.Context;
import android.net.Uri;
import android.view.Gravity;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.FrameLayout; import android.view.ViewTreeObserver;
import android.widget.ViewSwitcher; import android.widget.ViewSwitcher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.appcompat.widget.AppCompatImageView; import androidx.viewpager2.widget.ViewPager2;
import androidx.viewpager.widget.PagerAdapter;
import androidx.viewpager.widget.ViewPager;
import com.facebook.drawee.drawable.ScalingUtils; import com.google.android.exoplayer2.MediaItem;
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.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.SimpleExoPlayer;
import com.google.android.exoplayer2.source.ProgressiveMediaSource; 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.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.SimpleCache; 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.databinding.ItemFeedSliderBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.ViewerPostModel; import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.NumberUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper; 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 static final boolean shouldAutoPlay = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
private final ItemFeedSliderBinding binding; private final ItemFeedSliderBinding binding;
private final FeedAdapterV2.FeedItemCallback feedItemCallback;
private final DefaultDataSourceFactory dataSourceFactory; private final DefaultDataSourceFactory dataSourceFactory;
private final PlayerChangeListener playerChangeListener = (position, player) -> {
pagerPlayer = player;
playerPosition = position;
};
private CacheDataSourceFactory cacheDataSourceFactory; private CacheDataSourceFactory cacheDataSourceFactory;
private SimpleExoPlayer pagerPlayer; private SimpleExoPlayer pagerPlayer;
private int playerPosition = 0; private int playerPosition = 0;
public FeedSliderViewHolder(@NonNull final ItemFeedSliderBinding binding, public FeedSliderViewHolder(@NonNull final ItemFeedSliderBinding binding,
final MentionClickListener mentionClickListener, final FeedAdapterV2.FeedItemCallback feedItemCallback) {
final View.OnClickListener clickListener, super(binding.getRoot(), binding.itemFeedTop, binding.itemFeedBottom, feedItemCallback);
final View.OnLongClickListener longClickListener) {
super(binding.getRoot(), binding.itemFeedTop, binding.itemFeedBottom, mentionClickListener, clickListener, longClickListener);
this.binding = binding; this.binding = binding;
binding.itemFeedBottom.videoViewsContainer.setVisibility(View.GONE); this.feedItemCallback = feedItemCallback;
binding.itemFeedBottom.btnMute.setVisibility(View.GONE); binding.itemFeedBottom.tvVideoViews.setVisibility(View.GONE);
// binding.itemFeedBottom.btnMute.setVisibility(View.GONE);
final ViewGroup.LayoutParams layoutParams = binding.mediaList.getLayoutParams(); final ViewGroup.LayoutParams layoutParams = binding.mediaList.getLayoutParams();
layoutParams.height = Utils.displayMetrics.widthPixels + 1; layoutParams.height = Utils.displayMetrics.widthPixels + 1;
binding.mediaList.setLayoutParams(layoutParams); binding.mediaList.setLayoutParams(layoutParams);
@ -76,112 +65,108 @@ public class FeedSliderViewHolder extends FeedItemViewHolder {
@Override @Override
public void bindItem(final FeedModel feedModel) { public void bindItem(final FeedModel feedModel) {
final ViewerPostModel[] sliderItems = feedModel.getSliderItems(); final List<PostChild> sliderItems = feedModel.getSliderItems();
final int sliderItemLen = sliderItems != null ? sliderItems.length : 0; final int sliderItemLen = sliderItems != null ? sliderItems.size() : 0;
if (sliderItemLen <= 0) { if (sliderItemLen <= 0) return;
return;
}
final String text = "1/" + sliderItemLen; final String text = "1/" + sliderItemLen;
binding.mediaCounter.setText(text); binding.mediaCounter.setText(text);
binding.mediaList.setOffscreenPageLimit(Math.min(5, sliderItemLen)); binding.mediaList.setOffscreenPageLimit(1);
final SliderItemsAdapter adapter = new SliderItemsAdapter(null, null, false, new SliderCallbackAdapter() {
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;
@Override @Override
public void onPageSelected(final int position) { public void onItemClicked(final int position) {
ViewerPostModel sliderItem = sliderItems[prevPos]; feedItemCallback.onSliderClick(feedModel, position);
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);
} }
}); });
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 -> { private void setDimensions(final View view, final PostChild model) {
final int currentItem = binding.mediaList.getCurrentItem(); final ViewGroup.LayoutParams layoutParams = binding.mediaList.getLayoutParams();
if (currentItem < 0 || currentItem >= binding.mediaList.getChildCount()) { int requiredWidth = layoutParams.width;
return; if (requiredWidth <= 0) {
} final ViewTreeObserver.OnPreDrawListener preDrawListener = new ViewTreeObserver.OnPreDrawListener() {
final ViewerPostModel sliderItem = sliderItems[currentItem]; @Override
if (sliderItem.getItemType() != MediaItemType.MEDIA_TYPE_VIDEO) { public boolean onPreDraw() {
return; view.getViewTreeObserver().removeOnPreDrawListener(this);
} setLayoutParamDimens(binding.mediaList, model);
final View currentView = binding.mediaList.getChildAt(currentItem); return true;
if (!(currentView instanceof ViewSwitcher)) { }
return; };
} view.getViewTreeObserver().addOnPreDrawListener(preDrawListener);
final ViewSwitcher viewSwitcher = (ViewSwitcher) currentView; return;
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);
} }
binding.itemFeedBottom.btnMute.setImageResource(Utils.sessionVolumeFull ? R.drawable.ic_volume_off_24 : R.drawable.ic_volume_up_24); setLayoutParamDimens(binding.mediaList, model);
binding.itemFeedBottom.btnMute.setOnClickListener(muteClickListener); }
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) { private void autoPlay(final int position) {
if (!shouldAutoPlay) { // if (!shouldAutoPlay) {
return; // return;
} // }
final ChildMediaItemsAdapter adapter = (ChildMediaItemsAdapter) binding.mediaList.getAdapter(); // final ChildMediaItemsAdapter adapter = (ChildMediaItemsAdapter) binding.mediaList.getAdapter();
if (adapter == null) { // if (adapter == null) {
return; // return;
} // }
final ViewerPostModel sliderItem = adapter.getItemAtPosition(position); // final ViewerPostModel sliderItem = adapter.getItemAtPosition(position);
if (sliderItem.getItemType() != MediaItemType.MEDIA_TYPE_VIDEO) { // if (sliderItem.getItemType() != MediaItemType.MEDIA_TYPE_VIDEO) {
return; // return;
} // }
final ViewSwitcher viewSwitcher = (ViewSwitcher) binding.mediaList.getChildAt(position); // final ViewSwitcher viewSwitcher = (ViewSwitcher) binding.mediaList.getChildAt(position);
loadPlayer(binding.getRoot().getContext(), // loadPlayer(binding.getRoot().getContext(),
position, sliderItem.getDisplayUrl(), // position, sliderItem.getDisplayUrl(),
viewSwitcher, // viewSwitcher,
cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory, // cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory,
playerChangeListener); // playerChangeListener);
} }
public void startPlayingVideo() { public void startPlayingVideo() {
@ -200,7 +185,8 @@ public class FeedSliderViewHolder extends FeedItemViewHolder {
} }
private static void loadPlayer(final Context context, private static void loadPlayer(final Context context,
final int position, final String displayUrl, final int position,
final String displayUrl,
final ViewSwitcher viewSwitcher, final ViewSwitcher viewSwitcher,
final DataSource.Factory factory, final DataSource.Factory factory,
final PlayerChangeListener playerChangeListener) { final PlayerChangeListener playerChangeListener) {
@ -223,119 +209,14 @@ public class FeedSliderViewHolder extends FeedItemViewHolder {
if (vol == 0f && Utils.sessionVolumeFull) vol = 1f; if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
player.setVolume(vol); player.setVolume(vol);
player.setPlayWhenReady(Utils.settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS)); 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.setRepeatMode(Player.REPEAT_MODE_ALL);
player.prepare(mediaSource); player.setMediaSource(mediaSource);
player.prepare();
player.setVolume(vol); player.setVolume(vol);
playerChangeListener.playerChanged(position, player); playerChangeListener.playerChanged(position, player);
viewSwitcher.setTag(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; package awais.instagrabber.adapters.viewholder.feed;
import android.content.Context; import android.content.Context;
import android.net.Uri;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.view.View; import android.view.View;
@ -9,21 +8,14 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull; 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.DefaultDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory; import com.google.android.exoplayer2.upstream.cache.CacheDataSourceFactory;
import com.google.android.exoplayer2.upstream.cache.SimpleCache; 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.databinding.ItemFeedVideoBinding;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.NumberUtils; import awais.instagrabber.utils.NumberUtils;
@ -35,27 +27,26 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
private static final String TAG = "FeedVideoViewHolder"; private static final String TAG = "FeedVideoViewHolder";
private final ItemFeedVideoBinding binding; private final ItemFeedVideoBinding binding;
private final FeedAdapterV2.FeedItemCallback feedItemCallback;
private final Handler handler; private final Handler handler;
private final DefaultDataSourceFactory dataSourceFactory; private final DefaultDataSourceFactory dataSourceFactory;
private CacheDataSourceFactory cacheDataSourceFactory; private CacheDataSourceFactory cacheDataSourceFactory;
private FeedModel feedModel; private FeedModel feedModel;
private SimpleExoPlayer player;
private final Runnable loadRunnable = new Runnable() { // private final Runnable loadRunnable = new Runnable() {
@Override // @Override
public void run() { // public void run() {
loadPlayer(feedModel); // // loadPlayer(feedModel);
} // }
}; // };
public FeedVideoViewHolder(@NonNull final ItemFeedVideoBinding binding, public FeedVideoViewHolder(@NonNull final ItemFeedVideoBinding binding,
final MentionClickListener mentionClickListener, final FeedAdapterV2.FeedItemCallback feedItemCallback) {
final View.OnClickListener clickListener, super(binding.getRoot(), binding.itemFeedTop, binding.itemFeedBottom, feedItemCallback);
final View.OnLongClickListener longClickListener) {
super(binding.getRoot(), binding.itemFeedTop, binding.itemFeedBottom, mentionClickListener, clickListener, longClickListener);
this.binding = binding; this.binding = binding;
binding.itemFeedBottom.videoViewsContainer.setVisibility(View.VISIBLE); this.feedItemCallback = feedItemCallback;
binding.itemFeedBottom.tvVideoViews.setVisibility(View.VISIBLE);
handler = new Handler(Looper.getMainLooper()); handler = new Handler(Looper.getMainLooper());
final Context context = binding.getRoot().getContext(); final Context context = binding.getRoot().getContext();
dataSourceFactory = new DefaultDataSourceFactory(context, "instagram"); dataSourceFactory = new DefaultDataSourceFactory(context, "instagram");
@ -69,90 +60,80 @@ public class FeedVideoViewHolder extends FeedItemViewHolder {
public void bindItem(final FeedModel feedModel) { public void bindItem(final FeedModel feedModel) {
// Log.d(TAG, "Binding post: " + feedModel.getPostId()); // Log.d(TAG, "Binding post: " + feedModel.getPostId());
this.feedModel = feedModel; this.feedModel = feedModel;
setThumbnail(feedModel);
binding.itemFeedBottom.tvVideoViews.setText(String.valueOf(feedModel.getViewCount())); 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) { @Override
final ViewGroup.LayoutParams layoutParams = binding.thumbnailParent.getLayoutParams(); public void onThumbnailClick() {
final int requiredWidth = Utils.displayMetrics.widthPixels; feedItemCallback.onPostClick(feedModel, binding.itemFeedTop.ivProfilePic, binding.videoPost.thumbnail);
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));
}
private void loadPlayer(final FeedModel feedModel) { @Override
if (feedModel == null) { public void onPlayerViewLoaded() {
return; // binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE);
} final ViewGroup.LayoutParams layoutParams = binding.videoPost.playerView.getLayoutParams();
// Log.d(TAG, "playing post:" + feedModel.getPostId()); final int requiredWidth = Utils.displayMetrics.widthPixels;
if (binding.viewSwitcher.getDisplayedChild() == 0) { final int resultingHeight = NumberUtils.getResultingHeight(requiredWidth, feedModel.getImageHeight(), feedModel.getImageWidth());
binding.viewSwitcher.showNext(); layoutParams.width = requiredWidth;
} layoutParams.height = resultingHeight;
binding.itemFeedBottom.btnMute.setVisibility(View.VISIBLE); binding.videoPost.playerView.requestLayout();
final ViewGroup.LayoutParams layoutParams = binding.playerView.getLayoutParams(); setMuteIcon(vol == 0f && Utils.sessionVolumeFull ? 1f : vol);
final int requiredWidth = Utils.displayMetrics.widthPixels; }
final int resultingHeight = NumberUtils.getResultingHeight(requiredWidth, feedModel.getImageHeight(), feedModel.getImageWidth()); };
layoutParams.width = requiredWidth; // final DataSource.Factory factory = cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory;
layoutParams.height = resultingHeight; // final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(factory);
binding.playerView.requestLayout(); // final Uri uri = Uri.parse(feedModel.getDisplayUrl());
float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f; // final MediaItem mediaItem = MediaItem.fromUri(uri);
if (vol == 0f && Utils.sessionVolumeFull) vol = 1f; // final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(mediaItem);
setMuteIcon(vol); final float aspectRatio = (float) feedModel.getImageWidth() / feedModel.getImageHeight();
player = (SimpleExoPlayer) binding.playerView.getPlayer(); final VideoPlayerViewHelper videoPlayerViewHelper = new VideoPlayerViewHelper(binding.getRoot().getContext(),
if (player != null) { binding.videoPost,
player.release(); feedModel.getDisplayUrl(),
} vol,
player = new SimpleExoPlayer.Builder(itemView.getContext()) aspectRatio,
.setLooper(Looper.getMainLooper()) feedModel.getThumbnailUrl(),
.build(); false,
player.setVolume(vol); null,
player.setPlayWhenReady(true); videoPlayerCallback);
final DataSource.Factory factory = cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory; binding.videoPost.thumbnail.post(() -> {
final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(factory); if (feedModel.getImageHeight() > 0.8 * Utils.displayMetrics.heightPixels) {
final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(Uri.parse(feedModel.getDisplayUrl())); final ViewGroup.LayoutParams layoutParams = binding.videoPost.thumbnail.getLayoutParams();
player.setRepeatMode(Player.REPEAT_MODE_ALL); layoutParams.height = (int) (0.8 * Utils.displayMetrics.heightPixels);
player.prepare(mediaSource); binding.videoPost.thumbnail.requestLayout();
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;
}); });
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) { 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() { public FeedModel getCurrentFeedModel() {
return feedModel; return feedModel;
} }
public void stopPlaying() { // public void stopPlaying() {
// Log.d(TAG, "Stopping post: " + feedModel.getPostId() + ", player: " + player + ", player.isPlaying: " + (player != null && player.isPlaying())); // // Log.d(TAG, "Stopping post: " + feedModel.getPostId() + ", player: " + player + ", player.isPlaying: " + (player != null && player.isPlaying()));
handler.removeCallbacks(loadRunnable); // handler.removeCallbacks(loadRunnable);
if (player != null) { // if (player != null) {
player.release(); // player.release();
} // }
if (binding.viewSwitcher.getDisplayedChild() == 1) { // if (binding.videoPost.root.getDisplayedChild() == 1) {
binding.viewSwitcher.showPrevious(); // binding.videoPost.root.showPrevious();
} // }
} // }
//
public void startPlaying() { // public void startPlaying() {
handler.removeCallbacks(loadRunnable); // handler.removeCallbacks(loadRunnable);
handler.postDelayed(loadRunnable, 800); // 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.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Collections; import java.util.List;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
@ -25,23 +25,20 @@ import awaisomereport.LogCollector;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
public final class CommentsFetcher extends AsyncTask<Void, Void, CommentModel[]> { public final class CommentsFetcher extends AsyncTask<Void, Void, List<CommentModel>> {
private final String shortCode; private static final String TAG = "CommentsFetcher";
private final FetchListener<CommentModel[]> fetchListener;
/* private final String shortCode;
* i fucking spent the whole day on this and fixing all the fucking problems in this class. private final FetchListener<List<CommentModel>> fetchListener;
* DO NO FUCK WITH THIS CODE!
* -AWAiS (The Badak) @the.badak public CommentsFetcher(final String shortCode, final FetchListener<List<CommentModel>> fetchListener) {
*/
public CommentsFetcher(final String shortCode, final FetchListener<CommentModel[]> fetchListener) {
this.shortCode = shortCode; this.shortCode = shortCode;
this.fetchListener = fetchListener; this.fetchListener = fetchListener;
} }
@NonNull @NonNull
@Override @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 + "\"}"; "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":""} 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) { for (final CommentModel commentModel : commentModels) {
final CommentModel[] childCommentModels = commentModel.getChildCommentModels(); final List<CommentModel> childCommentModels = commentModel.getChildCommentModels();
if (childCommentModels != null) { if (childCommentModels != null) {
final int childCommentsLen = childCommentModels.length; final int childCommentsLen = childCommentModels.size();
final CommentModel lastChild = childCommentModels.get(childCommentsLen - 1);
final CommentModel lastChild = childCommentModels[childCommentsLen - 1];
if (lastChild != null && lastChild.hasNextPage() && !TextUtils.isEmpty(lastChild.getEndCursor())) { 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); commentModel.setChildCommentModels(remoteChildComments);
lastChild.setPageCursor(false, null); lastChild.setPageCursor(false, null);
} }
} }
} }
return commentModels;
return commentModels.toArray(new CommentModel[0]);
} }
@Override @Override
@ -75,14 +69,13 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, CommentModel[]>
} }
@Override @Override
protected void onPostExecute(final CommentModel[] result) { protected void onPostExecute(final List<CommentModel> result) {
if (fetchListener != null) fetchListener.onResult(result); if (fetchListener != null) fetchListener.onResult(result);
} }
@NonNull @NonNull
private synchronized CommentModel[] getChildComments(final String commentId) { private synchronized List<CommentModel> getChildComments(final String commentId) {
final ArrayList<CommentModel> commentModels = new ArrayList<>(); final List<CommentModel> commentModels = new ArrayList<>();
String endCursor = ""; String endCursor = "";
while (endCursor != null) { while (endCursor != null) {
final String url = "https://www.instagram.com/graphql/query/?query_hash=51fdd02b67508306ad4484ff574a0b62&variables=" + 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; if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) break;
else { else {
final JSONObject data = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("data") 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"); final JSONObject pageInfo = data.getJSONObject("page_info");
endCursor = pageInfo.getString("end_cursor"); endCursor = pageInfo.getString("end_cursor");
@ -110,43 +104,54 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, CommentModel[]>
if (childComment != null) { if (childComment != null) {
final JSONObject owner = childComment.getJSONObject("owner"); final JSONObject owner = childComment.getJSONObject("owner");
final ProfileModel profileModel = new ProfileModel(false, false, false, final ProfileModel profileModel = new ProfileModel(false,
owner.getString(Constants.EXTRAS_ID), false,
owner.getString(Constants.EXTRAS_USERNAME), false,
null, null, null, owner.getString(Constants.EXTRAS_ID),
owner.getString("profile_pic_url"), owner.getString(Constants.EXTRAS_USERNAME),
null, 0, 0, 0, false, false, false, false); null,
null,
null,
owner.getString("profile_pic_url"),
null,
0,
0,
0,
false,
false,
false,
false);
final JSONObject likedBy = childComment.optJSONObject("edge_liked_by"); final JSONObject likedBy = childComment.optJSONObject("edge_liked_by");
commentModels.add(new CommentModel(childComment.getString(Constants.EXTRAS_ID), commentModels.add(new CommentModel(childComment.getString(Constants.EXTRAS_ID),
childComment.getString("text"), childComment.getString("text"),
childComment.getLong("created_at"), childComment.getLong("created_at"),
likedBy != null ? likedBy.optLong("count", 0) : 0, likedBy != null ? likedBy.optLong("count", 0) : 0,
childComment.getBoolean("viewer_has_liked"), childComment.getBoolean("viewer_has_liked"),
profileModel)); profileModel));
} }
} }
} }
} }
conn.disconnect(); conn.disconnect();
} catch (final Exception e) { } catch (final Exception e) {
if (logCollector != null) if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_COMMENTS_FETCHER, "getChildComments", logCollector.appendException(e,
new Pair<>("commentModels.size", commentModels.size())); LogCollector.LogFile.ASYNC_COMMENTS_FETCHER,
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); "getChildComments",
new Pair<>("commentModels.size", commentModels.size()));
if (BuildConfig.DEBUG) Log.e(TAG, "", e);
break; break;
} }
} }
return commentModels.toArray(new CommentModel[0]); return commentModels;
} }
@NonNull @NonNull
private synchronized ArrayList<CommentModel> getParentComments() { private synchronized List<CommentModel> getParentComments() {
final ArrayList<CommentModel> commentModelsList = new ArrayList<>(); final List<CommentModel> commentModels = new ArrayList<>();
String endCursor = ""; String endCursor = "";
while (endCursor != null) { while (endCursor != null) {
final String url = "https://www.instagram.com/graphql/query/?query_hash=bc3296d1ce80a24b1b6e40b1e72903f5&variables=" + 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; if (conn.getResponseCode() != HttpURLConnection.HTTP_OK) break;
else { else {
final JSONObject parentComments = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("data") 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"); final JSONObject pageInfo = parentComments.getJSONObject("page_info");
endCursor = pageInfo.optString("end_cursor"); 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 JSONArray comments = parentComments.getJSONArray("edges");
final int commentsLen = comments.length(); final int commentsLen = comments.length();
final CommentModel[] commentModels = new CommentModel[commentsLen];
for (int i = 0; i < commentsLen; ++i) { for (int i = 0; i < commentsLen; ++i) {
final JSONObject comment = comments.getJSONObject(i).getJSONObject("node"); final JSONObject comment = comments.getJSONObject(i).getJSONObject("node");
final JSONObject owner = comment.getJSONObject("owner"); final JSONObject owner = comment.getJSONObject("owner");
final ProfileModel profileModel = new ProfileModel(false, false, final ProfileModel profileModel = new ProfileModel(false,
owner.optBoolean("is_verified"), false,
owner.getString(Constants.EXTRAS_ID), owner.optBoolean("is_verified"),
owner.getString(Constants.EXTRAS_USERNAME), owner.getString(Constants.EXTRAS_ID),
null, null, null, owner.getString(Constants.EXTRAS_USERNAME),
owner.getString("profile_pic_url"), null,
null, 0, 0, 0, false, false, false, false); null,
null,
owner.getString("profile_pic_url"),
null,
0,
0,
0,
false,
false,
false,
false);
final JSONObject likedBy = comment.optJSONObject("edge_liked_by"); final JSONObject likedBy = comment.optJSONObject("edge_liked_by");
final String commentId = comment.getString(Constants.EXTRAS_ID); final String commentId = comment.getString(Constants.EXTRAS_ID);
commentModels[i] = new CommentModel(commentId, final CommentModel commentModel = new CommentModel(commentId,
comment.getString("text"), comment.getString("text"),
comment.getLong("created_at"), comment.getLong("created_at"),
likedBy != null ? likedBy.optLong("count", 0) : 0, likedBy != null ? likedBy.optLong("count", 0) : 0,
comment.getBoolean("viewer_has_liked"), comment.getBoolean("viewer_has_liked"),
profileModel); profileModel);
JSONObject tempJsonObject; JSONObject tempJsonObject;
final JSONArray childCommentsArray; final JSONArray childCommentsArray;
final int childCommentsLen; final int childCommentsLen;
if ((tempJsonObject = comment.optJSONObject("edge_threaded_comments")) != null && if ((tempJsonObject = comment.optJSONObject("edge_threaded_comments")) != null &&
@ -223,47 +236,53 @@ public final class CommentsFetcher extends AsyncTask<Void, Void, CommentModel[]>
hasNextPage = false; hasNextPage = false;
} }
final CommentModel[] childCommentModels = new CommentModel[childCommentsLen]; final List<CommentModel> childCommentModels = new ArrayList<>();
for (int j = 0; j < childCommentsLen; ++j) { for (int j = 0; j < childCommentsLen; ++j) {
final JSONObject childComment = childCommentsArray.getJSONObject(j).getJSONObject("node"); final JSONObject childComment = childCommentsArray.getJSONObject(j).getJSONObject("node");
tempJsonObject = childComment.getJSONObject("owner"); tempJsonObject = childComment.getJSONObject("owner");
final ProfileModel childProfileModel = new ProfileModel(false, false, final ProfileModel childProfileModel = new ProfileModel(false,
tempJsonObject.optBoolean("is_verified"), false,
tempJsonObject.getString(Constants.EXTRAS_ID), tempJsonObject.optBoolean("is_verified"),
tempJsonObject.getString(Constants.EXTRAS_USERNAME), tempJsonObject.getString(Constants.EXTRAS_ID),
null, null, null, tempJsonObject.getString(Constants.EXTRAS_USERNAME),
tempJsonObject.getString("profile_pic_url"), null,
null, 0, 0, 0, false, false, false, false); null,
null,
tempJsonObject.getString("profile_pic_url"),
null,
0,
0,
0,
false,
false,
false,
false);
tempJsonObject = childComment.optJSONObject("edge_liked_by"); tempJsonObject = childComment.optJSONObject("edge_liked_by");
childCommentModels[j] = new CommentModel(childComment.getString(Constants.EXTRAS_ID), childCommentModels.add(new CommentModel(childComment.getString(Constants.EXTRAS_ID),
childComment.getString("text"), childComment.getString("text"),
childComment.getLong("created_at"), childComment.getLong("created_at"),
tempJsonObject != null ? tempJsonObject.optLong("count", 0) : 0, tempJsonObject != null ? tempJsonObject.optLong("count", 0) : 0,
childComment.getBoolean("viewer_has_liked"), childComment.getBoolean("viewer_has_liked"),
childProfileModel); childProfileModel));
} }
childCommentModels.get(childCommentsLen - 1).setPageCursor(hasNextPage, childEndCursor);
childCommentModels[childCommentsLen - 1].setPageCursor(hasNextPage, childEndCursor); commentModel.setChildCommentModels(childCommentModels);
commentModels[i].setChildCommentModels(childCommentModels);
} }
commentModels.add(commentModel);
} }
Collections.addAll(commentModelsList, commentModels);
} }
conn.disconnect(); conn.disconnect();
} catch (final Exception e) { } catch (final Exception e) {
if (logCollector != null) if (logCollector != null)
logCollector.appendException(e, LogCollector.LogFile.ASYNC_COMMENTS_FETCHER, "getParentComments", 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); if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
break; break;
} }
} }
return commentModels;
return commentModelsList;
} }
} }

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; package awais.instagrabber.asyncs;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Environment;
import android.util.Log; import android.util.Log;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import java.io.File;
import java.net.HttpURLConnection; import java.net.HttpURLConnection;
import java.net.URL; import java.net.URL;
import java.util.ArrayList;
import java.util.List;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostChild;
import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.models.ViewerPostModel;
import awais.instagrabber.models.enums.MediaItemType; import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.NetworkUtils; import awais.instagrabber.utils.NetworkUtils;
import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awaisomereport.LogCollector; 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.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 static final String TAG = "PostFetcher";
private final String shortCode; 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.shortCode = shortCode;
this.fetchListener = fetchListener; this.fetchListener = fetchListener;
} }
@Override @Override
protected ViewerPostModel[] doInBackground(final Void... voids) { protected FeedModel doInBackground(final Void... voids) {
ViewerPostModel[] result = null;
CookieUtils.setupCookies(Utils.settingsHelper.getString(Constants.COOKIE)); // <- direct download CookieUtils.setupCookies(Utils.settingsHelper.getString(Constants.COOKIE)); // <- direct download
HttpURLConnection conn = null;
try { 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.setUseCaches(false);
conn.connect(); 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") final JSONObject media = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("graphql")
.getJSONObject("shortcode_media"); .getJSONObject("shortcode_media");
ProfileModel profileModel = null; ProfileModel profileModel = null;
if (media.has("owner")) { if (media.has("owner")) {
final JSONObject owner = media.getJSONObject("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") 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 long timestamp = media.getLong("taken_at_timestamp");
final boolean isVideo = media.has("is_video") && media.optBoolean("is_video"); 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"); JSONObject commentObject = media.optJSONObject("edge_media_to_parent_comment");
final long commentsCount = commentObject != null ? commentObject.optLong("count") : 0; final long commentsCount = commentObject != null ? commentObject.optLong("count") : 0;
final FeedModel.Builder feedModelBuilder = new FeedModel.Builder()
String endCursor = null; .setItemType(mediaItemType)
if (commentObject != null && (commentObject = commentObject.optJSONObject("page_info")) != null) { .setPostId(media.getString(Constants.EXTRAS_ID))
endCursor = commentObject.optString("end_cursor"); .setDisplayUrl(isVideo ? media.getString("video_url")
} : ResponseBodyUtils.getHighQualityImage(media))
.setThumbnailUrl(media.getString("display_url"))
if (mediaItemType != MediaItemType.MEDIA_TYPE_SLIDER) { .setImageHeight(media.getJSONObject("dimensions").getInt("height"))
final ViewerPostModel postModel = new ViewerPostModel( .setImageWidth(media.getJSONObject("dimensions").getInt("width"))
mediaItemType, .setShortCode(shortCode)
media.getString(Constants.EXTRAS_ID), .setPostCaption(TextUtils.isEmpty(postCaption) ? null : postCaption)
isVideo ? media.getString("video_url") : ResponseBodyUtils.getHighQualityImage(media), .setProfileModel(profileModel)
shortCode, .setViewCount(isVideo && media.has("video_view_count")
TextUtils.isEmpty(postCaption) ? null : postCaption, ? media.getLong("video_view_count")
profileModel, : -1)
isVideo && media.has("video_view_count") ? media.getLong("video_view_count") : -1, .setTimestamp(timestamp)
timestamp, media.getBoolean("viewer_has_liked"), media.getBoolean("viewer_has_saved"), .setLiked(media.getBoolean("viewer_has_liked"))
media.getJSONObject("edge_media_preview_like").getLong("count"), .setBookmarked(media.getBoolean("viewer_has_saved"))
media.isNull("location") ? null : media.getJSONObject("location").optString("name"), .setLikesCount(media.getJSONObject("edge_media_preview_like")
media.isNull("location") ? null : .getLong("count"))
(media.getJSONObject("location").optString("id") + "/" + .setLocationName(media.isNull("location")
media.getJSONObject("location").optString("slug"))); ? null
: media.getJSONObject("location").optString("name"))
postModel.setCommentsCount(commentsCount); .setLocationId(media.isNull("location")
? null
DownloadUtils.checkExistence(downloadDir, customDir, false, postModel); : media.getJSONObject("location").optString("id"))
.setCommentsCount(commentsCount);
result = new ViewerPostModel[]{postModel}; if (isSlider) {
} else {
final JSONArray children = media.getJSONObject("edge_sidecar_to_children").getJSONArray("edges"); final JSONArray children = media.getJSONObject("edge_sidecar_to_children").getJSONArray("edges");
final ViewerPostModel[] postModels = new ViewerPostModel[children.length()]; final List<PostChild> postModels = new ArrayList<>();
for (int i = 0; i < children.length(); ++i) {
for (int i = 0; i < postModels.length; ++i) { final JSONObject childNode = children.getJSONObject(i).getJSONObject("node");
final JSONObject node = children.getJSONObject(i).getJSONObject("node"); final boolean isChildVideo = childNode.getBoolean("is_video");
final boolean isChildVideo = node.getBoolean("is_video"); postModels.add(new PostChild.Builder()
.setItemType(isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO
postModels[i] = new ViewerPostModel( : MediaItemType.MEDIA_TYPE_IMAGE)
isChildVideo ? MediaItemType.MEDIA_TYPE_VIDEO : MediaItemType.MEDIA_TYPE_IMAGE, .setDisplayUrl(isChildVideo ? childNode.getString("video_url")
media.getString(Constants.EXTRAS_ID), : childNode.getString("display_url"))
isChildVideo ? node.getString("video_url") : ResponseBodyUtils.getHighQualityImage(node), .setShortCode(media.getString(Constants.EXTRAS_SHORTCODE))
node.getString(Constants.EXTRAS_SHORTCODE), .setVideoViews(isChildVideo && childNode.has("video_view_count")
postCaption, ? childNode.getLong("video_view_count")
profileModel, : -1)
isChildVideo && node.has("video_view_count") ? node.getLong("video_view_count") : -1, .setThumbnailUrl(childNode.getString("display_url"))
timestamp, media.getBoolean("viewer_has_liked"), media.getBoolean("viewer_has_saved"), .setHeight(childNode.getJSONObject("dimensions").getInt("height"))
media.getJSONObject("edge_media_preview_like").getLong("count"), .setWidth(childNode.getJSONObject("dimensions").getInt("width"))
media.isNull("location") ? null : media.getJSONObject("location").optString("name"), .build());
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]);
} }
feedModelBuilder.setSliderItems(postModels);
postModels[0].setCommentsCount(commentsCount);
result = postModels;
} }
return feedModelBuilder.build();
} }
conn.disconnect();
} catch (Exception e) { } catch (Exception e) {
if (logCollector != null) { if (logCollector != null) {
logCollector.appendException(e, LogCollector.LogFile.ASYNC_POST_FETCHER, "doInBackground"); logCollector.appendException(e, LogCollector.LogFile.ASYNC_POST_FETCHER, "doInBackground");
} }
Log.e(TAG, "Error fetching post", e); Log.e(TAG, "Error fetching post", e);
} finally {
if (conn != null) {
conn.disconnect();
}
} }
return result; return null;
} }
@Override @Override
@ -188,7 +163,7 @@ public final class PostFetcher extends AsyncTask<Void, Void, ViewerPostModel[]>
} }
@Override @Override
protected void onPostExecute(final ViewerPostModel[] postModels) { protected void onPostExecute(final FeedModel feedModel) {
if (fetchListener != null) fetchListener.onResult(postModels); 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.Context;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.BitmapShader;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Paint;
import android.util.AttributeSet; import android.util.AttributeSet;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
@ -17,36 +14,27 @@ import com.facebook.drawee.generic.GenericDraweeHierarchyInflater;
import com.facebook.drawee.generic.RoundingParams; import com.facebook.drawee.generic.RoundingParams;
import com.facebook.drawee.view.SimpleDraweeView; import com.facebook.drawee.view.SimpleDraweeView;
public final class CircularImageView extends SimpleDraweeView { import awais.instagrabber.R;
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;
public class CircularImageView extends SimpleDraweeView {
public CircularImageView(Context context, GenericDraweeHierarchy hierarchy) { public CircularImageView(Context context, GenericDraweeHierarchy hierarchy) {
super(context); super(context);
setHierarchy(hierarchy); setHierarchy(hierarchy);
setup();
} }
public CircularImageView(final Context context) { public CircularImageView(final Context context) {
super(context); super(context);
inflateHierarchy(context, null); inflateHierarchy(context, null);
setup();
} }
public CircularImageView(final Context context, final AttributeSet attrs) { public CircularImageView(final Context context, final AttributeSet attrs) {
super(context, attrs); super(context, attrs);
inflateHierarchy(context, attrs); inflateHierarchy(context, attrs);
setup();
} }
public CircularImageView(final Context context, final AttributeSet attrs, final int defStyleAttr) { public CircularImageView(final Context context, final AttributeSet attrs, final int defStyleAttr) {
super(context, attrs, defStyleAttr); super(context, attrs, defStyleAttr);
inflateHierarchy(context, attrs); inflateHierarchy(context, attrs);
setup();
} }
protected void inflateHierarchy(Context context, @Nullable AttributeSet attrs) { protected void inflateHierarchy(Context context, @Nullable AttributeSet attrs) {
@ -58,77 +46,12 @@ public final class CircularImageView extends SimpleDraweeView {
GenericDraweeHierarchyInflater.updateBuilder(builder, context, attrs); GenericDraweeHierarchyInflater.updateBuilder(builder, context, attrs);
setAspectRatio(builder.getDesiredAspectRatio()); setAspectRatio(builder.getDesiredAspectRatio());
setHierarchy(builder.build()); 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() { public void setStoriesBorder() {
this.color = Color.GREEN; // private final int borderSize = 8;
// invalidate(); final int color = Color.GREEN;
// final RoundingParams roundingParams = RoundingParams.fromCornersRadius(5f);
//
RoundingParams roundingParams = getHierarchy().getRoundingParams(); RoundingParams roundingParams = getHierarchy().getRoundingParams();
if (roundingParams == null) { if (roundingParams == null) {
roundingParams = RoundingParams.asCircle().setRoundingMethod(RoundingParams.RoundingMethod.BITMAP_ONLY); 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 ClickableSpan clickableSpanUnderTouchOnActionDown;
private MentionClickListener mentionClickListener; private MentionClickListener mentionClickListener;
private boolean isUrlHighlighted; private boolean isUrlHighlighted;
private boolean isExpandable;
private boolean isExpanded; private boolean isExpanded;
private OnLongClickListener longClickListener; private OnLongClickListener longClickListener;
@ -59,13 +58,13 @@ public final class RamboTextView extends AppCompatTextView {
this.mentionClickListener = mentionClickListener; this.mentionClickListener = mentionClickListener;
} }
public void setCaptionIsExpandable(final boolean isExpandable) { // public void setCaptionIsExpandable(final boolean isExpandable) {
this.isExpandable = isExpandable; // this.isExpandable = isExpandable;
} // }
public void setCaptionIsExpanded(final boolean isExpanded) { // public void setCaptionIsExpanded(final boolean isExpanded) {
this.isExpanded = isExpanded; // this.isExpanded = isExpanded;
} // }
@Override @Override
public void setOnLongClickListener(@Nullable final OnLongClickListener l) { public void setOnLongClickListener(@Nullable final OnLongClickListener l) {
@ -98,7 +97,7 @@ public final class RamboTextView extends AppCompatTextView {
case MotionEvent.ACTION_DOWN: case MotionEvent.ACTION_DOWN:
final int longPressTimeout = ViewConfiguration.getLongPressTimeout(); final int longPressTimeout = ViewConfiguration.getLongPressTimeout();
handler.postDelayed(longPressRunnable, longPressTimeout); handler.postDelayed(longPressRunnable, longPressTimeout);
if (feedModel != null) feedModel.setMentionClicked(false); // if (feedModel != null) feedModel.setMentionClicked(false);
if (clickableSpanUnderTouch != null) { if (clickableSpanUnderTouch != null) {
highlightUrl(clickableSpanUnderTouch, spanText); highlightUrl(clickableSpanUnderTouch, spanText);
} }
@ -107,19 +106,19 @@ public final class RamboTextView extends AppCompatTextView {
handler.removeCallbacks(longPressRunnable); handler.removeCallbacks(longPressRunnable);
if (touchStartedOverAClickableSpan && clickableSpanUnderTouch == clickableSpanUnderTouchOnActionDown) { if (touchStartedOverAClickableSpan && clickableSpanUnderTouch == clickableSpanUnderTouchOnActionDown) {
dispatchUrlClick(spanText, clickableSpanUnderTouch); dispatchUrlClick(spanText, clickableSpanUnderTouch);
if (feedModel != null) feedModel.setMentionClicked(true); // if (feedModel != null) feedModel.setMentionClicked(true);
} }
cleanupOnTouchUp(spanText); cleanupOnTouchUp(spanText);
return super.onTouchEvent(event); return super.onTouchEvent(event);
case MotionEvent.ACTION_MOVE: case MotionEvent.ACTION_MOVE:
// handler.removeCallbacks(longPressRunnable); // handler.removeCallbacks(longPressRunnable);
if (feedModel != null) feedModel.setMentionClicked(false); // if (feedModel != null) feedModel.setMentionClicked(false);
if (clickableSpanUnderTouch != null) highlightUrl(clickableSpanUnderTouch, spanText); if (clickableSpanUnderTouch != null) highlightUrl(clickableSpanUnderTouch, spanText);
else removeUrlHighlightColor(spanText); else removeUrlHighlightColor(spanText);
return super.onTouchEvent(event); return super.onTouchEvent(event);
case MotionEvent.ACTION_CANCEL: case MotionEvent.ACTION_CANCEL:
handler.removeCallbacks(longPressRunnable); handler.removeCallbacks(longPressRunnable);
if (feedModel != null) feedModel.setMentionClicked(false); // if (feedModel != null) feedModel.setMentionClicked(false);
cleanupOnTouchUp(spanText); cleanupOnTouchUp(spanText);
return super.onTouchEvent(event); 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; RectF b = mTempRect;
b.set(mImageBounds); b.set(mImageBounds);
transform.mapRect(b); transform.mapRect(b);
float offsetLeft = final boolean shouldLimitX = shouldLimit(limitTypes, LIMIT_TRANSLATION_X);
shouldLimit(limitTypes, LIMIT_TRANSLATION_X) float offsetLeft = shouldLimitX
? getOffset( ? getOffset(b.left, b.right, mViewBounds.left, mViewBounds.right, mImageBounds.centerX())
b.left, b.right, mViewBounds.left, mViewBounds.right, mImageBounds.centerX()) : 0;
: 0; float offsetTop = shouldLimit(limitTypes, LIMIT_TRANSLATION_Y)
float offsetTop = ? getOffset(b.top, b.bottom, mViewBounds.top, mViewBounds.bottom, mImageBounds.centerY())
shouldLimit(limitTypes, LIMIT_TRANSLATION_Y) : 0;
? getOffset( if (mListener != null) {
b.top, b.bottom, mViewBounds.top, mViewBounds.bottom, mImageBounds.centerY()) mListener.onTranslationLimited(offsetLeft, offsetTop);
: 0; }
if (offsetLeft != 0 || offsetTop != 0) { if (offsetLeft != 0 || offsetTop != 0) {
transform.postTranslate(offsetLeft, offsetTop); transform.postTranslate(offsetLeft, offsetTop);
return true; 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) { public synchronized void addListener(ZoomableController.Listener listener) {
mListeners.add(listener); mListeners.add(listener);
} }

View File

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

View File

@ -4,7 +4,6 @@ import android.graphics.Rect;
import android.view.View; import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration { public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
@ -16,16 +15,14 @@ public class GridSpacingItemDecoration extends RecyclerView.ItemDecoration {
@Override @Override
public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) { public void getItemOffsets(@NonNull Rect outRect, @NonNull View view, @NonNull RecyclerView parent, @NonNull RecyclerView.State state) {
final RecyclerView.LayoutManager manager = parent.getLayoutManager(); final int halfSpace = spacing / 2;
if (manager instanceof GridLayoutManager) { if (parent.getPaddingLeft() != halfSpace) {
final int spanCount = ((GridLayoutManager) manager).getSpanCount(); parent.setPadding(halfSpace, halfSpace, halfSpace, halfSpace);
final int position = parent.getChildAdapterPosition(view); parent.setClipToPadding(false);
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;
} }
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; package awais.instagrabber.customviews.helpers;
import android.os.Handler;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.recyclerview.widget.GridLayoutManager; import androidx.recyclerview.widget.GridLayoutManager;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.recyclerview.widget.StaggeredGridLayoutManager;
import awais.instagrabber.interfaces.LazyLoadListener; 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 { 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 * The current offset index of data you have loaded
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. 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 LazyLoadListener lazyLoadListener;
private final RecyclerView.LayoutManager layoutManager; 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.layoutManager = layoutManager;
this.lazyLoadListener = lazyLoadListener; this.lazyLoadListener = lazyLoadListener;
if (threshold > 0) { if (threshold > 0) {
@ -26,6 +44,8 @@ public final class RecyclerLazyLoader extends RecyclerView.OnScrollListener {
} }
if (layoutManager instanceof GridLayoutManager) { if (layoutManager instanceof GridLayoutManager) {
this.visibleThreshold = 5 * Math.max(3, ((GridLayoutManager) layoutManager).getSpanCount()); 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) { } else if (layoutManager instanceof LinearLayoutManager) {
this.visibleThreshold = ((LinearLayoutManager) layoutManager).getReverseLayout() ? 4 : 8; this.visibleThreshold = ((LinearLayoutManager) layoutManager).getReverseLayout() ? 4 : 8;
} else { } 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); this(layoutManager, lazyLoadListener, -1);
} }
@ -52,22 +73,37 @@ public final class RecyclerLazyLoader extends RecyclerView.OnScrollListener {
previousTotalItemCount = totalItemCount; previousTotalItemCount = totalItemCount;
} }
final int lastVisibleItemPosition; int lastVisibleItemPosition;
if (layoutManager instanceof GridLayoutManager) { if (layoutManager instanceof GridLayoutManager) {
final GridLayoutManager layoutManager = (GridLayoutManager) this.layoutManager; final GridLayoutManager layoutManager = (GridLayoutManager) this.layoutManager;
lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition(); 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 { } else {
final LinearLayoutManager layoutManager = (LinearLayoutManager) this.layoutManager; final LinearLayoutManager layoutManager = (LinearLayoutManager) this.layoutManager;
lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition(); lastVisibleItemPosition = layoutManager.findLastVisibleItemPosition();
} }
if (!loading && lastVisibleItemPosition + visibleThreshold > totalItemCount) { if (!loading && lastVisibleItemPosition + visibleThreshold > totalItemCount) {
if (lazyLoadListener != null)
lazyLoadListener.onLoadMore(++currentPage, totalItemCount);
loading = true; loading = true;
if (lazyLoadListener != null) {
new Handler().postDelayed(() -> lazyLoadListener.onLoadMore(++currentPage, totalItemCount), 200);
}
} }
} }
public int getCurrentPage() {
return currentPage;
}
public void resetState() { public void resetState() {
this.currentPage = 0; this.currentPage = 0;
this.previousTotalItemCount = 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); final FeedVideoViewHolder videoHolder = getFirstVideoHolder(recyclerView, firstVisibleItemPos, lastVisibleItemPos);
if (videoHolder == null || videoHolder.getCurrentFeedModel() == null) { if (videoHolder == null || videoHolder.getCurrentFeedModel() == null) {
if (currentlyPlayingViewHolder != null) { if (currentlyPlayingViewHolder != null) {
currentlyPlayingViewHolder.stopPlaying(); // currentlyPlayingViewHolder.stopPlaying();
currentlyPlayingViewHolder = null; currentlyPlayingViewHolder = null;
} }
return; return;
@ -80,9 +80,9 @@ public class VideoAwareRecyclerScroller extends RecyclerView.OnScrollListener {
return; return;
} }
if (currentlyPlayingViewHolder != null) { if (currentlyPlayingViewHolder != null) {
currentlyPlayingViewHolder.stopPlaying(); // currentlyPlayingViewHolder.stopPlaying();
} }
videoHolder.startPlaying(); // videoHolder.startPlaying();
currentlyPlayingViewHolder = videoHolder; currentlyPlayingViewHolder = videoHolder;
} }
// boolean processFirstItem = false, processLastItem = false; // boolean processFirstItem = false, processLastItem = false;
@ -196,7 +196,7 @@ public class VideoAwareRecyclerScroller extends RecyclerView.OnScrollListener {
for (int pos = firstVisibleItemPos; pos <= lastVisibleItemPos; pos++) { for (int pos = firstVisibleItemPos; pos <= lastVisibleItemPos; pos++) {
final View view = layoutManager.findViewByPosition(pos); final View view = layoutManager.findViewByPosition(pos);
if (view != null && view.getId() == R.id.videoHolder) { 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) { if (viewSwitcher == null) {
continue; continue;
} }
@ -220,113 +220,113 @@ public class VideoAwareRecyclerScroller extends RecyclerView.OnScrollListener {
if (currentlyPlayingViewHolder == null) { if (currentlyPlayingViewHolder == null) {
return; return;
} }
currentlyPlayingViewHolder.startPlaying(); // currentlyPlayingViewHolder.startPlaying();
} }
public void stopPlaying() { public void stopPlaying() {
if (currentlyPlayingViewHolder == null) { if (currentlyPlayingViewHolder == null) {
return; return;
} }
currentlyPlayingViewHolder.stopPlaying(); // currentlyPlayingViewHolder.stopPlaying();
} }
// private synchronized void attachVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) { // private synchronized void attachVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// synchronized (LOCK) { // synchronized (LOCK) {
// if (recyclerView != null) { // if (recyclerView != null) {
// final RecyclerView.Adapter<?> adapter = recyclerView.getAdapter(); // final RecyclerView.Adapter<?> adapter = recyclerView.getAdapter();
// if (adapter instanceof FeedAdapter) { // if (adapter instanceof FeedAdapter) {
// final SimpleExoPlayer pagerPlayer = ((FeedAdapter) adapter).pagerPlayer; // final SimpleExoPlayer pagerPlayer = ((FeedAdapter) adapter).pagerPlayer;
// if (pagerPlayer != null) pagerPlayer.setPlayWhenReady(false); // if (pagerPlayer != null) pagerPlayer.setPlayWhenReady(false);
// } // }
// } // }
// if (itemView == null) { // if (itemView == null) {
// return; // return;
// } // }
// final boolean shouldAutoplay = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS); // final boolean shouldAutoplay = settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS);
// final FeedModel feedModel = feedModels.get(itemPos); // final FeedModel feedModel = feedModels.get(itemPos);
// // loadVideo(itemPos, itemView, shouldAutoplay, feedModel); // // loadVideo(itemPos, itemView, shouldAutoplay, feedModel);
// } // }
// } // }
// //
// private void loadVideo(final int itemPos, final View itemView, final boolean shouldAutoplay, final FeedModel feedModel) { // private void loadVideo(final int itemPos, final View itemView, final boolean shouldAutoplay, final FeedModel feedModel) {
// final PlayerView playerView = itemView.findViewById(R.id.playerView); // final PlayerView playerView = itemView.findViewById(R.id.playerView);
// if (playerView == null) { // if (playerView == null) {
// return; // return;
// } // }
// if (player != null) { // if (player != null) {
// player.stop(true); // player.stop(true);
// player.release(); // player.release();
// player = null; // player = null;
// } // }
// //
// player = new SimpleExoPlayer.Builder(context) // player = new SimpleExoPlayer.Builder(context)
// .setUseLazyPreparation(!shouldAutoplay) // .setUseLazyPreparation(!shouldAutoplay)
// .build(); // .build();
// player.setPlayWhenReady(shouldAutoplay); // player.setPlayWhenReady(shouldAutoplay);
// //
// final View btnComments = itemView.findViewById(R.id.btnComments); // final View btnComments = itemView.findViewById(R.id.btnComments);
// if (btnComments != null) { // if (btnComments != null) {
// if (feedModel.getCommentsCount() <= 0) btnComments.setEnabled(false); // if (feedModel.getCommentsCount() <= 0) btnComments.setEnabled(false);
// else { // else {
// btnComments.setTag(feedModel); // btnComments.setTag(feedModel);
// btnComments.setEnabled(true); // btnComments.setEnabled(true);
// btnComments.setOnClickListener(commentClickListener); // btnComments.setOnClickListener(commentClickListener);
// } // }
// } // }
// playerView.setPlayer(player); // playerView.setPlayer(player);
// btnMute = itemView.findViewById(R.id.btnMute); // btnMute = itemView.findViewById(R.id.btnMute);
// float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f; // float vol = settingsHelper.getBoolean(Constants.MUTED_VIDEOS) ? 0f : 1f;
// if (vol == 0f && Utils.sessionVolumeFull) vol = 1f; // if (vol == 0f && Utils.sessionVolumeFull) vol = 1f;
// player.setVolume(vol); // player.setVolume(vol);
// //
// if (btnMute != null) { // if (btnMute != null) {
// btnMute.setVisibility(View.VISIBLE); // btnMute.setVisibility(View.VISIBLE);
// btnMute.setImageResource(vol == 0f ? R.drawable.vol : R.drawable.mute); // btnMute.setImageResource(vol == 0f ? R.drawable.vol : R.drawable.mute);
// btnMute.setOnClickListener(muteClickListener); // btnMute.setOnClickListener(muteClickListener);
// } // }
// final DataSource.Factory factory = cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory; // final DataSource.Factory factory = cacheDataSourceFactory != null ? cacheDataSourceFactory : dataSourceFactory;
// final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(factory); // final ProgressiveMediaSource.Factory sourceFactory = new ProgressiveMediaSource.Factory(factory);
// final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(Uri.parse(feedModel.getDisplayUrl())); // final ProgressiveMediaSource mediaSource = sourceFactory.createMediaSource(Uri.parse(feedModel.getDisplayUrl()));
// //
// player.setRepeatMode(Player.REPEAT_MODE_ALL); // player.setRepeatMode(Player.REPEAT_MODE_ALL);
// player.prepare(mediaSource); // player.prepare(mediaSource);
// player.setVolume(vol); // player.setVolume(vol);
// //
// playerView.setOnClickListener(v -> player.setPlayWhenReady(!player.getPlayWhenReady())); // playerView.setOnClickListener(v -> player.setPlayWhenReady(!player.getPlayWhenReady()));
// //
// if (videoChangeCallback != null) videoChangeCallback.playerChanged(itemPos, player); // if (videoChangeCallback != null) videoChangeCallback.playerChanged(itemPos, player);
// } // }
// //
// private void releaseVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) { // private void releaseVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// // Log.d("AWAISKING_APP", "release: " + itemPos); // // Log.d("AWAISKING_APP", "release: " + itemPos);
// // if (player != null) { // // if (player != null) {
// // player.stop(true); // // player.stop(true);
// // player.release(); // // player.release();
// // } // // }
// // player = null; // // player = null;
// } // }
// //
// private void playVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) { // private void playVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// // if (player != null) { // // if (player != null) {
// // final int playbackState = player.getPlaybackState(); // // final int playbackState = player.getPlaybackState();
// // if (!player.isPlaying() // // if (!player.isPlaying()
// // || playbackState == Player.STATE_READY || playbackState == Player.STATE_ENDED // // || playbackState == Player.STATE_READY || playbackState == Player.STATE_ENDED
// // ) { // // ) {
// // player.setPlayWhenReady(true); // // player.setPlayWhenReady(true);
// // } // // }
// // } // // }
// // if (player != null) { // // if (player != null) {
// // player.setPlayWhenReady(true); // // player.setPlayWhenReady(true);
// // player.getPlaybackState(); // // player.getPlaybackState();
// // } // // }
// } // }
// //
// private void stopVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) { // private void stopVideo(final int itemPos, final RecyclerView recyclerView, final View itemView) {
// if (player != null) { // if (player != null) {
// player.setPlayWhenReady(false); // player.setPlayWhenReady(false);
// player.getPlaybackState(); // player.getPlaybackState();
// } // }
// } // }
public interface VideoChangeCallback { public interface VideoChangeCallback {
void playerChanged(final int itemPos, final SimpleExoPlayer player); 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 java.io.File;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.asyncs.DownloadAsync;
import awais.instagrabber.asyncs.ProfilePictureFetcher; import awais.instagrabber.asyncs.ProfilePictureFetcher;
import awais.instagrabber.databinding.DialogProfilepicBinding; import awais.instagrabber.databinding.DialogProfilepicBinding;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
@ -147,54 +146,9 @@ public class ProfilePicDialogFragment extends DialogFragment {
if (context == null) return; if (context == null) return;
if (dir.exists() || dir.mkdirs()) { if (dir.exists() || dir.mkdirs()) {
final File saveFile = new File(dir, name + '_' + System.currentTimeMillis() + ".jpg"); final File saveFile = new File(dir, name + '_' + System.currentTimeMillis() + ".jpg");
new DownloadAsync(context, DownloadUtils.download(context, url, saveFile.getAbsolutePath());
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);
return; return;
} }
Toast.makeText(context, R.string.downloader_error_creating_folder, Toast.LENGTH_SHORT).show(); 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.text.style.RelativeSizeSpan;
import android.util.Log; import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.inputmethod.InputMethodManager; import android.view.inputmethod.InputMethodManager;
import android.widget.LinearLayout;
import android.widget.Toast; import android.widget.Toast;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.LinearLayoutCompat;
import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections; import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.bottomsheet.BottomSheetDialogFragment;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.adapters.CommentsAdapter; import awais.instagrabber.adapters.CommentsAdapter;
import awais.instagrabber.asyncs.CommentsFetcher; import awais.instagrabber.asyncs.CommentsFetcher;
import awais.instagrabber.databinding.FragmentCommentsBinding; import awais.instagrabber.databinding.FragmentCommentsBinding;
import awais.instagrabber.dialogs.ProfilePicDialogFragment; import awais.instagrabber.dialogs.ProfilePicDialogFragment;
import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.CommentModel; import awais.instagrabber.models.CommentModel;
import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.ProfileModel;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.CommentsViewModel;
import awais.instagrabber.webservices.MediaService; import awais.instagrabber.webservices.MediaService;
import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.ServiceCallback;
import static android.content.Context.INPUT_METHOD_SERVICE; 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 static final String TAG = "CommentsViewerFragment";
private final String cookie = Utils.settingsHelper.getString(Constants.COOKIE); private final String cookie = Utils.settingsHelper.getString(Constants.COOKIE);
private CommentsAdapter commentsAdapter; private CommentsAdapter commentsAdapter;
private CommentModel commentModel;
private FragmentCommentsBinding binding; private FragmentCommentsBinding binding;
private String shortCode; private String shortCode;
private String userId; private String userId;
private Resources resources; private Resources resources;
private InputMethodManager imm; private InputMethodManager imm;
private AppCompatActivity fragmentActivity; private AppCompatActivity fragmentActivity;
private LinearLayout root; private LinearLayoutCompat root;
private boolean shouldRefresh = true; private boolean shouldRefresh = true;
private MediaService mediaService; private MediaService mediaService;
private String postId; 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 @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
fragmentActivity = (AppCompatActivity) getActivity(); fragmentActivity = (AppCompatActivity) getActivity();
mediaService = MediaService.getInstance(); mediaService = MediaService.getInstance();
setHasOptionsMenu(true); // setHasOptionsMenu(true);
} }
@NonNull @NonNull
@ -85,6 +145,8 @@ public final class CommentsViewerFragment extends Fragment implements SwipeRefre
return root; return root;
} }
binding = FragmentCommentsBinding.inflate(getLayoutInflater()); binding = FragmentCommentsBinding.inflate(getLayoutInflater());
binding.swipeRefreshLayout.setEnabled(false);
binding.swipeRefreshLayout.setNestedScrollingEnabled(false);
root = binding.getRoot(); root = binding.getRoot();
return root; return root;
} }
@ -96,34 +158,33 @@ public final class CommentsViewerFragment extends Fragment implements SwipeRefre
shouldRefresh = false; shouldRefresh = false;
} }
@Override // @Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { // public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.follow, menu); // inflater.inflate(R.menu.follow, menu);
menu.findItem(R.id.action_compare).setVisible(false); // menu.findItem(R.id.action_compare).setVisible(false);
final MenuItem menuSearch = menu.findItem(R.id.action_search); // final MenuItem menuSearch = menu.findItem(R.id.action_search);
final SearchView searchView = (SearchView) menuSearch.getActionView(); // final SearchView searchView = (SearchView) menuSearch.getActionView();
searchView.setQueryHint(getResources().getString(R.string.action_search)); // searchView.setQueryHint(getResources().getString(R.string.action_search));
searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { // searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() {
@Override // @Override
public boolean onQueryTextSubmit(final String query) { // public boolean onQueryTextSubmit(final String query) {
return false; // return false;
} // }
//
@Override // @Override
public boolean onQueryTextChange(final String query) { // public boolean onQueryTextChange(final String query) {
if (commentsAdapter != null) commentsAdapter.getFilter().filter(query); // // if (commentsAdapter != null) commentsAdapter.getFilter().filter(query);
return true; // return true;
} // }
}); // });
} // }
@Override @Override
public void onRefresh() { public void onRefresh() {
binding.swipeRefreshLayout.setRefreshing(true); binding.swipeRefreshLayout.setRefreshing(true);
new CommentsFetcher(shortCode, commentModels -> { new CommentsFetcher(shortCode, commentModels -> {
commentsViewModel.getList().postValue(commentModels);
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
commentsAdapter = new CommentsAdapter(commentModels, true, clickListener, mentionClickListener);
binding.rvComments.setAdapter(commentsAdapter);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
@ -133,9 +194,14 @@ public final class CommentsViewerFragment extends Fragment implements SwipeRefre
shortCode = fragmentArgs.getShortCode(); shortCode = fragmentArgs.getShortCode();
postId = fragmentArgs.getPostId(); postId = fragmentArgs.getPostId();
userId = fragmentArgs.getPostUserId(); userId = fragmentArgs.getPostUserId();
setTitle(); // setTitle();
binding.swipeRefreshLayout.setOnRefreshListener(this); binding.swipeRefreshLayout.setOnRefreshListener(this);
binding.swipeRefreshLayout.setRefreshing(true); 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(); resources = getResources();
if (!TextUtils.isEmpty(cookie)) { if (!TextUtils.isEmpty(cookie)) {
binding.commentField.setStartIconVisible(false); binding.commentField.setStartIconVisible(false);
@ -155,121 +221,107 @@ public final class CommentsViewerFragment extends Fragment implements SwipeRefre
public void afterTextChanged(final Editable s) {} public void afterTextChanged(final Editable s) {}
}); });
binding.commentField.setStartIconOnClickListener(v -> { binding.commentField.setStartIconOnClickListener(v -> {
if (commentModel != null) { commentsAdapter.clearSelection();
final View focus = binding.rvComments.findViewWithTag(commentModel);
focus.setBackgroundColor(0x00000000);
commentModel = null;
}
binding.commentText.setText(""); binding.commentText.setText("");
}); });
binding.commentField.setEndIconOnClickListener(newCommentListener); binding.commentField.setEndIconOnClickListener(newCommentListener);
} }
new CommentsFetcher(this.shortCode, commentModels -> { onRefresh();
commentsAdapter = new CommentsAdapter(commentModels, true, clickListener, mentionClickListener);
binding.rvComments.setAdapter(commentsAdapter);
binding.swipeRefreshLayout.setRefreshing(false);
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
private void setTitle() { // private void setTitle() {
final ActionBar actionBar = fragmentActivity.getSupportActionBar(); // final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar == null) return; // if (actionBar == null) return;
actionBar.setTitle(R.string.title_comments); // actionBar.setTitle(R.string.title_comments);
// actionBar.setSubtitle(shortCode); // 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(); final Context context = getContext();
if (context == null) return; if (context == null) return;
if (commentModel == null) { final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); final ProfileModel profileModel = commentModel.getProfileModel();
return; final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
} switch (which) {
final ProfileModel profileModel = commentModel.getProfileModel(); case 0: // open profile
switch (which) { openProfile("@" + profileModel.getUsername());
case 0: // open profile break;
openProfile(profileModel.getUsername()); case 1: // view profile pic
break; final FragmentManager fragmentManager = getParentFragmentManager();
case 1: // view profile pic final ProfilePicDialogFragment fragment = new ProfilePicDialogFragment(profileModel.getId(),
final FragmentManager fragmentManager = getParentFragmentManager(); profileModel.getUsername(),
final ProfilePicDialogFragment fragment = new ProfilePicDialogFragment(profileModel.getId(), profileModel.getHdProfilePic());
profileModel.getUsername(), final FragmentTransaction ft = fragmentManager.beginTransaction();
profileModel.getHdProfilePic()); ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
final FragmentTransaction ft = fragmentManager.beginTransaction(); .add(fragment, "profilePicDialog")
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) .commit();
.add(fragment, "profilePicDialog") break;
.commit(); case 2: // copy username
break; Utils.copyText(context, profileModel.getUsername());
case 2: // copy username break;
Utils.copyText(context, profileModel.getUsername()); // case 3: // copy comment
break; // Utils.copyText(context, commentModel.getText().toString());
case 3: // copy comment // break;
Utils.copyText(context, commentModel.getText().toString()); case 3: // reply to comment
break; // final View focus = binding.rvComments.findViewWithTag(commentModel);
case 4: // reply to comment // focus.setBackgroundColor(0x80888888);
final View focus = binding.rvComments.findViewWithTag(commentModel); commentsAdapter.setSelected(commentModel);
focus.setBackgroundColor(0x80888888); String mention = "@" + profileModel.getUsername() + " ";
String mention = "@" + profileModel.getUsername() + " "; binding.commentText.setText(mention);
binding.commentText.setText(mention); binding.commentText.requestFocus();
binding.commentText.requestFocus(); binding.commentText.setSelection(mention.length());
binding.commentText.setSelection(mention.length()); binding.commentText.postDelayed(() -> {
binding.commentText.postDelayed(new Runnable() {
@Override
public void run() {
imm = (InputMethodManager) context.getSystemService(INPUT_METHOD_SERVICE); imm = (InputMethodManager) context.getSystemService(INPUT_METHOD_SERVICE);
if (imm == null) return; if (imm == null) return;
imm.showSoftInput(binding.commentText, 0); imm.showSoftInput(binding.commentText, 0);
}, 200);
break;
case 4: // like/unlike comment
if (csrfToken == null) {
return;
} }
}, 200); if (!commentModel.getLiked()) {
break; mediaService.commentLike(commentModel.getId(), csrfToken, new ServiceCallback<Boolean>() {
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>() {
@Override @Override
public void onSuccess(final Boolean result) { public void onSuccess(final Boolean result) {
commentModel = null;
if (!result) { if (!result) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return; return;
@ -279,112 +331,62 @@ public final class CommentsViewerFragment extends Fragment implements SwipeRefre
@Override @Override
public void onFailure(final Throwable t) { 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(); 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 -> { @Override
final Object tag = v.getTag(); public void onFailure(final Throwable t) {
if (tag instanceof CommentModel) { Log.e(TAG, "Error unliking comment", t);
commentModel = (CommentModel) tag; 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(); @Override
final SpannableString title = new SpannableString(username + ":\n" + commentModel.getText()); public void onFailure(final Throwable t) {
title.setSpan(new RelativeSizeSpan(1.23f), 0, username.length(), Spanned.SPAN_INCLUSIVE_INCLUSIVE); Log.e(TAG, "Error deleting comment", t);
Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show();
String[] commentDialogList; }
});
final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); break;
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; new AlertDialog.Builder(context)
new AlertDialog.Builder(context) .setTitle(title)
.setTitle(title) .setItems(commentDialogList, profileDialogListener)
.setItems(commentDialogList, profileDialogListener) .setNegativeButton(R.string.cancel, null)
.setNegativeButton(R.string.cancel, null) .show();
.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();
}
});
};
private void openProfile(final String username) { private void openProfile(final String username) {
final NavDirections action = CommentsViewerFragmentDirections.actionGlobalProfileFragment("@" + username); final NavDirections action = CommentsViewerFragmentDirections.actionGlobalProfileFragment(username);
NavHostFragment.findNavController(this).navigate(action); NavHostFragment.findNavController(this).navigate(action);
} }
} }

View File

@ -1,7 +1,7 @@
package awais.instagrabber.fragments; package awais.instagrabber.fragments;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.pm.PackageManager;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
@ -12,6 +12,8 @@ import android.text.style.StyleSpan;
import android.util.Log; import android.util.Log;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -22,81 +24,79 @@ import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.core.content.PermissionChecker;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.navigation.NavController;
import androidx.navigation.NavDirections; import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.snackbar.BaseTransientBottomBar; import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar; 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.Date;
import java.util.List; import java.util.List;
import java.util.Set;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity; import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.PostsAdapter; import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.asyncs.HashtagFetcher; import awais.instagrabber.asyncs.HashtagFetcher;
import awais.instagrabber.asyncs.PostsFetcher; import awais.instagrabber.asyncs.HashtagPostFetchService;
import awais.instagrabber.customviews.PrimaryActionModeCallback; 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.NestedCoordinatorLayout;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentHashtagBinding; 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.HashtagModel;
import awais.instagrabber.models.PostModel; import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DataBox; import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.PostsViewModel;
import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService; import awais.instagrabber.webservices.StoriesService;
import awais.instagrabber.webservices.TagsService; import awais.instagrabber.webservices.TagsService;
import awaisomereport.LogCollector; 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.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;
public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "HashTagFragment"; 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 MainActivity fragmentActivity;
private FragmentHashtagBinding binding; private FragmentHashtagBinding binding;
private NestedCoordinatorLayout root; private NestedCoordinatorLayout root;
private boolean shouldRefresh = true, hasStories = false; private boolean shouldRefresh = true;
private boolean hasStories = false;
private String hashtag; private String hashtag;
private HashtagModel hashtagModel; private HashtagModel hashtagModel;
private PostsViewModel postsViewModel;
private PostsAdapter postsAdapter;
private ActionMode actionMode; private ActionMode actionMode;
private StoriesService storiesService; private StoriesService storiesService;
private boolean hasNextPage;
private String endCursor;
private AsyncTask<?, ?, ?> currentlyExecuting; private AsyncTask<?, ?, ?> currentlyExecuting;
private boolean isLoggedIn; private boolean isLoggedIn;
private TagsService tagsService; 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) { private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override @Override
public void handleOnBackPressed() { public void handleOnBackPressed() {
setEnabled(false); binding.posts.endSelection();
remove();
if (postsAdapter == null) return;
postsAdapter.clearSelection();
} }
}; };
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback( private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
@ -104,53 +104,146 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
new PrimaryActionModeCallback.CallbacksHelper() { new PrimaryActionModeCallback.CallbacksHelper() {
@Override @Override
public void onDestroy(final ActionMode mode) { public void onDestroy(final ActionMode mode) {
onBackPressedCallback.handleOnBackPressed(); binding.posts.endSelection();
} }
@Override @Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) { public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
if (item.getItemId() == R.id.action_download) { if (item.getItemId() == R.id.action_download) {
if (postsAdapter == null || hashtag == null) { if (HashTagFragment.this.selectedFeedModels == null) return false;
return false;
}
final Context context = getContext(); final Context context = getContext();
if (context == null) return false; if (context == null) return false;
DownloadUtils.batchDownload(context, if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
hashtag, DownloadUtils.download(context, ImmutableList.copyOf(HashTagFragment.this.selectedFeedModels));
DownloadMethod.DOWNLOAD_MAIN, binding.posts.endSelection();
postsAdapter.getSelectedModels()); return true;
checkAndResetAction(); }
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
return true; return true;
} }
return false; return false;
} }
}); });
private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() { private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() {
@Override @Override
public void onResult(final List<PostModel> result) { public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) {
binding.swipeRefreshLayout.setRefreshing(false); openPostDialog(feedModel, profilePicView, mainPostImage, -1);
if (result == null) return; }
binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE));
final List<PostModel> postModels = postsViewModel.getList().getValue(); @Override
List<PostModel> finalList = postModels == null || postModels.isEmpty() public void onSliderClick(final FeedModel feedModel, final int position) {
? new ArrayList<>() openPostDialog(feedModel, null, null, position);
: new ArrayList<>(postModels); }
if (isPullToRefresh) {
finalList = result; @Override
isPullToRefresh = false; public void onCommentsClick(final FeedModel feedModel) {
} else { final NavDirections commentsAction = HashTagFragmentDirections.actionGlobalCommentsViewerFragment(
finalList.addAll(result); 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); downloadFeedModel = feedModel;
postsViewModel.getList().postValue(finalList); downloadChildPosition = childPosition;
PostModel model = null; requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
if (!result.isEmpty()) { }
model = result.get(result.size() - 1);
@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(); fragmentActivity = (MainActivity) requireActivity();
tagsService = TagsService.getInstance(); tagsService = TagsService.getInstance();
storiesService = StoriesService.getInstance(); storiesService = StoriesService.getInstance();
setHasOptionsMenu(true);
} }
@Nullable @Nullable
@ -184,9 +278,8 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override @Override
public void onRefresh() { public void onRefresh() {
isPullToRefresh = true; binding.posts.refresh();
endCursor = null; fetchStories();
fetchHashtagModel();
} }
@Override @Override
@ -196,10 +289,35 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
@Override @Override
public void onDestroy() { public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
super.onDestroy(); inflater.inflate(R.menu.topic_posts_menu, menu);
if (postsViewModel != null) { }
postsViewModel.getList().postValue(Collections.emptyList());
@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; isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null;
final HashTagFragmentArgs fragmentArgs = HashTagFragmentArgs.fromBundle(getArguments()); final HashTagFragmentArgs fragmentArgs = HashTagFragmentArgs.fromBundle(getArguments());
hashtag = fragmentArgs.getHashtag(); hashtag = fragmentArgs.getHashtag();
setTitle();
setupPosts();
fetchHashtagModel(); 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() { private void fetchHashtagModel() {
stopCurrentExecutor(); stopCurrentExecutor();
binding.swipeRefreshLayout.setRefreshing(true); binding.swipeRefreshLayout.setRefreshing(true);
currentlyExecuting = new HashtagFetcher(hashtag.substring(1), result -> { currentlyExecuting = new HashtagFetcher(hashtag.substring(1), result -> {
final Context context = getContext();
if (context == null) return;
hashtagModel = result; hashtagModel = result;
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
final Context context = getContext();
if (context == null) return;
if (hashtagModel == null) { if (hashtagModel == null) {
Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_SHORT).show();
return; return;
} }
fetchPosts(); setTitle();
setHashtagDetails();
setupPosts();
fetchStories();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
private void fetchPosts() { private void setupPosts() {
stopCurrentExecutor(); 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); 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 private void setHashtagDetails() {
public void onFailure(final Throwable t) { if (isLoggedIn) {
Log.e(TAG, "Error", t);
}
});
binding.btnFollowTag.setVisibility(View.VISIBLE); binding.btnFollowTag.setVisibility(View.VISIBLE);
binding.btnFollowTag.setText(hashtagModel.getFollowing() ? R.string.unfollow : R.string.follow); binding.btnFollowTag.setText(hashtagModel.getFollowing() ? R.string.unfollow : R.string.follow);
binding.btnFollowTag.setChipIconResource(hashtagModel.getFollowing() binding.btnFollowTag.setChipIconResource(hashtagModel.getFollowing()
@ -324,9 +371,35 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
binding.btnFollowTag.setOnClickListener(v -> { binding.btnFollowTag.setOnClickListener(v -> {
final String cookie = settingsHelper.getString(Constants.COOKIE); final String cookie = settingsHelper.getString(Constants.COOKIE);
final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie);
binding.btnFollowTag.setClickable(false); if (csrfToken != null) {
if (!hashtagModel.getFollowing()) { binding.btnFollowTag.setClickable(false);
tagsService.follow(hashtag.substring(1), csrfToken, new ServiceCallback<Boolean>() { 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 @Override
public void onSuccess(final Boolean result) { public void onSuccess(final Boolean result) {
binding.btnFollowTag.setClickable(true); binding.btnFollowTag.setClickable(true);
@ -349,31 +422,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
.show(); .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 { } else {
binding.btnFollowTag.setVisibility(View.GONE); binding.btnFollowTag.setVisibility(View.GONE);
@ -420,16 +469,43 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
binding.mainTagPostCount.setText(span); binding.mainTagPostCount.setText(span);
binding.mainTagPostCount.setVisibility(View.VISIBLE); binding.mainTagPostCount.setVisibility(View.VISIBLE);
binding.mainHashtagImage.setOnClickListener(v -> { binding.mainHashtagImage.setOnClickListener(v -> {
if (hasStories) { if (!hasStories) return;
// show stories // show stories
final NavDirections action = HashTagFragmentDirections final NavDirections action = HashTagFragmentDirections
.actionHashtagFragmentToStoryViewerFragment(-1, null, true, false, hashtagModel.getName(), hashtagModel.getName()); .actionHashtagFragmentToStoryViewerFragment(-1, null, true, false, hashtagModel.getName(), hashtagModel.getName());
NavHostFragment.findNavController(this).navigate(action); NavHostFragment.findNavController(this).navigate(action);
return;
}
}); });
} }
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() { public void stopCurrentExecutor() {
if (currentlyExecuting != null) { if (currentlyExecuting != null) {
try { try {
@ -445,24 +521,28 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
private void setTitle() { private void setTitle() {
final ActionBar actionBar = fragmentActivity.getSupportActionBar(); final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar != null) { if (actionBar != null) {
Log.d(TAG, "setting title: " + hashtag); // Log.d(TAG, "setting title: " + hashtag);
final Handler handler = new Handler(); actionBar.setTitle(hashtag);
handler.postDelayed(() -> actionBar.setTitle(hashtag), 200); // final Handler handler = new Handler();
// handler.postDelayed(() -> , 1000);
} }
} }
private boolean checkAndResetAction() { private void updateSwipeRefreshState() {
if (!onBackPressedCallback.isEnabled() && actionMode == null) { binding.swipeRefreshLayout.setRefreshing(binding.posts.isFetching() || storiesFetching);
return false; }
}
if (onBackPressedCallback.isEnabled()) { private void navigateToProfile(final String username) {
onBackPressedCallback.setEnabled(false); final NavController navController = NavHostFragment.findNavController(this);
onBackPressedCallback.remove(); final Bundle bundle = new Bundle();
} bundle.putString("username", username);
if (actionMode != null) { navController.navigate(R.id.action_global_profileFragment, bundle);
actionMode.finish(); }
actionMode = null;
} private void showPostsLayoutPreferences() {
return true; 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.Context;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.style.RelativeSizeSpan; import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import android.util.Log; import android.util.Log;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
@ -23,134 +27,221 @@ import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.core.content.PermissionChecker;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.navigation.NavController;
import androidx.navigation.NavDirections; import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.google.android.material.snackbar.BaseTransientBottomBar; import com.google.android.material.snackbar.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar; 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.Date;
import java.util.List; import java.util.List;
import java.util.Set;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity; import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.PostsAdapter; import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.asyncs.LocationFetcher; import awais.instagrabber.asyncs.LocationFetcher;
import awais.instagrabber.asyncs.PostsFetcher; import awais.instagrabber.asyncs.LocationPostFetchService;
import awais.instagrabber.customviews.PrimaryActionModeCallback; 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.NestedCoordinatorLayout;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentLocationBinding; 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.LocationModel;
import awais.instagrabber.models.PostModel; import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DataBox; import awais.instagrabber.utils.DataBox;
import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.PostsViewModel;
import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService; import awais.instagrabber.webservices.StoriesService;
import awaisomereport.LogCollector; 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.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;
public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "LocationFragment"; 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 MainActivity fragmentActivity;
private FragmentLocationBinding binding; private FragmentLocationBinding binding;
private NestedCoordinatorLayout root; private NestedCoordinatorLayout root;
private boolean shouldRefresh = true, hasStories = false; private boolean shouldRefresh = true;
private boolean hasStories = false;
private String locationId; private String locationId;
private LocationModel locationModel; private LocationModel locationModel;
private PostsViewModel postsViewModel;
private PostsAdapter postsAdapter;
private ActionMode actionMode; private ActionMode actionMode;
private StoriesService storiesService; private StoriesService storiesService;
private boolean hasNextPage;
private String endCursor;
private AsyncTask<?, ?, ?> currentlyExecuting; private AsyncTask<?, ?, ?> currentlyExecuting;
private boolean isLoggedIn; 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) { private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override @Override
public void handleOnBackPressed() { public void handleOnBackPressed() {
if (postsAdapter == null) { binding.posts.endSelection();
setEnabled(false);
remove();
return;
}
postsAdapter.clearSelection();
setEnabled(false);
remove();
} }
}; };
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback( private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
R.menu.multi_select_download_menu, new PrimaryActionModeCallback.CallbacksHelper() { R.menu.multi_select_download_menu, new PrimaryActionModeCallback.CallbacksHelper() {
@Override @Override
public void onDestroy(final ActionMode mode) { public void onDestroy(final ActionMode mode) {
onBackPressedCallback.handleOnBackPressed(); binding.posts.endSelection();
} }
@Override @Override
public boolean onActionItemClicked(final ActionMode mode, public boolean onActionItemClicked(final ActionMode mode,
final MenuItem item) { final MenuItem item) {
if (item.getItemId() == R.id.action_download) { if (item.getItemId() == R.id.action_download) {
if (postsAdapter == null || locationId == null) { if (LocationFragment.this.selectedFeedModels == null) return false;
return false;
}
final Context context = getContext(); final Context context = getContext();
if (context == null) return false; if (context == null) return false;
DownloadUtils.batchDownload(context, if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
locationId, DownloadUtils.download(context, ImmutableList.copyOf(LocationFragment.this.selectedFeedModels));
DownloadMethod.DOWNLOAD_MAIN, binding.posts.endSelection();
postsAdapter.getSelectedModels()); return true;
checkAndResetAction(); }
return true; requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
} }
return false; return false;
} }
}); });
private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() { private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() {
@Override @Override
public void onResult(final List<PostModel> result) { public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) {
binding.swipeRefreshLayout.setRefreshing(false); openPostDialog(feedModel, profilePicView, mainPostImage, -1);
if (result == null) return; }
binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE));
final List<PostModel> postModels = postsViewModel.getList().getValue(); @Override
List<PostModel> finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() public void onSliderClick(final FeedModel feedModel, final int position) {
: new ArrayList<>(postModels); openPostDialog(feedModel, null, null, position);
if (isPullToRefresh) { }
finalList = result;
isPullToRefresh = false; @Override
} else { public void onCommentsClick(final FeedModel feedModel) {
finalList.addAll(result); 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); downloadFeedModel = feedModel;
PostModel model = null; downloadChildPosition = childPosition;
if (!result.isEmpty()) { requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
model = result.get(result.size() - 1); }
@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); super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity(); fragmentActivity = (MainActivity) requireActivity();
storiesService = StoriesService.getInstance(); storiesService = StoriesService.getInstance();
setHasOptionsMenu(true);
} }
@Nullable @Nullable
@ -185,9 +277,8 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
@Override @Override
public void onRefresh() { public void onRefresh() {
isPullToRefresh = true; binding.posts.refresh();
endCursor = null; fetchStories();
fetchLocationModel();
} }
@Override @Override
@ -197,10 +288,35 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
} }
@Override @Override
public void onDestroy() { public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
super.onDestroy(); inflater.inflate(R.menu.topic_posts_menu, menu);
if (postsViewModel != null) { }
postsViewModel.getList().postValue(Collections.emptyList());
@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.favChip.setVisibility(View.GONE);
binding.btnMap.setVisibility(View.GONE); binding.btnMap.setVisibility(View.GONE);
setTitle(); setTitle();
setupPosts();
fetchLocationModel(); fetchLocationModel();
} }
private void setupPosts() { private void setupPosts() {
postsViewModel = new ViewModelProvider(this).get(PostsViewModel.class); binding.posts.setViewModelStoreOwner(this)
final Context context = getContext(); .setLifeCycleOwner(this)
if (context == null) return; .setPostFetchService(new LocationPostFetchService(locationModel))
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(context, Utils.convertDpToPx(110)); .setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_LOCATION_POSTS_LAYOUT)))
binding.mainPosts.setLayoutManager(layoutManager); .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
binding.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4))); .setFeedItemCallback(feedItemCallback)
postsAdapter = new PostsAdapter((postModel, position) -> { .setSelectionModeCallback(selectionModeCallback)
if (postsAdapter.isSelecting()) { .init();
if (actionMode == null) return; binding.swipeRefreshLayout.setRefreshing(true);
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);
} }
private void fetchLocationModel() { private void fetchLocationModel() {
@ -289,34 +358,15 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
} }
setTitle(); setTitle();
setupLocationDetails(); setupLocationDetails();
fetchPosts(); setupPosts();
fetchStories();
// fetchPosts();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
private void setupLocationDetails() { private void setupLocationDetails() {
final String locationId = locationModel.getId(); final String locationId = locationModel.getId();
binding.swipeRefreshLayout.setRefreshing(true); // 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.mainLocationImage.setImageURI(locationModel.getSdProfilePic()); binding.mainLocationImage.setImageURI(locationModel.getSdProfilePic());
final String postCount = String.valueOf(locationModel.getPostCount()); final String postCount = String.valueOf(locationModel.getPostCount());
final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline, 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.mainLocPostCount.setVisibility(View.VISIBLE);
binding.locationFullName.setText(locationModel.getName()); binding.locationFullName.setText(locationModel.getName());
CharSequence biography = locationModel.getBio(); CharSequence biography = locationModel.getBio();
binding.locationBiography.setCaptionIsExpandable(true); // binding.locationBiography.setCaptionIsExpandable(true);
binding.locationBiography.setCaptionIsExpanded(true); // binding.locationBiography.setCaptionIsExpanded(true);
if (TextUtils.isEmpty(biography)) { if (TextUtils.isEmpty(biography)) {
binding.locationBiography.setVisibility(View.GONE); binding.locationBiography.setVisibility(View.GONE);
@ -405,18 +455,38 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
final NavDirections action = LocationFragmentDirections final NavDirections action = LocationFragmentDirections
.actionLocationFragmentToStoryViewerFragment(-1, null, false, true, locationId, locationModel.getName()); .actionLocationFragmentToStoryViewerFragment(-1, null, false, true, locationId, locationModel.getName());
NavHostFragment.findNavController(this).navigate(action); NavHostFragment.findNavController(this).navigate(action);
return;
} }
}); });
} }
private void fetchPosts() { private void fetchStories() {
stopCurrentExecutor(); if (isLoggedIn) {
currentlyExecuting = new PostsFetcher(locationModel.getId(), PostItemType.LOCATION, endCursor, postsFetchListener) storiesFetching = true;
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 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) { if (currentlyExecuting != null) {
try { try {
currentlyExecuting.cancel(true); currentlyExecuting.cancel(true);
@ -435,18 +505,21 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR
} }
} }
private boolean checkAndResetAction() { private void updateSwipeRefreshState() {
if (!onBackPressedCallback.isEnabled() && actionMode == null) { binding.swipeRefreshLayout.setRefreshing(binding.posts.isFetching() || storiesFetching);
return false; }
}
if (onBackPressedCallback.isEnabled()) { private void navigateToProfile(final String username) {
onBackPressedCallback.setEnabled(false); final NavController navController = NavHostFragment.findNavController(this);
onBackPressedCallback.remove(); final Bundle bundle = new Bundle();
} bundle.putString("username", username);
if (actionMode != null) { navController.navigate(R.id.action_global_profileFragment, bundle);
actionMode.finish(); }
actionMode = null;
} private void showPostsLayoutPreferences() {
return true; 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;
import awais.instagrabber.adapters.NotificationsAdapter.OnNotificationClickListener; import awais.instagrabber.adapters.NotificationsAdapter.OnNotificationClickListener;
import awais.instagrabber.asyncs.NotificationsFetcher; import awais.instagrabber.asyncs.NotificationsFetcher;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.databinding.FragmentNotificationsViewerBinding; import awais.instagrabber.databinding.FragmentNotificationsViewerBinding;
import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections; import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections;
import awais.instagrabber.interfaces.MentionClickListener; import awais.instagrabber.interfaces.MentionClickListener;
@ -75,7 +76,8 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
} else { } else {
commentDialogList = new String[]{getString(R.string.open_profile)}; 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) -> { final DialogInterface.OnClickListener profileDialogListener = (dialog, which) -> {
switch (which) { switch (which) {
case 0: case 0:
@ -101,9 +103,18 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
}); });
return; return;
} }
final NavDirections action = MorePreferencesFragmentDirections final AlertDialog alertDialog = new AlertDialog.Builder(context)
.actionGlobalPostViewFragment(0, new String[]{model.getShortCode()}, false); .setCancelable(false)
NavHostFragment.findNavController(this).navigate(action); .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; break;
case 2: case 2:
friendshipService.ignore(userId, model.getUserId(), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() { friendshipService.ignore(userId, model.getUserId(), csrfToken, new ServiceCallback<FriendshipRepoChangeRootResponse>() {
@ -125,7 +136,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
break; break;
} }
}; };
new AlertDialog.Builder(getContext()) new AlertDialog.Builder(context)
.setTitle(title) .setTitle(title)
.setItems(commentDialogList, profileDialogListener) .setItems(commentDialogList, profileDialogListener)
.setNegativeButton(R.string.cancel, null) .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.Context;
import android.content.pm.PackageManager; import android.content.pm.PackageManager;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.os.Handler;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.LinearLayout;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback; import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher; import androidx.activity.OnBackPressedDispatcher;
@ -19,64 +18,55 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.core.content.PermissionChecker;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.Observer; import androidx.navigation.NavController;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections; import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.ArrayList; import com.google.common.collect.ImmutableList;
import java.util.Collections;
import java.util.List; import java.util.Set;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.adapters.PostsAdapter; import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.asyncs.PostsFetcher; import awais.instagrabber.asyncs.SavedPostFetchService;
import awais.instagrabber.asyncs.i.iLikedFetcher;
import awais.instagrabber.customviews.PrimaryActionModeCallback; 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.databinding.FragmentSavedBinding;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.fragments.main.ProfileFragmentDirections; import awais.instagrabber.fragments.main.ProfileFragmentDirections;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.PostModel; import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; 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 { public final class SavedViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static AsyncTask<?, ?, ?> currentlyExecuting; private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private PostsAdapter postsAdapter; private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030;
private boolean hasNextPage;
private FragmentSavedBinding binding; private FragmentSavedBinding binding;
private String username; private String username;
private String endCursor;
private RecyclerLazyLoader lazyLoader;
private ArrayList<PostModel> selectedItems = new ArrayList<>();
private ActionMode actionMode; private ActionMode actionMode;
private PostsViewModel postsViewModel; private SwipeRefreshLayout root;
private LinearLayout root;
private AppCompatActivity fragmentActivity; private AppCompatActivity fragmentActivity;
private boolean shouldRefresh = true; private boolean shouldRefresh = true;
private PostItemType type; private PostItemType type;
private String profileId; private String profileId;
private Set<FeedModel> selectedFeedModels;
private FeedModel downloadFeedModel;
private int downloadChildPosition = -1;
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override @Override
public void handleOnBackPressed() { public void handleOnBackPressed() {
setEnabled(false); binding.posts.endSelection();
remove();
if (postsAdapter == null) return;
postsAdapter.clearSelection();
} }
}; };
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback( private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
@ -84,70 +74,153 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
new PrimaryActionModeCallback.CallbacksHelper() { new PrimaryActionModeCallback.CallbacksHelper() {
@Override @Override
public void onDestroy(final ActionMode mode) { public void onDestroy(final ActionMode mode) {
onBackPressedCallback.handleOnBackPressed(); binding.posts.endSelection();
} }
@Override @Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) { public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
if (item.getItemId() == R.id.action_download) { if (item.getItemId() == R.id.action_download) {
if (postsAdapter == null || username == null) { if (SavedViewerFragment.this.selectedFeedModels == null) return false;
return false;
}
final Context context = getContext(); final Context context = getContext();
if (context == null) return false; if (context == null) return false;
DownloadUtils.batchDownload(context, if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
username, DownloadUtils.download(context, ImmutableList.copyOf(SavedViewerFragment.this.selectedFeedModels));
DownloadMethod.DOWNLOAD_SAVED, binding.posts.endSelection();
postsAdapter.getSelectedModels()); return true;
checkAndResetAction(); }
return true; requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
} }
return false; return false;
} }
}); });
private final FetchListener<List<PostModel>> postsFetchListener = new FetchListener<List<PostModel>>() { private final FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() {
@Override @Override
public void onResult(final List<PostModel> result) { public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) {
final List<PostModel> current = postsViewModel.getList().getValue(); openPostDialog(feedModel, profilePicView, mainPostImage, -1);
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);
});
final PostModel model = !result.isEmpty() ? result.get(result.size() - 1) : null; @Override
if (model != null) { public void onSliderClick(final FeedModel feedModel, final int position) {
endCursor = model.getEndCursor(); openPostDialog(feedModel, null, null, position);
hasNextPage = model.hasNextPage(); }
if (hasNextPage) {
fetchPosts(); @Override
} else { public void onCommentsClick(final FeedModel feedModel) {
binding.swipeRefreshLayout.setRefreshing(false); final NavDirections commentsAction = ProfileFragmentDirections.actionGlobalCommentsViewerFragment(
} feedModel.getShortCode(),
model.setPageCursor(false, null); feedModel.getPostId(),
} feedModel.getProfileModel().getId()
} else if (current == null) { );
final Context context = getContext(); NavHostFragment.findNavController(SavedViewerFragment.this).navigate(commentsAction);
if (context == null) return; }
Toast.makeText(context, R.string.empty_list, Toast.LENGTH_SHORT).show();
NavHostFragment.findNavController(SavedViewerFragment.this).popBackStack(); @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 @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
fragmentActivity = (AppCompatActivity) getActivity(); fragmentActivity = (AppCompatActivity) getActivity();
setHasOptionsMenu(true);
} }
@Override @Override
@ -164,22 +237,34 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
@Override @Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return; if (!shouldRefresh) return;
binding.swipeRefreshLayout.setOnRefreshListener(this);
init(); 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 @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
setTitle(); setTitle();
observeData();
} }
private void observeData() { @Override
postsViewModel = new ViewModelProvider(this).get(PostsViewModel.class); public void onRefresh() {
postsViewModel.getList().removeObserver(listObserver); binding.posts.refresh();
if (postsAdapter != null) {
postsViewModel.getList().observe(getViewLifecycleOwner(), listObserver);
}
} }
private void init() { private void init() {
@ -189,122 +274,89 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
username = fragmentArgs.getUsername(); username = fragmentArgs.getUsername();
profileId = fragmentArgs.getProfileId(); profileId = fragmentArgs.getProfileId();
type = fragmentArgs.getType(); type = fragmentArgs.getType();
setTitle(); setupPosts();
binding.swipeRefreshLayout.setOnRefreshListener(this); // postsAdapter = new PostsAdapter((postModel, position) -> {
// autoloadPosts = Utils.settingsHelper.getBoolean(AUTOLOAD_POSTS); // if (postsAdapter.isSelecting()) {
binding.mainPosts.setNestedScrollingEnabled(false); // if (actionMode == null) return;
final Context context = getContext(); // final String title = getString(R.string.number_selected, postsAdapter.getSelectedModels().size());
if (context == null) return; // actionMode.setTitle(title);
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(context, Utils.convertDpToPx(110)); // return;
binding.mainPosts.setLayoutManager(layoutManager); // }
binding.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4))); // if (checkAndResetAction()) return;
postsAdapter = new PostsAdapter((postModel, position) -> { // final List<PostModel> postModels = postsViewModel.getList().getValue();
if (postsAdapter.isSelecting()) { // if (postModels == null || postModels.size() == 0) return;
if (actionMode == null) return; // if (postModels.get(0) == null) return;
final String title = getString(R.string.number_selected, postsAdapter.getSelectedModels().size()); // final String postId = postModels.get(0).getPostId();
actionMode.setTitle(title); // final boolean isId = postId != null;
return; // final String[] idsOrShortCodes = new String[postModels.size()];
} // for (int i = 0; i < postModels.size(); i++) {
if (checkAndResetAction()) return; // final PostModel tempPostModel = postModels.get(i);
final List<PostModel> postModels = postsViewModel.getList().getValue(); // final String tempId = tempPostModel.getPostId();
if (postModels == null || postModels.size() == 0) return; // final String finalPostId = type == PostItemType.LIKED ? tempId.substring(0, tempId.indexOf("_")) : tempId;
if (postModels.get(0) == null) return; // idsOrShortCodes[i] = isId ? finalPostId
final String postId = postModels.get(0).getPostId(); // : tempPostModel.getShortCode();
final boolean isId = postId != null; // }
final String[] idsOrShortCodes = new String[postModels.size()]; // final NavDirections action = ProfileFragmentDirections.actionGlobalPostViewFragment(
for (int i = 0; i < postModels.size(); i++) { // position,
final PostModel tempPostModel = postModels.get(i); // idsOrShortCodes,
final String tempId = tempPostModel.getPostId(); // isId);
final String finalPostId = type == PostItemType.LIKED ? tempId.substring(0, tempId.indexOf("_")) : tempId; // NavHostFragment.findNavController(this).navigate(action);
idsOrShortCodes[i] = isId ? finalPostId // }, (model, position) -> {
: tempPostModel.getShortCode(); // if (!postsAdapter.isSelecting()) {
} // checkAndResetAction();
final NavDirections action = ProfileFragmentDirections.actionGlobalPostViewFragment( // return true;
position, // }
idsOrShortCodes, // final OnBackPressedDispatcher onBackPressedDispatcher = fragmentActivity.getOnBackPressedDispatcher();
isId); // if (onBackPressedCallback.isEnabled()) return true;
NavHostFragment.findNavController(this).navigate(action); // actionMode = fragmentActivity.startActionMode(multiSelectAction);
}, (model, position) -> { // final String title = getString(R.string.number_selected, 1);
if (!postsAdapter.isSelecting()) { // actionMode.setTitle(title);
checkAndResetAction(); // onBackPressedDispatcher.addCallback(getViewLifecycleOwner(), onBackPressedCallback);
return true; // 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();
} }
private void fetchPosts() { private void setupPosts() {
stopCurrentExecutor(); binding.posts.setViewModelStoreOwner(this)
final AsyncTask<Void, Void, List<PostModel>> asyncTask; .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) { switch (type) {
case LIKED: case LIKED:
asyncTask = new iLikedFetcher(endCursor, postsFetchListener); return Constants.PREF_LIKED_POSTS_LAYOUT;
break;
case SAVED:
case TAGGED: case TAGGED:
if (TextUtils.isEmpty(profileId)) return; return Constants.PREF_TAGGED_POSTS_LAYOUT;
asyncTask = new PostsFetcher(profileId, type, endCursor, postsFetchListener); case SAVED:
break;
default: 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 @Override
public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults); super.onRequestPermissionsResult(requestCode, permissions, grantResults);
if (requestCode == 8020 && grantResults[0] == PackageManager.PERMISSION_GRANTED && selectedItems.size() > 0) { final boolean granted = grantResults[0] == PackageManager.PERMISSION_GRANTED;
final Context context = getContext(); final Context context = getContext();
if (context == null) return; if (context == null) return;
DownloadUtils.batchDownload(context, null, DownloadMethod.DOWNLOAD_SAVED, selectedItems); 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));
public static void stopCurrentExecutor() { binding.posts.endSelection();
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);
}
} }
} }
@ -313,9 +365,6 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
if (actionBar == null) return; if (actionBar == null) return;
final int titleRes; final int titleRes;
switch (type) { switch (type) {
case SAVED:
titleRes = R.string.saved;
break;
case LIKED: case LIKED:
titleRes = R.string.liked; titleRes = R.string.liked;
break; break;
@ -323,24 +372,29 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL
titleRes = R.string.tagged; titleRes = R.string.tagged;
break; break;
default: default:
return; // no other types supported in this view case SAVED:
titleRes = R.string.saved;
break;
} }
actionBar.setTitle(titleRes); actionBar.setTitle(titleRes);
actionBar.setSubtitle(username); actionBar.setSubtitle(username);
} }
private boolean checkAndResetAction() { private void updateSwipeRefreshState() {
if (!onBackPressedCallback.isEnabled() && actionMode == null) { binding.swipeRefreshLayout.setRefreshing(binding.posts.isFetching());
return false; }
}
if (onBackPressedCallback.isEnabled()) { private void navigateToProfile(final String username) {
onBackPressedCallback.setEnabled(false); final NavController navController = NavHostFragment.findNavController(this);
onBackPressedCallback.remove(); final Bundle bundle = new Bundle();
} bundle.putString("username", username);
if (actionMode != null) { navController.navigate(R.id.action_global_profileFragment, bundle);
actionMode.finish(); }
actionMode = null;
} private void showPostsLayoutPreferences() {
return true; 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.content.pm.PackageManager;
import android.graphics.drawable.Animatable; import android.graphics.drawable.Animatable;
import android.net.Uri; import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Environment;
import android.os.Handler; import android.os.Handler;
import android.util.Log; import android.util.Log;
import android.util.Pair; import android.util.Pair;
@ -45,14 +43,16 @@ import com.facebook.drawee.interfaces.DraweeController;
import com.facebook.imagepipeline.image.ImageInfo; import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder; import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.SimpleExoPlayer; 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.MediaSource;
import com.google.android.exoplayer2.source.MediaSourceEventListener; import com.google.android.exoplayer2.source.MediaSourceEventListener;
import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.source.ProgressiveMediaSource;
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
import java.io.File;
import java.io.IOException; import java.io.IOException;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
import java.util.Collections; import java.util.Collections;
@ -62,7 +62,7 @@ import java.util.List;
import awais.instagrabber.BuildConfig; import awais.instagrabber.BuildConfig;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.adapters.StoriesAdapter; import awais.instagrabber.adapters.StoriesAdapter;
import awais.instagrabber.asyncs.DownloadAsync; import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.asyncs.QuizAction; import awais.instagrabber.asyncs.QuizAction;
import awais.instagrabber.asyncs.RespondAction; import awais.instagrabber.asyncs.RespondAction;
import awais.instagrabber.asyncs.SeenAction; 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.QuestionModel;
import awais.instagrabber.models.stickers.QuizModel; import awais.instagrabber.models.stickers.QuizModel;
import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; 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_THRESHOLD;
import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_VELOCITY_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.Constants.MARK_AS_SEEN;
import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;
@ -316,15 +313,7 @@ public class StoryViewerFragment extends Fragment {
final Object feedStoryModel = isRightSwipe final Object feedStoryModel = isRightSwipe
? finalModels.get(index - 1) ? finalModels.get(index - 1)
: finalModels.size() == index + 1 ? null : finalModels.get(index + 1); : finalModels.size() == index + 1 ? null : finalModels.get(index + 1);
if (feedStoryModel != null) { paginateStories(feedStoryModel, context, isRightSwipe, currentFeedStoryIndex == finalModels.size() - 2);
if (fetching) {
Toast.makeText(context, R.string.be_patient, Toast.LENGTH_SHORT).show();
return;
}
fetching = true;
currentFeedStoryIndex = isRightSwipe ? (index - 1) : (index + 1);
resetView();
}
return; return;
} }
if (isRightSwipe) { if (isRightSwipe) {
@ -359,6 +348,15 @@ public class StoryViewerFragment extends Fragment {
return false; 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.imageViewer.setTapListener(simpleOnGestureListener);
binding.spotify.setOnClickListener(v -> { binding.spotify.setOnClickListener(v -> {
final Object tag = v.getTag(); final Object tag = v.getTag();
@ -371,14 +369,19 @@ public class StoryViewerFragment extends Fragment {
binding.viewStoryPost.setOnClickListener(v -> { binding.viewStoryPost.setOnClickListener(v -> {
final Object tag = v.getTag(); final Object tag = v.getTag();
if (!(tag instanceof CharSequence)) return; if (!(tag instanceof CharSequence)) return;
final String postId = tag.toString(); final String shortCode = tag.toString();
final boolean isId = tag.toString().matches("^[\\d]+$"); final AlertDialog alertDialog = new AlertDialog.Builder(context)
final String[] idsOrShortCodes = new String[]{postId}; .setCancelable(false)
final NavDirections action = HashTagFragmentDirections.actionGlobalPostViewFragment( .setView(R.layout.dialog_opening_post)
0, .create();
idsOrShortCodes, alertDialog.show();
isId); new PostFetcher(shortCode, feedModel -> {
NavHostFragment.findNavController(this).navigate(action); final PostViewV2Fragment fragment = PostViewV2Fragment
.builder(feedModel)
.build();
fragment.setOnShowListener(dialog -> alertDialog.dismiss());
fragment.show(getChildFragmentManager(), "post_view");
}).execute();
}); });
final View.OnClickListener storyActionListener = v -> { final View.OnClickListener storyActionListener = v -> {
final Object tag = v.getTag(); final Object tag = v.getTag();
@ -509,7 +512,7 @@ public class StoryViewerFragment extends Fragment {
} }
storiesViewModel.getList().setValue(Collections.emptyList()); storiesViewModel.getList().setValue(Collections.emptyList());
if (currentStoryMediaId == null) return; if (currentStoryMediaId == null) return;
final ServiceCallback storyCallback = new ServiceCallback<List<StoryModel>>() { final ServiceCallback<List<StoryModel>> storyCallback = new ServiceCallback<List<StoryModel>>() {
@Override @Override
public void onSuccess(final List<StoryModel> storyModels) { public void onSuccess(final List<StoryModel> storyModels) {
fetching = false; fetching = false;
@ -604,40 +607,13 @@ public class StoryViewerFragment extends Fragment {
} }
private void downloadStory() { private void downloadStory() {
int error = 0;
final Context context = getContext(); final Context context = getContext();
if (context == null) return; if (context == null) return;
if (currentStory != null) { 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)
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
return;
}
DownloadUtils.download(context, currentStory);
} }
private void setupImage() { private void setupImage() {
@ -687,8 +663,10 @@ public class StoryViewerFragment extends Fragment {
binding.playerView.setPlayer(player); binding.playerView.setPlayer(player);
player.setPlayWhenReady(settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS)); 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")) final ProgressiveMediaSource mediaSource = new ProgressiveMediaSource.Factory(new DefaultDataSourceFactory(context, "instagram"))
.createMediaSource(Uri.parse(url)); .createMediaSource(mediaItem);
mediaSource.addEventListener(new Handler(), new MediaSourceEventListener() { mediaSource.addEventListener(new Handler(), new MediaSourceEventListener() {
@Override @Override
public void onLoadCompleted(final int windowIndex, public void onLoadCompleted(final int windowIndex,
@ -732,7 +710,8 @@ public class StoryViewerFragment extends Fragment {
binding.progressView.setVisibility(View.GONE); binding.progressView.setVisibility(View.GONE);
} }
}); });
player.prepare(mediaSource); player.setMediaSource(mediaSource);
player.prepare();
binding.playerView.setOnClickListener(v -> { binding.playerView.setOnClickListener(v -> {
if (player != null) { if (player != null) {
@ -751,12 +730,10 @@ public class StoryViewerFragment extends Fragment {
if (t == '@') { if (t == '@') {
final NavDirections action = HashTagFragmentDirections.actionGlobalProfileFragment(username); final NavDirections action = HashTagFragmentDirections.actionGlobalProfileFragment(username);
NavHostFragment.findNavController(this).navigate(action); NavHostFragment.findNavController(this).navigate(action);
} } else if (t == '#') {
else if (t == '#') {
final NavDirections action = HashTagFragmentDirections.actionGlobalHashTagFragment(username.substring(1)); final NavDirections action = HashTagFragmentDirections.actionGlobalHashTagFragment(username.substring(1));
NavHostFragment.findNavController(this).navigate(action); NavHostFragment.findNavController(this).navigate(action);
} } else {
else {
final NavDirections action = ProfileFragmentDirections.actionGlobalLocationFragment(username.split(" \\(")[1].replace(")", "")); final NavDirections action = ProfileFragmentDirections.actionGlobalLocationFragment(username.split(" \\(")[1].replace(")", ""));
NavHostFragment.findNavController(this).navigate(action); NavHostFragment.findNavController(this).navigate(action);
} }
@ -768,4 +745,18 @@ public class StoryViewerFragment extends Fragment {
try { player.release(); } catch (Exception ignored) { } try { player.release(); } catch (Exception ignored) { }
player = null; 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.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.net.Uri; import android.net.Uri;
@ -26,6 +27,8 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.core.content.ContextCompat;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModel;
@ -55,10 +58,12 @@ import awais.instagrabber.ProfileNavGraphDirections;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.adapters.DirectMessageItemsAdapter; import awais.instagrabber.adapters.DirectMessageItemsAdapter;
import awais.instagrabber.asyncs.ImageUploader; import awais.instagrabber.asyncs.ImageUploader;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.asyncs.direct_messages.DirectMessageInboxThreadFetcher; import awais.instagrabber.asyncs.direct_messages.DirectMessageInboxThreadFetcher;
import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster; import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding; import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding;
import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.MentionClickListener; import awais.instagrabber.interfaces.MentionClickListener;
import awais.instagrabber.models.ImageUploadOptions; import awais.instagrabber.models.ImageUploadOptions;
@ -78,6 +83,7 @@ import awais.instagrabber.utils.Utils;
public class DirectMessageThreadFragment extends Fragment { public class DirectMessageThreadFragment extends Fragment {
private static final String TAG = "DirectMessagesThreadFmt"; private static final String TAG = "DirectMessagesThreadFmt";
private static final int PICK_IMAGE = 100; private static final int PICK_IMAGE = 100;
private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private AppCompatActivity fragmentActivity; private AppCompatActivity fragmentActivity;
private String threadId; private String threadId;
@ -169,8 +175,9 @@ public class DirectMessageThreadFragment extends Fragment {
binding.swipeRefreshLayout.setRefreshing(false); binding.swipeRefreshLayout.setRefreshing(false);
} }
}; };
private LinearLayout root; private ConstraintLayout root;
private boolean shouldRefresh = true; private boolean shouldRefresh = true;
private DirectItemModel.DirectItemMediaModel downloadMediaItem;
@Override @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
@ -234,16 +241,14 @@ public class DirectMessageThreadFragment extends Fragment {
switch (itemType) { switch (itemType) {
case MEDIA_SHARE: case MEDIA_SHARE:
case CLIP: case CLIP:
final long postId = directItemModel.getMediaModel().getPk(); case FELIX_SHARE:
final boolean isId = true; final String shortCode = directItemModel.getMediaModel().getCode();
final NavController navController = NavHostFragment.findNavController(this); new PostFetcher(shortCode, feedModel -> {
final NavDirections action = DirectMessageThreadFragmentDirections final PostViewV2Fragment fragment = PostViewV2Fragment
.actionGlobalPostViewFragment( .builder(feedModel)
0, .build();
new String[]{String.valueOf(postId)}, fragment.show(getChildFragmentManager(), "post_view");
isId }).execute();
);
navController.navigate(action);
break; break;
case LINK: case LINK:
Intent linkIntent = new Intent(Intent.ACTION_VIEW); Intent linkIntent = new Intent(Intent.ACTION_VIEW);
@ -258,18 +263,7 @@ public class DirectMessageThreadFragment extends Fragment {
break; break;
case RAVEN_MEDIA: case RAVEN_MEDIA:
case MEDIA: case MEDIA:
final ProfileModel user = getUser(directItemModel.getUserId()); downloadItem(context);
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();
}
break; break;
case STORY_SHARE: case STORY_SHARE:
if (directItemModel.getReelShare() != null) { if (directItemModel.getReelShare() != null) {
@ -302,11 +296,13 @@ public class DirectMessageThreadFragment extends Fragment {
} else if (which == 1) { } else if (which == 1) {
sendText(null, directItemModel.getItemId(), directItemModel.isLiked()); sendText(null, directItemModel.getItemId(), directItemModel.isLiked());
} else if (which == 2) { } else if (which == 2) {
if (directItemModel == null) if (directItemModel == null) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); 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()); new ThreadAction().execute("delete", directItemModel.getItemId());
else searchUsername(getUser(directItemModel.getUserId()).getUsername()); } else {
searchUsername(getUser(directItemModel.getUserId()).getUsername());
}
} }
}; };
final View.OnClickListener onClickListener = v -> { final View.OnClickListener onClickListener = v -> {
@ -322,6 +318,7 @@ public class DirectMessageThreadFragment extends Fragment {
switch (itemType) { switch (itemType) {
case MEDIA_SHARE: case MEDIA_SHARE:
case CLIP: case CLIP:
case FELIX_SHARE:
firstOption = R.string.view_post; firstOption = R.string.view_post;
break; break;
case LINK: 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 @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
@ -428,6 +445,17 @@ public class DirectMessageThreadFragment extends Fragment {
if (listViewModel != null) listViewModel.getList().postValue(Collections.emptyList()); 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) { private void sendText(final String text, final String itemId, final boolean delete) {
DirectThreadBroadcaster.TextBroadcastOptions textOptions = null; DirectThreadBroadcaster.TextBroadcastOptions textOptions = null;
DirectThreadBroadcaster.ReactionBroadcastOptions reactionOptions = null; DirectThreadBroadcaster.ReactionBroadcastOptions reactionOptions = null;

View File

@ -1,126 +1,84 @@
package awais.instagrabber.fragments.main; package awais.instagrabber.fragments.main;
import android.content.Context;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log;
import android.view.ActionMode; import android.view.ActionMode;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.Toast;
import androidx.activity.OnBackPressedCallback; import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavDirections; import androidx.navigation.fragment.FragmentNavigator;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity; import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.DiscoverAdapter; import awais.instagrabber.adapters.DiscoverTopicsAdapter;
import awais.instagrabber.asyncs.DiscoverFetcher;
import awais.instagrabber.asyncs.i.iTopicFetcher;
import awais.instagrabber.customviews.PrimaryActionModeCallback; import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.helpers.GridAutofitLayoutManager;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration; import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentDiscoverBinding; 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.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 { public class DiscoverFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "DiscoverFragment";
private MainActivity fragmentActivity; private MainActivity fragmentActivity;
private CoordinatorLayout root; private CoordinatorLayout root;
private FragmentDiscoverBinding binding; 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 ActionMode actionMode;
private DiscoverItemViewModel discoverItemViewModel; private TopicClusterViewModel topicClusterViewModel;
private boolean shouldRefresh = true; private boolean shouldRefresh = true;
private boolean isPullToRefresh; private DiscoverService discoverService;
private final FetchListener<DiscoverTopicModel> topicFetchListener = new FetchListener<DiscoverTopicModel>() { // private final FetchListener<DiscoverItemModel[]> postsFetchListener = new FetchListener<DiscoverItemModel[]>() {
@Override // @Override
public void doBefore() {} // public void doBefore() {}
//
@Override // @Override
public void onResult(final DiscoverTopicModel result) { // public void onResult(final DiscoverItemModel[] result) {
if (result != null) { // if (result == null || result.length <= 0) {
topicIds = result.getIds(); // binding.swipeRefreshLayout.setRefreshing(false);
rankToken = result.getToken(); // final Context context = getContext();
final Context context = getContext(); // if (context == null) return;
if (context == null) return; // Toast.makeText(context, R.string.discover_empty, Toast.LENGTH_SHORT).show();
final ArrayAdapter<String> spinnerArrayAdapter = new ArrayAdapter<>( // return;
context, // }
android.R.layout.simple_spinner_dropdown_item, // List<DiscoverItemModel> current = discoverItemViewModel.getList().getValue();
result.getNames() // final List<DiscoverItemModel> resultList = Arrays.asList(result);
); // current = current == null ? new ArrayList<>() : new ArrayList<>(current); // copy to modifiable list
binding.discoverType.setAdapter(spinnerArrayAdapter); // if (isPullToRefresh) {
} // current = resultList;
} // isPullToRefresh = false;
}; // } else {
private final FetchListener<DiscoverItemModel[]> postsFetchListener = new FetchListener<DiscoverItemModel[]>() { // current.addAll(resultList);
@Override // }
public void doBefore() {} // discoverItemViewModel.getList().postValue(current);
// binding.swipeRefreshLayout.setRefreshing(false);
@Override // final DiscoverItemModel discoverItemModel = result[result.length - 1];
public void onResult(final DiscoverItemModel[] result) { // if (discoverItemModel != null) {
if (result == null || result.length <= 0) { // discoverEndMaxId = discoverItemModel.getNextMaxId();
binding.discoverSwipeRefreshLayout.setRefreshing(false); // discoverHasMore = discoverItemModel.hasMore();
final Context context = getContext(); // discoverItemModel.setMore(false, null);
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 OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override @Override
public void handleOnBackPressed() { public void handleOnBackPressed() {
setEnabled(false); setEnabled(false);
remove(); remove();
if (discoverAdapter == null) return; // if (discoverAdapter == null) return;
discoverAdapter.clearSelection(); // discoverAdapter.clearSelection();
} }
}; };
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback( private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
@ -134,14 +92,14 @@ public class DiscoverFragment extends Fragment implements SwipeRefreshLayout.OnR
@Override @Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) { public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
if (item.getItemId() == R.id.action_download) { if (item.getItemId() == R.id.action_download) {
if (discoverAdapter == null) return false; // if (discoverAdapter == null) return false;
final Context context = getContext(); // final Context context = getContext();
if (context == null) return false; // if (context == null) return false;
DownloadUtils.batchDownload(context, // DownloadUtils.batchDownload(context,
null, // null,
DownloadMethod.DOWNLOAD_DISCOVER, // DownloadMethod.DOWNLOAD_DISCOVER,
discoverAdapter.getSelectedModels()); // discoverAdapter.getSelectedModels());
checkAndResetAction(); // checkAndResetAction();
return true; return true;
} }
return false; return false;
@ -152,6 +110,7 @@ public class DiscoverFragment extends Fragment implements SwipeRefreshLayout.OnR
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity(); fragmentActivity = (MainActivity) requireActivity();
discoverService = DiscoverService.getInstance();
} }
@Override @Override
@ -170,96 +129,159 @@ public class DiscoverFragment extends Fragment implements SwipeRefreshLayout.OnR
@Override @Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
if (!shouldRefresh) return; if (!shouldRefresh) return;
binding.discoverSwipeRefreshLayout.setOnRefreshListener(this); binding.swipeRefreshLayout.setOnRefreshListener(this);
setupExplore(); init();
shouldRefresh = false; 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 @Override
public void onRefresh() { 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(); fetchTopics();
} }
private void fetchTopics() { public void setupTopics() {
new iTopicFetcher(topicFetchListener).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); 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() { private void setupExplore() {
binding.discoverSwipeRefreshLayout.setRefreshing(true); // discoverItemViewModel = new ViewModelProvider(fragmentActivity).get(DiscoverItemViewModel.class);
new DiscoverFetcher(currentTopic, discoverEndMaxId, rankToken, postsFetchListener, false) // final Context context = getContext();
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); // 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() { private boolean checkAndResetAction() {
if (!onBackPressedCallback.isEnabled() && actionMode == null) { if (!onBackPressedCallback.isEnabled() && actionMode == null) {
return false; return false;

View File

@ -1,295 +1,245 @@
package awais.instagrabber.fragments.main; package awais.instagrabber.fragments.main;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler;
import android.util.Log; import android.util.Log;
import android.view.ActionMode;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import androidx.activity.OnBackPressedCallback;
import androidx.activity.OnBackPressedDispatcher;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.PermissionChecker;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections; import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
import com.facebook.common.executors.UiThreadImmediateExecutorService; import com.google.common.collect.ImmutableList;
import com.facebook.datasource.BaseDataSubscriber;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.imagepipeline.request.ImageRequest;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Set;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity; import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.FeedAdapter; import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.adapters.FeedStoriesAdapter; import awais.instagrabber.adapters.FeedStoriesAdapter;
import awais.instagrabber.adapters.viewholder.feed.FeedItemViewHolder; import awais.instagrabber.asyncs.FeedPostFetchService;
import awais.instagrabber.asyncs.FeedFetcher; import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.RamboTextView;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.customviews.helpers.VideoAwareRecyclerScroller;
import awais.instagrabber.databinding.FragmentFeedBinding; import awais.instagrabber.databinding.FragmentFeedBinding;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.interfaces.MentionClickListener; import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.models.BasePostModel;
import awais.instagrabber.models.FeedModel; import awais.instagrabber.models.FeedModel;
import awais.instagrabber.models.FeedStoryModel; import awais.instagrabber.models.FeedStoryModel;
import awais.instagrabber.models.PostModel; import awais.instagrabber.models.PostsLayoutPreferences;
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.utils.Constants; import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.NumberUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.FeedStoriesViewModel; import awais.instagrabber.viewmodels.FeedStoriesViewModel;
import awais.instagrabber.viewmodels.FeedViewModel;
import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService; 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; import static awais.instagrabber.utils.Utils.settingsHelper;
public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "FeedFragment"; private static final String TAG = "FeedFragment";
private static final double MAX_VIDEO_HEIGHT = 0.9 * Utils.displayMetrics.heightPixels; private static final int STORAGE_PERM_REQUEST_CODE = 8020;
private static final int RESIZED_VIDEO_HEIGHT = (int) (0.8 * Utils.displayMetrics.heightPixels); private static final int STORAGE_PERM_REQUEST_CODE_FOR_SELECTION = 8030;
private MainActivity fragmentActivity; private MainActivity fragmentActivity;
private CoordinatorLayout root; private CoordinatorLayout root;
private FragmentFeedBinding binding; private FragmentFeedBinding binding;
private StoriesService storiesService; private StoriesService storiesService;
private boolean feedHasNextPage = false;
private String feedEndCursor = null;
private FeedViewModel feedViewModel;
private VideoAwareRecyclerScroller videoAwareRecyclerScroller;
private boolean shouldRefresh = true; 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 FeedAdapterV2.FeedItemCallback feedItemCallback = new FeedAdapterV2.FeedItemCallback() {
private final FetchListener<FeedModel[]> feedFetchListener = new FetchListener<FeedModel[]>() {
@Override @Override
public void doBefore() { public void onPostClick(final FeedModel feedModel, final View profilePicView, final View mainPostImage) {
binding.feedSwipeRefreshLayout.post(() -> binding.feedSwipeRefreshLayout.setRefreshing(true)); openPostDialog(feedModel, profilePicView, mainPostImage, -1);
} }
@Override @Override
public void onResult(final FeedModel[] result) { public void onSliderClick(final FeedModel feedModel, final int position) {
if (result == null || result.length <= 0) { openPostDialog(feedModel, null, null, position);
binding.feedSwipeRefreshLayout.setRefreshing(false); }
@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; return;
} }
final List<FeedModel> currentFeedModelList = feedViewModel.getList().getValue(); downloadFeedModel = feedModel;
final Map<String, FeedModel> thumbToFeedMap = new HashMap<>(); downloadChildPosition = childPosition;
for (final FeedModel feedModel : result) { requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE);
thumbToFeedMap.put(feedModel.getThumbnailUrl(), feedModel); }
}
final BaseDataSubscriber<Void> subscriber = new BaseDataSubscriber<Void>() {
int success = 0;
int failed = 0;
@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 @Override
protected void onNewResultImpl(@NonNull final DataSource<Void> dataSource) { public void onDestroy(final ActionMode mode) {
final Map<String, Object> extras = dataSource.getExtras(); binding.feedRecyclerView.endSelection();
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();
} }
@Override @Override
protected void onFailureImpl(@NonNull final DataSource<Void> dataSource) { public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
failed++; if (item.getItemId() == R.id.action_download) {
updateAdapter(); if (FeedFragment.this.selectedFeedModels == null) return false;
} final Context context = getContext();
if (context == null) return false;
public void updateAdapter() { if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
if (failed + success != result.length) return; DownloadUtils.download(context, ImmutableList.copyOf(FeedFragment.this.selectedFeedModels));
List<FeedModel> finalList = currentFeedModelList == null || currentFeedModelList.isEmpty() binding.feedRecyclerView.endSelection();
? new ArrayList<>() return true;
: new ArrayList<>(currentFeedModelList); }
final List<FeedModel> resultList = Arrays.asList(result); requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
if (isPullToRefresh) { return true;
finalList = resultList;
isPullToRefresh = false;
} else {
finalList.addAll(resultList);
} }
feedViewModel.getList().postValue(finalList); return false;
final PostModel feedPostModel = result[result.length - 1];
if (feedPostModel != null) {
feedEndCursor = feedPostModel.getEndCursor();
feedHasNextPage = feedPostModel.hasNextPage();
feedPostModel.setPageCursor(false, null);
}
binding.feedSwipeRefreshLayout.setRefreshing(false);
} }
}; });
private final FeedAdapterV2.SelectionModeCallback selectionModeCallback = new FeedAdapterV2.SelectionModeCallback() {
for (final FeedModel feedModel : result) { @Override
final DataSource<Void> ds = Fresco.getImagePipeline() public void onSelectionStart() {
.prefetchToBitmapCache(ImageRequest.fromUri(feedModel.getThumbnailUrl()), null); if (!onBackPressedCallback.isEnabled()) {
ds.subscribe(subscriber, UiThreadImmediateExecutorService.getInstance()); 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; private void navigateToProfile(final String username) {
if (v instanceof RamboTextView) { final NavController navController = NavHostFragment.findNavController(this);
if (feedModel.isMentionClicked()) feedModel.toggleCaption(); final Bundle bundle = new Bundle();
feedModel.setMentionClicked(false); bundle.putString("username", username);
if (!FeedItemViewHolder.expandCollapseTextView((RamboTextView) v, feedModel.getPostCaption())) navController.navigate(R.id.action_global_profileFragment, bundle);
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;
@Override @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
fragmentActivity = (MainActivity) requireActivity(); fragmentActivity = (MainActivity) requireActivity();
storiesService = StoriesService.getInstance(); storiesService = StoriesService.getInstance();
// feedService = FeedService.getInstance(); setHasOptionsMenu(true);
} }
@Override @Override
@ -314,58 +264,80 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
shouldRefresh = false; 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 @Override
public void onPause() { public void onPause() {
super.onPause(); super.onPause();
if (videoAwareRecyclerScroller != null) { // if (videoAwareRecyclerScroller != null) {
videoAwareRecyclerScroller.stopPlaying(); // videoAwareRecyclerScroller.stopPlaying();
} // }
} }
@Override @Override
public void onResume() { public void onResume() {
super.onResume(); super.onResume();
binding.feedSwipeRefreshLayout.setRefreshing(false); binding.feedSwipeRefreshLayout.setRefreshing(false);
if (videoAwareRecyclerScroller != null && shouldAutoPlay) { // if (videoAwareRecyclerScroller != null && shouldAutoPlay) {
videoAwareRecyclerScroller.startPlaying(); // videoAwareRecyclerScroller.startPlaying();
} // }
} }
@Override @Override
public void onRefresh() { public void onRefresh() {
isPullToRefresh = true; binding.feedRecyclerView.refresh();
feedEndCursor = null;
fetchFeed();
fetchStories(); fetchStories();
} }
private void setupFeed() { @Override
feedViewModel = new ViewModelProvider(fragmentActivity).get(FeedViewModel.class); 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(); final Context context = getContext();
if (context == null) return; if (context == null) return;
final LinearLayoutManager layoutManager = new LinearLayoutManager(context); if (requestCode == STORAGE_PERM_REQUEST_CODE && granted) {
binding.feedRecyclerView.setLayoutManager(layoutManager); if (downloadFeedModel == null) return;
binding.feedRecyclerView.setHasFixedSize(true); DownloadUtils.showDownloadDialog(context, downloadFeedModel, downloadChildPosition);
final FeedAdapter feedAdapter = new FeedAdapter(postViewClickListener, mentionClickListener); downloadFeedModel = null;
feedAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY); downloadChildPosition = -1;
binding.feedRecyclerView.setAdapter(feedAdapter); return;
feedViewModel.getList().observe(fragmentActivity, feedAdapter::submitList); }
final RecyclerLazyLoader lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { if (requestCode == STORAGE_PERM_REQUEST_CODE_FOR_SELECTION && granted) {
if (feedHasNextPage) { DownloadUtils.download(context, ImmutableList.copyOf(selectedFeedModels));
fetchFeed(); binding.feedRecyclerView.endSelection();
}
});
if (shouldAutoPlay) {
videoAwareRecyclerScroller = new VideoAwareRecyclerScroller();
binding.feedRecyclerView.addOnScrollListener(videoAwareRecyclerScroller);
} }
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); 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() { private void setupFeedStories() {
@ -383,16 +355,34 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
} }
private void fetchStories() { private void fetchStories() {
storiesFetching = true;
updateSwipeRefreshState();
storiesService.getFeedStories(new ServiceCallback<List<FeedStoryModel>>() { storiesService.getFeedStories(new ServiceCallback<List<FeedStoryModel>>() {
@Override @Override
public void onSuccess(final List<FeedStoryModel> result) { public void onSuccess(final List<FeedStoryModel> result) {
feedStoriesViewModel.getList().postValue(result); feedStoriesViewModel.getList().postValue(result);
storiesFetching = false;
updateSwipeRefreshState();
} }
@Override @Override
public void onFailure(final Throwable t) { public void onFailure(final Throwable t) {
Log.e(TAG, "failed", 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.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.pm.PackageManager;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.os.AsyncTask; import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.text.SpannableStringBuilder; import android.text.SpannableStringBuilder;
import android.text.method.LinkMovementMethod;
import android.text.style.RelativeSizeSpan; import android.text.style.RelativeSizeSpan;
import android.text.style.StyleSpan; import android.text.style.StyleSpan;
import android.util.Log; import android.util.Log;
@ -19,7 +19,6 @@ import android.view.MenuInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.widget.TextView;
import android.widget.Toast; import android.widget.Toast;
import androidx.activity.OnBackPressedCallback; import androidx.activity.OnBackPressedCallback;
@ -29,10 +28,12 @@ import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.ActionBar;
import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AlertDialog;
import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.coordinatorlayout.widget.CoordinatorLayout;
import androidx.core.content.PermissionChecker;
import androidx.fragment.app.Fragment; import androidx.fragment.app.Fragment;
import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentManager;
import androidx.fragment.app.FragmentTransaction; import androidx.fragment.app.FragmentTransaction;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections; import androidx.navigation.NavDirections;
import androidx.navigation.fragment.NavHostFragment; import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager; 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.BaseTransientBottomBar;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections; import java.util.Collections;
import java.util.Date; import java.util.Date;
import java.util.List; import java.util.List;
import java.util.Set;
import awais.instagrabber.ProfileNavGraphDirections; import awais.instagrabber.ProfileNavGraphDirections;
import awais.instagrabber.R; import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity; import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.adapters.HighlightsAdapter; import awais.instagrabber.adapters.HighlightsAdapter;
import awais.instagrabber.adapters.PostsAdapter;
import awais.instagrabber.asyncs.HighlightsFetcher; import awais.instagrabber.asyncs.HighlightsFetcher;
import awais.instagrabber.asyncs.PostsFetcher;
import awais.instagrabber.asyncs.ProfileFetcher; import awais.instagrabber.asyncs.ProfileFetcher;
import awais.instagrabber.asyncs.ProfilePostFetchService;
import awais.instagrabber.asyncs.UsernameFetcher; import awais.instagrabber.asyncs.UsernameFetcher;
import awais.instagrabber.asyncs.direct_messages.CreateThreadAction; import awais.instagrabber.asyncs.direct_messages.CreateThreadAction;
import awais.instagrabber.customviews.PrimaryActionModeCallback; import awais.instagrabber.customviews.PrimaryActionModeCallback;
import awais.instagrabber.customviews.PrimaryActionModeCallback.CallbacksHelper; 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.NestedCoordinatorLayout;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader;
import awais.instagrabber.databinding.FragmentProfileBinding; import awais.instagrabber.databinding.FragmentProfileBinding;
import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment;
import awais.instagrabber.dialogs.ProfilePicDialogFragment; import awais.instagrabber.dialogs.ProfilePicDialogFragment;
import awais.instagrabber.fragments.PostViewV2Fragment;
import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.interfaces.MentionClickListener; 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.ProfileModel;
import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.StoryModel;
import awais.instagrabber.models.enums.DownloadMethod;
import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.models.enums.FavoriteType;
import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.models.enums.PostItemType;
import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse; import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse;
@ -82,17 +83,19 @@ import awais.instagrabber.utils.DownloadUtils;
import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.HighlightsViewModel; import awais.instagrabber.viewmodels.HighlightsViewModel;
import awais.instagrabber.viewmodels.PostsViewModel;
import awais.instagrabber.webservices.FriendshipService; import awais.instagrabber.webservices.FriendshipService;
import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService; 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; import static awais.instagrabber.utils.Utils.settingsHelper;
public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "ProfileFragment"; 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 MainActivity fragmentActivity;
private CoordinatorLayout root; private CoordinatorLayout root;
@ -101,21 +104,21 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private String cookie; private String cookie;
private String username; private String username;
private ProfileModel profileModel; private ProfileModel profileModel;
private PostsViewModel postsViewModel;
private PostsAdapter postsAdapter;
private ActionMode actionMode; private ActionMode actionMode;
private Handler usernameSettingHandler; private Handler usernameSettingHandler;
private FriendshipService friendshipService; private FriendshipService friendshipService;
private StoriesService storiesService; private StoriesService storiesService;
private boolean shouldRefresh = true, hasStories = false; private boolean shouldRefresh = true;
private boolean hasNextPage; private boolean hasStories = false;
private String endCursor;
private AsyncTask<Void, Void, List<PostModel>> currentlyExecuting;
private boolean isPullToRefresh;
private HighlightsAdapter highlightsAdapter; private HighlightsAdapter highlightsAdapter;
private HighlightsViewModel highlightsViewModel; private HighlightsViewModel highlightsViewModel;
private MenuItem blockMenuItem; private MenuItem blockMenuItem;
private MenuItem restrictMenuItem; 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 = () -> { private final Runnable usernameSettingRunnable = () -> {
final ActionBar actionBar = fragmentActivity.getSupportActionBar(); final ActionBar actionBar = fragmentActivity.getSupportActionBar();
@ -129,10 +132,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) {
@Override @Override
public void handleOnBackPressed() { public void handleOnBackPressed() {
setEnabled(false); binding.postsRecyclerView.endSelection();
remove();
if (postsAdapter == null) return;
postsAdapter.clearSelection();
} }
}; };
private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback( private final PrimaryActionModeCallback multiSelectAction = new PrimaryActionModeCallback(
@ -140,58 +140,26 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
new CallbacksHelper() { new CallbacksHelper() {
@Override @Override
public void onDestroy(final ActionMode mode) { public void onDestroy(final ActionMode mode) {
onBackPressedCallback.handleOnBackPressed(); binding.postsRecyclerView.endSelection();
} }
@Override @Override
public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) { public boolean onActionItemClicked(final ActionMode mode, final MenuItem item) {
if (item.getItemId() == R.id.action_download) { if (item.getItemId() == R.id.action_download) {
if (postsAdapter == null || username == null) { if (ProfileFragment.this.selectedFeedModels == null) return false;
return false;
}
final Context context = getContext(); final Context context = getContext();
if (context == null) return false; if (context == null) return false;
DownloadUtils.batchDownload(context, if (checkSelfPermission(context, WRITE_PERMISSION) == PermissionChecker.PERMISSION_GRANTED) {
username, DownloadUtils.download(context, ImmutableList.copyOf(ProfileFragment.this.selectedFeedModels));
DownloadMethod.DOWNLOAD_MAIN, binding.postsRecyclerView.endSelection();
postsAdapter.getSelectedModels()); return true;
checkAndResetAction(); }
requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE_FOR_SELECTION);
return true; return true;
} }
return false; 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) -> { private final MentionClickListener mentionClickListener = (view, text, isHashtag, isLocation) -> {
Log.d(TAG, "action..."); Log.d(TAG, "action...");
if (isHashtag) { if (isHashtag) {
@ -210,6 +178,128 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
action.setUsername("@" + text); action.setUsername("@" + text);
NavHostFragment.findNavController(this).navigate(action); 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 @Override
public void onCreate(@Nullable final Bundle savedInstanceState) { public void onCreate(@Nullable final Bundle savedInstanceState) {
@ -264,7 +354,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override @Override
public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) {
inflater.inflate(R.menu.profile_menu, menu); inflater.inflate(R.menu.profile_menu, menu);
// favMenuItem = menu.findItem(R.id.favourites);
blockMenuItem = menu.findItem(R.id.block); blockMenuItem = menu.findItem(R.id.block);
if (blockMenuItem != null) { if (blockMenuItem != null) {
blockMenuItem.setVisible(false); blockMenuItem.setVisible(false);
@ -277,6 +366,10 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override @Override
public boolean onOptionsItemSelected(@NonNull final MenuItem item) { public boolean onOptionsItemSelected(@NonNull final MenuItem item) {
if (item.getItemId() == R.id.layout) {
showPostsLayoutPreferences();
return true;
}
if (item.getItemId() == R.id.restrict) { if (item.getItemId() == R.id.restrict) {
if (!isLoggedIn) return false; if (!isLoggedIn) return false;
final String action = profileModel.getRestricted() ? "Unrestrict" : "Restrict"; final String action = profileModel.getRestricted() ? "Unrestrict" : "Restrict";
@ -343,10 +436,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
@Override @Override
public void onRefresh() { public void onRefresh() {
isPullToRefresh = true;
endCursor = null;
fetchProfileDetails(); fetchProfileDetails();
fetchPosts();
} }
@Override @Override
@ -355,14 +445,30 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
if (usernameSettingHandler != null) { if (usernameSettingHandler != null) {
usernameSettingHandler.removeCallbacks(usernameSettingRunnable); usernameSettingHandler.removeCallbacks(usernameSettingRunnable);
} }
if (postsViewModel != null) {
postsViewModel.getList().postValue(Collections.emptyList());
}
if (highlightsViewModel != null) { if (highlightsViewModel != null) {
highlightsViewModel.getList().postValue(Collections.emptyList()); 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() { private void init() {
if (getArguments() != null) { if (getArguments() != null) {
final ProfileFragmentArgs fragmentArgs = ProfileFragmentArgs.fromBundle(getArguments()); final ProfileFragmentArgs fragmentArgs = ProfileFragmentArgs.fromBundle(getArguments());
@ -381,7 +487,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
return; return;
} }
binding.swipeRefreshLayout.setEnabled(true); binding.swipeRefreshLayout.setEnabled(true);
setupPosts();
setupHighlights(); setupHighlights();
setupCommonListeners(); setupCommonListeners();
fetchUsername(); fetchUsername();
@ -416,7 +521,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private void fetchProfileDetails() { private void fetchProfileDetails() {
if (TextUtils.isEmpty(username)) return; if (TextUtils.isEmpty(username)) return;
new ProfileFetcher(username.substring(1), profileModel -> { new ProfileFetcher(username.trim().substring(1), profileModel -> {
if (getContext() == null) return; if (getContext() == null) return;
this.profileModel = profileModel; this.profileModel = profileModel;
// final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); // 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(); Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_SHORT).show();
return; return;
} }
if (!postsSetupDone) {
setupPosts();
} else {
binding.postsRecyclerView.refresh();
}
binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE); binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE);
final String profileId = profileModel.getId(); final String profileId = profileModel.getId();
final String myId = CookieUtils.getUserIdFromCookie(cookie); final String myId = CookieUtils.getUserIdFromCookie(cookie);
if (isLoggedIn) { if (isLoggedIn) {
storiesService.getUserStory(profileId, fetchStoryAndHighlights(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);
}
}
} }
setupButtons(profileId, myId);
if (!profileId.equals(myId)) { if (!profileId.equals(myId)) {
binding.favCb.setVisibility(View.VISIBLE); binding.favCb.setVisibility(View.VISIBLE);
final boolean isFav = Utils.dataBox.getFavorite(username.substring(1), FavoriteType.USER) != null; 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 { } else {
binding.favCb.setVisibility(View.GONE); binding.favCb.setVisibility(View.GONE);
} }
binding.mainProfileImage.setImageURI(profileModel.getSdProfilePic()); binding.mainProfileImage.setImageURI(profileModel.getHdProfilePic());
final long followersCount = profileModel.getFollowersCount(); final long followersCount = profileModel.getFollowersCount();
final long followingCount = profileModel.getFollowingCount(); 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() binding.mainFullName.setText(TextUtils.isEmpty(profileModel.getName()) ? profileModel.getUsername()
: profileModel.getName()); : profileModel.getName());
CharSequence biography = profileModel.getBiography(); final String biography = profileModel.getBiography();
binding.mainBiography.setCaptionIsExpandable(true); if (!TextUtils.isEmpty(biography)) {
binding.mainBiography.setCaptionIsExpanded(true);
if (TextUtils.hasMentions(biography)) {
biography = TextUtils.getMentionText(biography);
binding.mainBiography.setText(biography, TextView.BufferType.SPANNABLE);
binding.mainBiography.setMentionClickListener(mentionClickListener);
} else {
binding.mainBiography.setText(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(); final String url = profileModel.getUrl();
if (TextUtils.isEmpty(url)) { if (TextUtils.isEmpty(url)) {
binding.mainUrl.setVisibility(View.GONE); binding.mainUrl.setVisibility(View.GONE);
} else { } else {
binding.mainUrl.setVisibility(View.VISIBLE); binding.mainUrl.setVisibility(View.VISIBLE);
binding.mainUrl.setText(TextUtils.getSpannableUrl(url)); binding.mainUrl.setText(url);
binding.mainUrl.setMovementMethod(LinkMovementMethod.getInstance()); 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()) { if (!profileModel.isReallyPrivate()) {
binding.mainFollowing.setClickable(true); binding.mainFollowing.setClickable(true);
binding.mainFollowers.setClickable(true); binding.mainFollowers.setClickable(true);
@ -608,7 +649,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
binding.swipeRefreshLayout.setRefreshing(true); binding.swipeRefreshLayout.setRefreshing(true);
binding.mainPosts.setVisibility(View.VISIBLE); binding.postsRecyclerView.setVisibility(View.VISIBLE);
fetchPosts(); fetchPosts();
} else { } else {
binding.mainFollowers.setClickable(false); binding.mainFollowers.setClickable(false);
@ -618,15 +659,97 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
binding.privatePage1.setImageResource(R.drawable.lock); binding.privatePage1.setImageResource(R.drawable.lock);
binding.privatePage2.setText(R.string.priv_acc); binding.privatePage2.setText(R.string.priv_acc);
binding.privatePage.setVisibility(View.VISIBLE); 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() { private void setupCommonListeners() {
final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie);
// final boolean isSelf = isLoggedIn && profileModel != null && userIdFromCookie != null && userIdFromCookie
// .equals(profileModel.getId());
binding.btnFollow.setOnClickListener(v -> { binding.btnFollow.setOnClickListener(v -> {
if (profileModel.getFollowing() || profileModel.getRequested()) { if (profileModel.getFollowing() || profileModel.getRequested()) {
friendshipService.unfollow( friendshipService.unfollow(
@ -636,7 +759,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
new ServiceCallback<FriendshipRepoChangeRootResponse>() { new ServiceCallback<FriendshipRepoChangeRootResponse>() {
@Override @Override
public void onSuccess(final FriendshipRepoChangeRootResponse result) { public void onSuccess(final FriendshipRepoChangeRootResponse result) {
Log.d(TAG, "Unfollow success: " + result); // Log.d(TAG, "Unfollow success: " + result);
onRefresh(); onRefresh();
} }
@ -653,7 +776,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
new ServiceCallback<FriendshipRepoChangeRootResponse>() { new ServiceCallback<FriendshipRepoChangeRootResponse>() {
@Override @Override
public void onSuccess(final FriendshipRepoChangeRootResponse result) { public void onSuccess(final FriendshipRepoChangeRootResponse result) {
Log.d(TAG, "Follow success: " + result); // Log.d(TAG, "Follow success: " + result);
onRefresh(); onRefresh();
} }
@ -682,12 +805,10 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
PostItemType.TAGGED); PostItemType.TAGGED);
NavHostFragment.findNavController(this).navigate(action); NavHostFragment.findNavController(this).navigate(action);
}); });
binding.btnDM.setOnClickListener(v -> { binding.btnDM.setOnClickListener(v -> new CreateThreadAction(cookie, profileModel.getId(), threadId -> {
new CreateThreadAction(cookie, profileModel.getId(), threadId -> { final NavDirections action = ProfileFragmentDirections.actionProfileFragmentToDMThreadFragment(threadId, profileModel.getUsername());
final NavDirections action = ProfileFragmentDirections.actionProfileFragmentToDMThreadFragment(threadId, profileModel.getUsername()); NavHostFragment.findNavController(this).navigate(action);
NavHostFragment.findNavController(this).navigate(action); }).execute());
}).execute();
});
binding.mainProfileImage.setOnClickListener(v -> { binding.mainProfileImage.setOnClickListener(v -> {
if (!hasStories) { if (!hasStories) {
// show profile pic // 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 ProfilePicDialogFragment fragment = new ProfilePicDialogFragment(profileModel.getId(), username, profileModel.getHdProfilePic());
final FragmentTransaction ft = fragmentManager.beginTransaction(); final FragmentTransaction ft = fragmentManager.beginTransaction();
ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN)
.add(fragment, "profilePicDialog") .add(fragment, "profilePicDialog")
.commit(); .commit();
} }
} }
@ -773,62 +894,20 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
private void setupPosts() { private void setupPosts() {
postsViewModel = new ViewModelProvider(this).get(PostsViewModel.class); binding.postsRecyclerView.setViewModelStoreOwner(this)
final Context context = getContext(); .setLifeCycleOwner(this)
if (context == null) return; .setPostFetchService(new ProfilePostFetchService(profileModel))
final GridAutofitLayoutManager layoutManager = new GridAutofitLayoutManager(context, Utils.convertDpToPx(110)); .setLayoutPreferences(PostsLayoutPreferences.fromJson(settingsHelper.getString(Constants.PREF_PROFILE_POSTS_LAYOUT)))
binding.mainPosts.setLayoutManager(layoutManager); .addFetchStatusChangeListener(fetching -> updateSwipeRefreshState())
binding.mainPosts.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(4))); .setFeedItemCallback(feedItemCallback)
postsAdapter = new PostsAdapter((postModel, position) -> { .setSelectionModeCallback(selectionModeCallback)
if (postsAdapter.isSelecting()) { .init();
if (actionMode == null) return; binding.swipeRefreshLayout.setRefreshing(true);
final String title = getString(R.string.number_selected, postsSetupDone = true;
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);
}, (model, position) -> { private void updateSwipeRefreshState() {
if (!postsAdapter.isSelecting()) { binding.swipeRefreshLayout.setRefreshing(binding.postsRecyclerView.isFetching() || highlightsFetching);
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 setupHighlights() { private void setupHighlights() {
@ -847,36 +926,24 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
private void fetchPosts() { private void fetchPosts() {
stopCurrentExecutor(); // stopCurrentExecutor();
binding.swipeRefreshLayout.setRefreshing(true); binding.swipeRefreshLayout.setRefreshing(true);
currentlyExecuting = new PostsFetcher(profileModel.getId(), PostItemType.MAIN, endCursor, postsFetchListener) // currentlyExecuting = new PostsFetcher(profileModel.getId(), PostItemType.MAIN, endCursor, postsFetchListener)
.setUsername(profileModel.getUsername()) // .setUsername(profileModel.getUsername())
.executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); // .executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} }
public void stopCurrentExecutor() { private void navigateToProfile(final String username) {
if (currentlyExecuting != null) { final NavController navController = NavHostFragment.findNavController(this);
try { final Bundle bundle = new Bundle();
currentlyExecuting.cancel(true); bundle.putString("username", username);
} catch (final Exception e) { navController.navigate(R.id.action_global_profileFragment, bundle);
if (logCollector != null) logCollector.appendException(e, LogCollector.LogFile.MAIN_HELPER, "stopCurrentExecutor");
Log.e(TAG, "", e);
}
}
} }
private boolean checkAndResetAction() { private void showPostsLayoutPreferences() {
if (!onBackPressedCallback.isEnabled() && actionMode == null) { final PostsLayoutPreferencesDialogFragment fragment = new PostsLayoutPreferencesDialogFragment(
return false; Constants.PREF_PROFILE_POSTS_LAYOUT,
} preferences -> new Handler().postDelayed(() -> binding.postsRecyclerView.setLayoutPreferences(preferences), 200));
if (onBackPressedCallback.isEnabled()) { fragment.show(getChildFragmentManager(), "posts_layout_preferences");
onBackPressedCallback.setEnabled(false);
onBackPressedCallback.remove();
}
if (actionMode != null) {
actionMode.finish();
actionMode = null;
}
return true;
} }
} }

View File

@ -26,24 +26,26 @@ public class AboutFragment extends BasePreferencesFragment {
generalCategory.addPreference(getRepoPreference()); generalCategory.addPreference(getRepoPreference());
generalCategory.addPreference(getFeedbackPreference()); 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); final PreferenceCategory licenseCategory = new PreferenceCategory(context);
screen.addPreference(licenseCategory); screen.addPreference(licenseCategory);
licenseCategory.setTitle(R.string.about_category_license); licenseCategory.setTitle(R.string.about_category_license);
licenseCategory.setIconSpaceReserved(false); licenseCategory.setIconSpaceReserved(false);
licenseCategory.addPreference(getLicensePreference()); licenseCategory.addPreference(getLicensePreference());
licenseCategory.addPreference(getLiabilityPreference()); 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() { private Preference getDocsPreference() {
@ -55,7 +57,7 @@ public class AboutFragment extends BasePreferencesFragment {
preference.setIconSpaceReserved(false); preference.setIconSpaceReserved(false);
preference.setOnPreferenceClickListener(p -> { preference.setOnPreferenceClickListener(p -> {
final Intent intent = new Intent(Intent.ACTION_VIEW); 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); startActivity(intent);
return true; return true;
}); });
@ -71,7 +73,7 @@ public class AboutFragment extends BasePreferencesFragment {
preference.setIconSpaceReserved(false); preference.setIconSpaceReserved(false);
preference.setOnPreferenceClickListener(p -> { preference.setOnPreferenceClickListener(p -> {
final Intent intent = new Intent(Intent.ACTION_VIEW); 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); startActivity(intent);
return true; return true;
}); });
@ -174,6 +176,38 @@ public class AboutFragment extends BasePreferencesFragment {
return preference; 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() { private Preference getLicensePreference() {
final Context context = getContext(); final Context context = getContext();
if (context == null) return null; if (context == null) return null;

View File

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

View File

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

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