Merge branch 'master' into support-android-11

This commit is contained in:
Ammar Githam 2021-04-04 09:06:26 +09:00
commit 7848b60b97
106 changed files with 2162 additions and 627 deletions

View File

@ -66,11 +66,13 @@ android {
dimension "repo"
// versionNameSuffix "-github" // appended in assemble task
buildConfigField("String", "dsn", SENTRY_DSN)
buildConfigField("boolean", "isPre", "false")
}
fdroid {
dimension "repo"
versionNameSuffix "-fdroid"
buildConfigField("boolean", "isPre", "false")
}
}
@ -84,6 +86,7 @@ android {
def suffix = "${versionName}-${flavor}_${builtType}" // eg. 19.1.0-github_debug or release
if (builtType.toString() == 'release' && project.hasProperty("pre")) {
buildConfigField("boolean", "isPre", "true")
// append latest commit short hash for pre-release
suffix = "${versionName}.${getGitHash()}-${flavor}" // eg. 19.1.0.b123456-github
}

View File

@ -0,0 +1,64 @@
package awais.instagrabber.utils;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity;
import org.json.JSONObject;
import java.net.HttpURLConnection;
import java.net.URL;
public class UpdateChecker {
private static final Object LOCK = new Object();
private static final String TAG = UpdateChecker.class.getSimpleName();
private static UpdateChecker instance;
public static UpdateChecker getInstance() {
if (instance == null) {
synchronized (LOCK) {
if (instance == null) {
instance = new UpdateChecker();
}
}
}
return instance;
}
/**
* Needs to be called asynchronously
*
* @return the latest version from f-droid
*/
@Nullable
public String getLatestVersion() {
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL("https://f-droid.org/api/v1/packages/me.austinhuang.instagrabber").openConnection();
conn.setUseCaches(false);
conn.setRequestProperty("User-Agent", "https://Barinsta.AustinHuang.me / mailto:Barinsta@AustinHuang.me");
conn.connect();
final int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
final JSONObject data = new JSONObject(NetworkUtils.readFromConnection(conn));
return "v" + data.getJSONArray("packages").getJSONObject(0).getString("versionName");
// if (BuildConfig.VERSION_CODE < data.getInt("suggestedVersionCode")) {
// }
}
} catch (final Exception e) {
Log.e(TAG, "", e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
return null;
}
public void onDownload(@NonNull final AppCompatActivity context) {
Utils.openURL(context, "https://f-droid.org/packages/me.austinhuang.instagrabber/");
}
}

View File

@ -0,0 +1,61 @@
package awais.instagrabber.utils;
import android.content.Context;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import java.net.HttpURLConnection;
import java.net.URL;
public class UpdateChecker {
private static final Object LOCK = new Object();
private static final String TAG = UpdateChecker.class.getSimpleName();
private static UpdateChecker instance;
public static UpdateChecker getInstance() {
if (instance == null) {
synchronized (LOCK) {
if (instance == null) {
instance = new UpdateChecker();
}
}
}
return instance;
}
/**
* Needs to be called asynchronously
*
* @return the latest version from Github
*/
@Nullable
public String getLatestVersion() {
HttpURLConnection conn = null;
try {
conn = (HttpURLConnection) new URL("https://github.com/austinhuang0131/barinsta/releases/latest").openConnection();
conn.setInstanceFollowRedirects(false);
conn.setUseCaches(false);
conn.setRequestProperty("User-Agent", "https://Barinsta.AustinHuang.me / mailto:Barinsta@AustinHuang.me");
conn.connect();
final int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_MOVED_TEMP) {
return "v" + conn.getHeaderField("Location").split("/v")[1];
// return !version.equals(BuildConfig.VERSION_NAME);
}
} catch (final Exception e) {
Log.e(TAG, "", e);
} finally {
if (conn != null) {
conn.disconnect();
}
}
return null;
}
public void onDownload(@NonNull final Context context) {
Utils.openURL(context, "https://github.com/austinhuang0131/instagrabber/releases/latest");
}
}

View File

@ -130,6 +130,9 @@
android:name="android.support.PARENT_ACTIVITY"
android:value=".activities.MainActivity" />
</activity>
<activity
android:name=".utils.ProcessPhoenix"
android:theme="@style/Theme.AppCompat.Translucent" />
<provider
android:name="androidx.core.content.FileProvider"

View File

@ -27,6 +27,9 @@ import static awais.instagrabber.utils.Utils.clipboardManager;
import static awais.instagrabber.utils.Utils.datetimeParser;
import static awais.instagrabber.utils.Utils.settingsHelper;
//import awaisomereport.LogCollector;
//import static awais.instagrabber.utils.Utils.logCollector;
public final class InstaGrabberApplication extends Application {
private static final String TAG = "InstaGrabberApplication";
@ -53,7 +56,7 @@ public final class InstaGrabberApplication extends Application {
Log.e(TAG, "Error", e);
}
}
// final Set<RequestListener> requestListeners = new HashSet<>();
// requestListeners.add(new RequestLoggingListener());
final ImagePipelineConfig imagePipelineConfig = ImagePipelineConfig

View File

@ -13,7 +13,6 @@ import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.CompoundButton;
import android.widget.Toast;
import androidx.annotation.Nullable;
@ -24,7 +23,7 @@ import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.TextUtils;
public final class Login extends BaseLanguageActivity implements View.OnClickListener, CompoundButton.OnCheckedChangeListener {
public final class Login extends BaseLanguageActivity implements View.OnClickListener {
private final WebViewClient webViewClient = new WebViewClient() {
@Override
public void onPageStarted(final WebView view, final String url, final Bitmap favicon) {
@ -53,7 +52,7 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
}
private final WebChromeClient webChromeClient = new WebChromeClient();
private String webViewUrl, defaultUserAgent;
private String webViewUrl;
private boolean ready = false;
private ActivityLoginBinding loginBinding;
@ -65,7 +64,6 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
initWebView();
loginBinding.desktopMode.setOnCheckedChangeListener(this);
loginBinding.cookies.setOnClickListener(this);
loginBinding.refresh.setOnClickListener(this);
}
@ -86,23 +84,6 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
}
}
@Override
public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) {
final WebSettings webSettings = loginBinding.webView.getSettings();
final String newUserAgent = isChecked
? "Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/83.0.4103.61 Safari/537.36"
: defaultUserAgent;
webSettings.setUserAgentString(newUserAgent);
webSettings.setUseWideViewPort(isChecked);
webSettings.setLoadWithOverviewMode(isChecked);
webSettings.setSupportZoom(isChecked);
webSettings.setBuiltInZoomControls(isChecked);
loginBinding.webView.loadUrl("https://instagram.com/");
}
@SuppressLint("SetJavaScriptEnabled")
private void initWebView() {
if (loginBinding != null) {
@ -110,7 +91,7 @@ public final class Login extends BaseLanguageActivity implements View.OnClickLis
loginBinding.webView.setWebViewClient(webViewClient);
final WebSettings webSettings = loginBinding.webView.getSettings();
if (webSettings != null) {
if (defaultUserAgent == null) defaultUserAgent = webSettings.getUserAgentString();
webSettings.setUserAgentString("Mozilla/5.0 (Linux; Android 10) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/89.0.4389.105 Mobile Safari/537.36");
webSettings.setJavaScriptEnabled(true);
webSettings.setDomStorageEnabled(true);
webSettings.setSupportZoom(true);

View File

@ -8,7 +8,6 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.res.TypedArray;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Build;
@ -24,6 +23,7 @@ import android.view.WindowManager;
import android.widget.AutoCompleteTextView;
import android.widget.Toast;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
@ -50,15 +50,16 @@ import com.google.android.material.appbar.CollapsingToolbarLayout;
import com.google.android.material.badge.BadgeDrawable;
import com.google.android.material.behavior.HideBottomViewOnScrollBehavior;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterators;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.adapters.SuggestionsAdapter;
import awais.instagrabber.asyncs.PostFetcher;
@ -69,6 +70,7 @@ import awais.instagrabber.fragments.directmessages.DirectMessageInboxFragmentDir
import awais.instagrabber.fragments.main.FeedFragment;
import awais.instagrabber.fragments.settings.PreferenceKeys;
import awais.instagrabber.models.IntentModel;
import awais.instagrabber.models.Tab;
import awais.instagrabber.models.enums.SuggestionType;
import awais.instagrabber.repositories.responses.search.SearchItem;
import awais.instagrabber.repositories.responses.search.SearchResponse;
@ -84,6 +86,7 @@ import awais.instagrabber.utils.Utils;
import awais.instagrabber.utils.emoji.EmojiParser;
import awais.instagrabber.viewmodels.AppStateViewModel;
import awais.instagrabber.viewmodels.DirectInboxViewModel;
import awais.instagrabber.webservices.RetrofitFactory;
import awais.instagrabber.webservices.SearchService;
import retrofit2.Call;
import retrofit2.Callback;
@ -94,15 +97,8 @@ import static awais.instagrabber.utils.Utils.settingsHelper;
public class MainActivity extends BaseLanguageActivity implements FragmentManager.OnBackStackChangedListener {
private static final String TAG = "MainActivity";
private static final List<Integer> SHOW_BOTTOM_VIEW_DESTINATIONS = Arrays.asList(
R.id.directMessagesInboxFragment,
R.id.feedFragment,
R.id.profileFragment,
R.id.discoverFragment,
R.id.morePreferencesFragment);
private static final Map<Integer, Integer> NAV_TO_MENU_ID_MAP = new HashMap<>();
private static final String FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex";
private static final String LAST_SELECT_NAV_MENU_ID = "lastSelectedNavMenuId";
private ActivityMainBinding binding;
private LiveData<NavController> currentNavControllerLiveData;
@ -114,10 +110,13 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
private boolean showSearch = true;
private Handler suggestionsFetchHandler;
private int firstFragmentGraphIndex;
private int lastSelectedNavMenuId;
private boolean isActivityCheckerServiceBound = false;
private boolean isBackStackEmpty = false;
private boolean isLoggedIn;
private HideBottomViewOnScrollBehavior<BottomNavigationView> behavior;
private List<Tab> currentTabs;
private List<Integer> showBottomViewDestinations = Collections.emptyList();
private final ServiceConnection serviceConnection = new ServiceConnection() {
@Override
@ -133,16 +132,9 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
}
};
static {
NAV_TO_MENU_ID_MAP.put(R.navigation.direct_messages_nav_graph, R.id.direct_messages_nav_graph);
NAV_TO_MENU_ID_MAP.put(R.navigation.feed_nav_graph, R.id.feed_nav_graph);
NAV_TO_MENU_ID_MAP.put(R.navigation.profile_nav_graph, R.id.profile_nav_graph);
NAV_TO_MENU_ID_MAP.put(R.navigation.discover_nav_graph, R.id.discover_nav_graph);
NAV_TO_MENU_ID_MAP.put(R.navigation.more_nav_graph, R.id.more_nav_graph);
}
@Override
protected void onCreate(@Nullable final Bundle savedInstanceState) {
RetrofitFactory.setup(this);
super.onCreate(savedInstanceState);
binding = ActivityMainBinding.inflate(getLayoutInflater());
final String cookie = settingsHelper.getString(Constants.COOKIE);
@ -165,8 +157,10 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
setupBottomNavigationBar(true);
}
setupSuggestions();
final boolean checkUpdates = settingsHelper.getBoolean(Constants.CHECK_UPDATES);
if (checkUpdates) FlavorTown.updateCheck(this);
if (!BuildConfig.isPre) {
final boolean checkUpdates = settingsHelper.getBoolean(Constants.CHECK_UPDATES);
if (checkUpdates) FlavorTown.updateCheck(this);
}
FlavorTown.changelogCheck(this);
new ViewModelProvider(this).get(AppStateViewModel.class); // Just initiate the App state here
final Intent intent = getIntent();
@ -227,6 +221,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
@Override
protected void onSaveInstanceState(@NonNull final Bundle outState) {
outState.putString(FIRST_FRAGMENT_GRAPH_INDEX_KEY, String.valueOf(firstFragmentGraphIndex));
outState.putString(LAST_SELECT_NAV_MENU_ID, String.valueOf(binding.bottomNavView.getSelectedItemId()));
super.onSaveInstanceState(outState);
}
@ -239,6 +234,12 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
firstFragmentGraphIndex = Integer.parseInt(key);
} catch (NumberFormatException ignored) { }
}
final String lastSelected = (String) savedInstanceState.get(LAST_SELECT_NAV_MENU_ID);
if (lastSelected != null) {
try {
lastSelectedNavMenuId = Integer.parseInt(lastSelected);
} catch (NumberFormatException ignored) { }
}
setupBottomNavigationBar(false);
}
@ -264,6 +265,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
Log.e(TAG, "onDestroy: ", e);
}
unbindActivityCheckerService();
RetrofitFactory.getInstance().destroy();
}
@Override
@ -273,9 +275,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
final NavController navController = currentNavControllerLiveData.getValue();
if (navController != null) {
@SuppressLint("RestrictedApi") final Deque<NavBackStackEntry> backStack = navController.getBackStack();
if (backStack != null) {
currentNavControllerBackStack = backStack.size();
}
currentNavControllerBackStack = backStack.size();
}
}
if (isTaskRoot() && isBackStackEmpty && currentNavControllerBackStack == 2) {
@ -328,7 +328,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
final Bundle bundle = new Bundle();
switch (type) {
case TYPE_LOCATION:
bundle.putLong("locationId", Long.valueOf(query));
bundle.putLong("locationId", Long.parseLong(query));
navController.navigate(R.id.action_global_locationFragment, bundle);
break;
case TYPE_HASHTAG:
@ -378,14 +378,16 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
cursor = null;
return;
}
final List<SearchItem> result = new ArrayList<SearchItem>();
final List<SearchItem> result = new ArrayList<>();
if (isLoggedIn) {
if (body.getList() != null) result.addAll(searchHash ? body.getList()
.stream()
.filter(i -> i.getUser() == null)
.collect(Collectors.toList()) : body.getList());
}
else {
if (body.getList() != null) {
result.addAll(searchHash ? body.getList()
.stream()
.filter(i -> i.getUser() == null)
.collect(Collectors.toList())
: body.getList());
}
} else {
if (body.getUsers() != null && !searchHash) result.addAll(body.getUsers());
if (body.getHashtags() != null) result.addAll(body.getHashtags());
if (body.getPlaces() != null) result.addAll(body.getPlaces());
@ -430,9 +432,10 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
@Override
public void onFailure(@NonNull final Call<SearchResponse> call,
Throwable t) {
if (!call.isCanceled() && t != null)
@NonNull Throwable t) {
if (!call.isCanceled()) {
Log.e(TAG, "Exception on search:", t);
}
}
};
@ -454,7 +457,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
}
prevSuggestionAsync = searchService.search(isLoggedIn,
searchUser || searchHash ? currentSearchQuery.substring(1)
: currentSearchQuery,
: currentSearchQuery,
searchUser ? "user" : (searchHash ? "hashtag" : "blended"));
suggestionAdapter.changeCursor(null);
prevSuggestionAsync.enqueue(cb);
@ -484,36 +487,18 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
return true;
}
private void setupBottomNavigationBar(final boolean setDefaultFromSettings) {
int main_nav_ids = R.array.main_nav_ids;
if (!isLoggedIn) {
main_nav_ids = R.array.logged_out_main_nav_ids;
final int selectedItemId = binding.bottomNavView.getSelectedItemId();
binding.bottomNavView.getMenu().clear();
binding.bottomNavView.inflateMenu(R.menu.logged_out_bottom_navigation_menu);
if (selectedItemId == R.id.profile_nav_graph
|| selectedItemId == R.id.more_nav_graph) {
binding.bottomNavView.setSelectedItemId(selectedItemId);
} else {
setBottomNavSelectedItem(R.navigation.profile_nav_graph);
}
}
final List<Integer> mainNavList = getMainNavList(main_nav_ids);
if (setDefaultFromSettings) {
final String defaultTabResNameString = settingsHelper.getString(Constants.DEFAULT_TAB);
try {
int navId = 0;
if (!TextUtils.isEmpty(defaultTabResNameString)) {
navId = getResources().getIdentifier(defaultTabResNameString, "navigation", getPackageName());
}
final int defaultNavId = navId <= 0 ? R.navigation.profile_nav_graph
: navId;
final int index = mainNavList.indexOf(defaultNavId);
if (index >= 0) firstFragmentGraphIndex = index;
setBottomNavSelectedItem(defaultNavId);
} catch (NumberFormatException e) {
Log.e(TAG, "Error parsing id", e);
}
private void setupBottomNavigationBar(final boolean setDefaultTabFromSettings) {
currentTabs = !isLoggedIn ? setupAnonBottomNav() : setupMainBottomNav();
final List<Integer> mainNavList = currentTabs.stream()
.map(Tab::getNavigationResId)
.collect(Collectors.toList());
showBottomViewDestinations = currentTabs.stream()
.map(Tab::getStartDestinationFragmentId)
.collect(Collectors.toList());
if (setDefaultTabFromSettings) {
setSelectedTab(currentTabs);
} else {
binding.bottomNavView.setSelectedItemId(lastSelectedNavMenuId);
}
final LiveData<NavController> navControllerLiveData = setupWithNavController(
binding.bottomNavView,
@ -536,27 +521,86 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
});
}
private void setBottomNavSelectedItem(final int navId) {
final Integer menuId = NAV_TO_MENU_ID_MAP.get(navId);
if (menuId != null) {
binding.bottomNavView.setSelectedItemId(menuId);
private void setSelectedTab(final List<Tab> tabs) {
final String defaultTabResNameString = settingsHelper.getString(Constants.DEFAULT_TAB);
try {
int navId = 0;
if (!TextUtils.isEmpty(defaultTabResNameString)) {
navId = getResources().getIdentifier(defaultTabResNameString, "navigation", getPackageName());
}
final int navGraph = isLoggedIn ? R.navigation.feed_nav_graph
: R.navigation.profile_nav_graph;
final int defaultNavId = navId <= 0 ? navGraph : navId;
int index = Iterators.indexOf(tabs.iterator(), tab -> {
if (tab == null) return false;
return tab.getNavigationResId() == defaultNavId;
});
if (index < 0 || index >= tabs.size()) index = 0;
firstFragmentGraphIndex = index;
setBottomNavSelectedTab(tabs.get(index));
} catch (Exception e) {
Log.e(TAG, "Error parsing id", e);
}
}
@NonNull
private List<Integer> getMainNavList(final int main_nav_ids) {
final TypedArray navIds = getResources().obtainTypedArray(main_nav_ids);
final List<Integer> mainNavList = new ArrayList<>(navIds.length());
final int length = navIds.length();
for (int i = 0; i < length; i++) {
final int resourceId = navIds.getResourceId(i, -1);
if (resourceId < 0) continue;
mainNavList.add(resourceId);
private List<Tab> setupAnonBottomNav() {
final int selectedItemId = binding.bottomNavView.getSelectedItemId();
final Tab profileTab = new Tab(R.drawable.ic_person_24,
getString(R.string.profile),
false,
"profile_nav_graph",
R.navigation.profile_nav_graph,
R.id.profile_nav_graph,
R.id.profileFragment);
final Tab moreTab = new Tab(R.drawable.ic_more_horiz_24,
getString(R.string.more),
false,
"more_nav_graph",
R.navigation.more_nav_graph,
R.id.more_nav_graph,
R.id.morePreferencesFragment);
final Menu menu = binding.bottomNavView.getMenu();
menu.clear();
menu.add(0, profileTab.getNavigationRootId(), 0, profileTab.getTitle()).setIcon(profileTab.getIconResId());
menu.add(0, moreTab.getNavigationRootId(), 0, moreTab.getTitle()).setIcon(moreTab.getIconResId());
if (selectedItemId != R.id.profile_nav_graph && selectedItemId != R.id.more_nav_graph) {
setBottomNavSelectedTab(profileTab);
}
navIds.recycle();
return mainNavList;
return ImmutableList.of(profileTab, moreTab);
}
private List<Tab> setupMainBottomNav() {
final Menu menu = binding.bottomNavView.getMenu();
menu.clear();
final List<Tab> navTabList = Utils.getNavTabList(this).first;
for (final Tab tab : navTabList) {
menu.add(0, tab.getNavigationRootId(), 0, tab.getTitle()).setIcon(tab.getIconResId());
}
return navTabList;
}
private void setBottomNavSelectedTab(@NonNull final Tab tab) {
binding.bottomNavView.setSelectedItemId(tab.getNavigationRootId());
}
private void setBottomNavSelectedTab(@SuppressWarnings("SameParameterValue") @IdRes final int navGraphRootId) {
binding.bottomNavView.setSelectedItemId(navGraphRootId);
}
// @NonNull
// private List<Integer> getMainNavList(final int main_nav_ids) {
// final TypedArray navIds = getResources().obtainTypedArray(main_nav_ids);
// final List<Integer> mainNavList = new ArrayList<>(navIds.length());
// final int length = navIds.length();
// for (int i = 0; i < length; i++) {
// final int resourceId = navIds.getResourceId(i, -1);
// if (resourceId < 0) continue;
// mainNavList.add(resourceId);
// }
// navIds.recycle();
// return mainNavList;
// }
private void setupNavigation(final Toolbar toolbar, final NavController navController) {
if (navController == null) return;
NavigationUI.setupWithNavController(toolbar, navController);
@ -574,7 +618,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
final int destinationId = destination.getId();
@SuppressLint("RestrictedApi") final Deque<NavBackStackEntry> backStack = navController.getBackStack();
setupMenu(backStack.size(), destinationId);
final boolean contains = SHOW_BOTTOM_VIEW_DESTINATIONS.contains(destinationId);
final boolean contains = showBottomViewDestinations.contains(destinationId);
binding.bottomNavView.setVisibility(contains ? View.VISIBLE : View.GONE);
if (contains && behavior != null) {
behavior.slideUp(binding.bottomNavView);
@ -684,7 +728,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
});
final int selectedItemId = binding.bottomNavView.getSelectedItemId();
if (selectedItemId != R.navigation.direct_messages_nav_graph) {
setBottomNavSelectedItem(R.navigation.direct_messages_nav_graph);
setBottomNavSelectedTab(R.id.direct_messages_nav_graph);
}
}
@ -756,7 +800,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
final NavController navController = currentNavControllerLiveData.getValue();
if (navController == null) return;
final Bundle bundle = new Bundle();
bundle.putLong("locationId", Long.valueOf(locationId));
bundle.putLong("locationId", Long.parseLong(locationId));
navController.navigate(R.id.action_global_locationFragment, bundle);
}
@ -864,6 +908,18 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
return binding.toolbar;
}
public View getRootView() {
return binding.getRoot();
}
public List<Tab> getCurrentTabs() {
return currentTabs;
}
// public boolean isNavRootInCurrentTabs(@IdRes final int navRootId) {
// return showBottomViewDestinations.stream().anyMatch(id -> id == navRootId);
// }
private void setNavBarDMUnreadCountBadge(final int unseenCount) {
final BadgeDrawable badge = binding.bottomNavView.getOrCreateBadge(R.id.direct_messages_nav_graph);
if (badge == null) return;

View File

@ -10,7 +10,6 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.cursoradapter.widget.CursorAdapter;
import awais.instagrabber.R;
import awais.instagrabber.databinding.ItemSuggestionBinding;
import awais.instagrabber.models.enums.SuggestionType;

View File

@ -0,0 +1,156 @@
package awais.instagrabber.adapters;
import android.view.LayoutInflater;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import androidx.recyclerview.widget.DiffUtil;
import androidx.recyclerview.widget.ListAdapter;
import androidx.recyclerview.widget.RecyclerView;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.stream.Collectors;
import awais.instagrabber.R;
import awais.instagrabber.adapters.viewholder.TabViewHolder;
import awais.instagrabber.databinding.ItemFavSectionHeaderBinding;
import awais.instagrabber.databinding.ItemTabOrderPrefBinding;
import awais.instagrabber.models.Tab;
import awais.instagrabber.utils.Utils;
public class TabsAdapter extends ListAdapter<TabsAdapter.TabOrHeader, RecyclerView.ViewHolder> {
private static final DiffUtil.ItemCallback<TabOrHeader> DIFF_CALLBACK = new DiffUtil.ItemCallback<TabOrHeader>() {
@Override
public boolean areItemsTheSame(@NonNull final TabOrHeader oldItem, @NonNull final TabOrHeader newItem) {
if (oldItem.isHeader() && newItem.isHeader()) {
return oldItem.header == newItem.header;
}
if (!oldItem.isHeader() && !newItem.isHeader()) {
final Tab oldTab = oldItem.tab;
final Tab newTab = newItem.tab;
return oldTab.getIconResId() == newTab.getIconResId()
&& Objects.equals(oldTab.getTitle(), newTab.getTitle());
}
return false;
}
@Override
public boolean areContentsTheSame(@NonNull final TabOrHeader oldItem, @NonNull final TabOrHeader newItem) {
if (oldItem.isHeader() && newItem.isHeader()) {
return oldItem.header == newItem.header;
}
if (!oldItem.isHeader() && !newItem.isHeader()) {
final Tab oldTab = oldItem.tab;
final Tab newTab = newItem.tab;
return oldTab.getIconResId() == newTab.getIconResId()
&& Objects.equals(oldTab.getTitle(), newTab.getTitle());
}
return false;
}
};
private final TabAdapterCallback tabAdapterCallback;
private List<Tab> current = new ArrayList<>();
private List<Tab> others = new ArrayList<>();
public TabsAdapter(@NonNull final TabAdapterCallback tabAdapterCallback) {
super(DIFF_CALLBACK);
this.tabAdapterCallback = tabAdapterCallback;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) {
final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext());
if (viewType == 1) {
final ItemTabOrderPrefBinding binding = ItemTabOrderPrefBinding.inflate(layoutInflater, parent, false);
return new TabViewHolder(binding, tabAdapterCallback);
}
final ItemFavSectionHeaderBinding headerBinding = ItemFavSectionHeaderBinding.inflate(layoutInflater, parent, false);
return new DirectUsersAdapter.HeaderViewHolder(headerBinding);
}
@Override
public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) {
if (holder instanceof DirectUsersAdapter.HeaderViewHolder) {
((DirectUsersAdapter.HeaderViewHolder) holder).bind(R.string.other_tabs);
return;
}
if (holder instanceof TabViewHolder) {
final Tab tab = getItem(position).tab;
((TabViewHolder) holder).bind(tab, others.contains(tab), current.size() == 5);
}
}
@Override
public int getItemViewType(final int position) {
return getItem(position).isHeader() ? 0 : 1;
}
public void submitList(final List<Tab> current, final List<Tab> others, final Runnable commitCallback) {
final ImmutableList.Builder<TabOrHeader> builder = ImmutableList.builder();
if (current != null) {
builder.addAll(current.stream()
.map(TabOrHeader::new)
.collect(Collectors.toList()));
}
builder.add(new TabOrHeader(R.string.other_tabs));
if (others != null) {
builder.addAll(others.stream()
.map(TabOrHeader::new)
.collect(Collectors.toList()));
}
// Mutable non-null copies
this.current = current != null ? new ArrayList<>(current) : new ArrayList<>();
this.others = others != null ? new ArrayList<>(others) : new ArrayList<>();
submitList(builder.build(), commitCallback);
}
public void submitList(final List<Tab> current, final List<Tab> others) {
submitList(current, others, null);
}
public void moveItem(final int from, final int to) {
final List<Tab> currentCopy = new ArrayList<>(current);
Utils.moveItem(from, to, currentCopy);
submitList(currentCopy, others);
tabAdapterCallback.onOrderChange(currentCopy);
}
public int getCurrentCount() {
return current.size();
}
public static class TabOrHeader {
Tab tab;
int header;
public TabOrHeader(final Tab tab) {
this.tab = tab;
}
public TabOrHeader(@StringRes final int header) {
this.header = header;
}
boolean isHeader() {
return header != 0;
}
}
public interface TabAdapterCallback {
void onStartDrag(TabViewHolder viewHolder);
void onOrderChange(List<Tab> newOrderTabs);
void onAdd(Tab tab);
void onRemove(Tab tab);
}
}

View File

@ -0,0 +1,88 @@
package awais.instagrabber.adapters.viewholder;
import android.annotation.SuppressLint;
import android.content.res.ColorStateList;
import android.graphics.drawable.Drawable;
import android.view.MotionEvent;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.core.content.ContextCompat;
import androidx.core.widget.ImageViewCompat;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.color.MaterialColors;
import awais.instagrabber.R;
import awais.instagrabber.adapters.TabsAdapter;
import awais.instagrabber.databinding.ItemTabOrderPrefBinding;
import awais.instagrabber.models.Tab;
public class TabViewHolder extends RecyclerView.ViewHolder {
private final ItemTabOrderPrefBinding binding;
private final TabsAdapter.TabAdapterCallback tabAdapterCallback;
private final int highlightColor;
private final Drawable originalBgColor;
private boolean draggable = true;
@SuppressLint("ClickableViewAccessibility")
public TabViewHolder(@NonNull final ItemTabOrderPrefBinding binding,
@NonNull final TabsAdapter.TabAdapterCallback tabAdapterCallback) {
super(binding.getRoot());
this.binding = binding;
this.tabAdapterCallback = tabAdapterCallback;
highlightColor = MaterialColors.getColor(itemView.getContext(), R.attr.colorControlHighlight, 0);
originalBgColor = itemView.getBackground();
binding.handle.setOnTouchListener((v, event) -> {
if (event.getActionMasked() == MotionEvent.ACTION_DOWN) {
tabAdapterCallback.onStartDrag(this);
}
return true;
});
}
public void bind(@NonNull final Tab tab,
final boolean isInOthers,
final boolean isCurrentFull) {
draggable = !isInOthers;
binding.icon.setImageResource(tab.getIconResId());
binding.title.setText(tab.getTitle());
binding.handle.setVisibility(isInOthers ? View.GONE : View.VISIBLE);
binding.addRemove.setImageResource(isInOthers ? R.drawable.ic_round_add_circle_24
: R.drawable.ic_round_remove_circle_24);
final ColorStateList tintList = ColorStateList.valueOf(ContextCompat.getColor(
itemView.getContext(),
isInOthers ? R.color.green_500
: R.color.red_500));
ImageViewCompat.setImageTintList(binding.addRemove, tintList);
binding.addRemove.setOnClickListener(v -> {
if (isInOthers) {
tabAdapterCallback.onAdd(tab);
return;
}
tabAdapterCallback.onRemove(tab);
});
final boolean enabled = tab.isRemovable()
&& !(isInOthers && isCurrentFull); // All slots are full in current
binding.addRemove.setEnabled(enabled);
binding.addRemove.setAlpha(enabled ? 1 : 0.5F);
}
public boolean isDraggable() {
return draggable;
}
public void setDragging(final boolean isDragging) {
if (isDragging) {
if (highlightColor != 0) {
itemView.setBackgroundColor(highlightColor);
} else {
itemView.setAlpha(0.5F);
}
return;
}
itemView.setAlpha(1);
itemView.setBackground(originalBgColor);
}
}

View File

@ -16,7 +16,6 @@ import awais.instagrabber.databinding.LayoutDmBaseBinding;
import awais.instagrabber.databinding.LayoutDmRavenMediaBinding;
import awais.instagrabber.models.enums.MediaItemType;
import awais.instagrabber.models.enums.RavenMediaViewMode;
import awais.instagrabber.repositories.responses.ImageVersions2;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.directmessages.DirectItem;

View File

@ -47,7 +47,7 @@ public class FavoriteDataSource {
}
public final void insertOrUpdateFavorite(@NonNull final Favorite favorite) {
if (favorite.getId() > 0) {
if (favorite.getId() != 0) {
favoriteDao.updateFavorites(favorite);
return;
}

View File

@ -12,7 +12,6 @@ import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.fragment.app.FragmentActivity;
import androidx.recyclerview.widget.LinearLayoutManager;
import java.util.Collections;
@ -25,8 +24,10 @@ import awais.instagrabber.db.datasources.AccountDataSource;
import awais.instagrabber.db.entities.Account;
import awais.instagrabber.db.repositories.AccountRepository;
import awais.instagrabber.db.repositories.RepositoryCallback;
import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.ProcessPhoenix;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
@ -55,9 +56,14 @@ public class AccountSwitcherDialogFragment extends DialogFragment {
}
CookieUtils.setupCookies(model.getCookie());
settingsHelper.putString(Constants.COOKIE, model.getCookie());
final FragmentActivity activity = getActivity();
if (activity != null) activity.recreate();
dismiss();
// final FragmentActivity activity = getActivity();
// if (activity != null) activity.recreate();
// dismiss();
AppExecutors.getInstance().mainThread().execute(() -> {
final Context context = getContext();
if (context == null) return;
ProcessPhoenix.triggerRebirth(context);
}, 200);
};
private final AccountSwitcherAdapter.OnAccountLongClickListener accountLongClickListener = (model, isCurrent) -> {

View File

@ -0,0 +1,275 @@
package awais.instagrabber.dialogs;
import android.app.Dialog;
import android.content.Context;
import android.graphics.Canvas;
import android.os.Bundle;
import android.util.Pair;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.fragment.app.DialogFragment;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.stream.Collectors;
import awais.instagrabber.R;
import awais.instagrabber.adapters.DirectUsersAdapter;
import awais.instagrabber.adapters.TabsAdapter;
import awais.instagrabber.adapters.viewholder.TabViewHolder;
import awais.instagrabber.fragments.settings.PreferenceKeys;
import awais.instagrabber.models.Tab;
import awais.instagrabber.utils.Utils;
import static androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_DRAG;
import static androidx.recyclerview.widget.ItemTouchHelper.DOWN;
import static androidx.recyclerview.widget.ItemTouchHelper.UP;
public class TabOrderPreferenceDialogFragment extends DialogFragment {
private Callback callback;
private Context context;
private List<Tab> tabsInPref;
private ItemTouchHelper itemTouchHelper;
private AlertDialog dialog;
private List<Tab> newOrderTabs;
private List<Tab> newOtherTabs;
private final TabsAdapter.TabAdapterCallback tabAdapterCallback = new TabsAdapter.TabAdapterCallback() {
@Override
public void onStartDrag(final TabViewHolder viewHolder) {
if (itemTouchHelper == null || viewHolder == null) return;
itemTouchHelper.startDrag(viewHolder);
}
@Override
public void onOrderChange(final List<Tab> newOrderTabs) {
if (newOrderTabs == null || tabsInPref == null || dialog == null) return;
TabOrderPreferenceDialogFragment.this.newOrderTabs = newOrderTabs;
setSaveButtonState(newOrderTabs);
}
@Override
public void onAdd(final Tab tab) {
// Add this tab to newOrderTabs
newOrderTabs = ImmutableList.<Tab>builder()
.addAll(newOrderTabs)
.add(tab)
.build();
// Remove this tab from newOtherTabs
if (newOtherTabs != null) {
newOtherTabs = newOtherTabs.stream()
.filter(t -> !t.equals(tab))
.collect(Collectors.toList());
}
setSaveButtonState(newOrderTabs);
// submit these tab lists to adapter
if (adapter == null) return;
adapter.submitList(newOrderTabs, newOtherTabs, () -> list.postDelayed(() -> adapter.notifyDataSetChanged(), 300));
}
@Override
public void onRemove(final Tab tab) {
// Remove this tab from newOrderTabs
newOrderTabs = newOrderTabs.stream()
.filter(t -> !t.equals(tab))
.collect(Collectors.toList());
// Add this tab to newOtherTabs
if (newOtherTabs != null) {
newOtherTabs = ImmutableList.<Tab>builder()
.addAll(newOtherTabs)
.add(tab)
.build();
}
setSaveButtonState(newOrderTabs);
// submit these tab lists to adapter
if (adapter == null) return;
adapter.submitList(newOrderTabs, newOtherTabs, () -> list.postDelayed(() -> {
adapter.notifyDataSetChanged();
if (tab.getNavigationRootId() == R.id.direct_messages_nav_graph) {
final ConfirmDialogFragment dialogFragment = ConfirmDialogFragment.newInstance(
111, 0, R.string.dm_remove_warning, R.string.ok, 0, 0
);
dialogFragment.show(getChildFragmentManager(), "dm_warning_dialog");
}
}, 500));
}
private void setSaveButtonState(final List<Tab> newOrderTabs) {
dialog.getButton(AlertDialog.BUTTON_POSITIVE)
.setEnabled(!newOrderTabs.equals(tabsInPref));
}
};
private final SimpleCallback simpleCallback = new SimpleCallback(UP | DOWN, 0) {
private int movePosition = RecyclerView.NO_POSITION;
@Override
public int getMovementFlags(@NonNull final RecyclerView recyclerView, @NonNull final RecyclerView.ViewHolder viewHolder) {
if (viewHolder instanceof DirectUsersAdapter.HeaderViewHolder) return 0;
if (viewHolder instanceof TabViewHolder && !((TabViewHolder) viewHolder).isDraggable()) return 0;
return super.getMovementFlags(recyclerView, viewHolder);
}
@Override
public void onChildDraw(@NonNull final Canvas c,
@NonNull final RecyclerView recyclerView,
@NonNull final RecyclerView.ViewHolder viewHolder,
final float dX,
final float dY,
final int actionState,
final boolean isCurrentlyActive) {
if (actionState != ACTION_STATE_DRAG) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
return;
}
final TabsAdapter adapter = (TabsAdapter) recyclerView.getAdapter();
if (adapter == null) {
super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive);
return;
}
// Do not allow dragging into 'Other tabs' category
float edgeY = dY;
final int lastPosition = adapter.getCurrentCount() - 1;
final View view = viewHolder.itemView;
// final int topEdge = recyclerView.getTop();
final int bottomEdge = view.getHeight() * adapter.getCurrentCount() - view.getBottom();
// if (movePosition == 0 && dY < topEdge) {
// edgeY = topEdge;
// } else
if (movePosition >= lastPosition && dY >= bottomEdge) {
edgeY = bottomEdge;
}
super.onChildDraw(c, recyclerView, viewHolder, dX, edgeY, actionState, isCurrentlyActive);
}
@Override
public boolean onMove(@NonNull final RecyclerView recyclerView,
@NonNull final RecyclerView.ViewHolder viewHolder,
@NonNull final RecyclerView.ViewHolder target) {
final TabsAdapter adapter = (TabsAdapter) recyclerView.getAdapter();
if (adapter == null) return false;
movePosition = target.getBindingAdapterPosition();
if (movePosition >= adapter.getCurrentCount()) {
return false;
}
final int from = viewHolder.getBindingAdapterPosition();
final int to = target.getBindingAdapterPosition();
adapter.moveItem(from, to);
// adapter.notifyItemMoved(from, to);
return true;
}
@Override
public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder, final int direction) {}
@Override
public void onSelectedChanged(@Nullable final RecyclerView.ViewHolder viewHolder, final int actionState) {
super.onSelectedChanged(viewHolder, actionState);
if (!(viewHolder instanceof TabViewHolder)) {
movePosition = RecyclerView.NO_POSITION;
return;
}
if (actionState == ACTION_STATE_DRAG) {
((TabViewHolder) viewHolder).setDragging(true);
movePosition = viewHolder.getBindingAdapterPosition();
}
}
@Override
public void clearView(@NonNull final RecyclerView recyclerView,
@NonNull final RecyclerView.ViewHolder viewHolder) {
super.clearView(recyclerView, viewHolder);
((TabViewHolder) viewHolder).setDragging(false);
movePosition = RecyclerView.NO_POSITION;
}
};
private TabsAdapter adapter;
private RecyclerView list;
public static TabOrderPreferenceDialogFragment newInstance() {
final Bundle args = new Bundle();
final TabOrderPreferenceDialogFragment fragment = new TabOrderPreferenceDialogFragment();
fragment.setArguments(args);
return fragment;
}
public TabOrderPreferenceDialogFragment() {}
@Override
public void onAttach(@NonNull final Context context) {
super.onAttach(context);
try {
callback = (Callback) getParentFragment();
} catch (ClassCastException e) {
// throw new ClassCastException("Calling fragment must implement TabOrderPreferenceDialogFragment.Callback interface");
}
this.context = context;
}
@NonNull
@Override
public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) {
return new MaterialAlertDialogBuilder(context)
.setView(createView())
.setPositiveButton(R.string.save, (d, w) -> {
final boolean hasChanged = newOrderTabs != null && !newOrderTabs.equals(tabsInPref);
if (hasChanged) {
saveNewOrder();
}
if (callback == null) return;
callback.onSave(hasChanged);
})
.setNegativeButton(R.string.cancel, (dialog, which) -> {
if (callback == null) return;
callback.onCancel();
})
.create();
}
private void saveNewOrder() {
final String newOrderString = newOrderTabs.stream()
.map(Tab::getGraphName)
.collect(Collectors.joining(","));
Utils.settingsHelper.putString(PreferenceKeys.PREF_TAB_ORDER, newOrderString);
}
@Override
public void onStart() {
super.onStart();
final Dialog dialog = getDialog();
if (!(dialog instanceof AlertDialog)) return;
this.dialog = (AlertDialog) dialog;
this.dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false);
}
@NonNull
private View createView() {
list = new RecyclerView(context);
list.setLayoutManager(new LinearLayoutManager(context));
itemTouchHelper = new ItemTouchHelper(simpleCallback);
itemTouchHelper.attachToRecyclerView(list);
adapter = new TabsAdapter(tabAdapterCallback);
list.setAdapter(adapter);
final Pair<List<Tab>, List<Tab>> navTabListPair = Utils.getNavTabList(context);
tabsInPref = navTabListPair.first;
// initially set newOrderTabs and newOtherTabs same as current tabs
newOrderTabs = navTabListPair.first;
newOtherTabs = navTabListPair.second;
adapter.submitList(navTabListPair.first, navTabListPair.second);
return list;
}
public interface Callback {
void onSave(final boolean orderHasChanged);
void onCancel();
}
}

View File

@ -65,13 +65,14 @@ import awais.instagrabber.webservices.GraphQLService;
import awais.instagrabber.webservices.LocationService;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService;
//import awaisomereport.LogCollector;
import static androidx.core.content.PermissionChecker.checkSelfPermission;
import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION;
//import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper;
//import awaisomereport.LogCollector;
//import static awais.instagrabber.utils.Utils.logCollector;
public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener {
private static final String TAG = "LocationFragment";
private static final int STORAGE_PERM_REQUEST_CODE = 8020;

View File

@ -31,7 +31,6 @@ import awais.instagrabber.R;
import awais.instagrabber.adapters.NotificationsAdapter;
import awais.instagrabber.adapters.NotificationsAdapter.OnNotificationClickListener;
import awais.instagrabber.databinding.FragmentNotificationsViewerBinding;
import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections;
import awais.instagrabber.models.enums.NotificationType;
import awais.instagrabber.repositories.requests.StoryViewerOptions;
import awais.instagrabber.repositories.responses.FriendshipChangeResponse;
@ -79,8 +78,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
try {
binding.swipeRefreshLayout.setRefreshing(false);
Toast.makeText(getContext(), t.getMessage(), Toast.LENGTH_SHORT).show();
}
catch(Throwable e) {}
} catch (Throwable ignored) {}
}
};
@ -93,10 +91,10 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
@Override
public void onPreviewClick(final Notification model) {
final NotificationImage notificationImage = model.getArgs().getMedia().get(0);
final long mediaId = Long.valueOf(notificationImage.getId().split("_")[0]);
final long mediaId = Long.parseLong(notificationImage.getId().split("_")[0]);
if (model.getType() == NotificationType.RESPONDED_STORY) {
final NavDirections action = NotificationsViewerFragmentDirections
.actionNotificationsViewerFragmentToStoryViewerFragment(
.actionNotificationsToStory(
StoryViewerOptions.forStory(
mediaId,
model.getArgs().getUsername()));
@ -278,8 +276,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe
}
private void openProfile(final String username) {
final NavDirections action = MorePreferencesFragmentDirections
.actionGlobalProfileFragment("@" + username);
final NavDirections action = NotificationsViewerFragmentDirections.actionGlobalProfileFragment("@" + username);
NavHostFragment.findNavController(this).navigate(action);
}
}

View File

@ -2,14 +2,12 @@ package awais.instagrabber.fragments;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.graphics.drawable.Animatable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.util.Log;
import android.util.Pair;
import android.view.GestureDetector;
import android.view.Gravity;
import android.view.LayoutInflater;
@ -72,7 +70,6 @@ import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.adapters.StoriesAdapter;
import awais.instagrabber.asyncs.CreateThreadAction;
import awais.instagrabber.asyncs.PostFetcher;
import awais.instagrabber.customviews.helpers.SwipeGestureListener;
import awais.instagrabber.databinding.FragmentStoryViewerBinding;
import awais.instagrabber.fragments.main.ProfileFragmentDirections;
@ -105,7 +102,6 @@ import awais.instagrabber.webservices.DirectMessagesService;
import awais.instagrabber.webservices.MediaService;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.StoriesService;
//import awaisomereport.LogCollector;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
@ -113,9 +109,11 @@ import retrofit2.Response;
import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_THRESHOLD;
import static awais.instagrabber.customviews.helpers.SwipeGestureListener.SWIPE_VELOCITY_THRESHOLD;
import static awais.instagrabber.utils.Constants.MARK_AS_SEEN;
//import static awais.instagrabber.utils.Utils.logCollector;
import static awais.instagrabber.utils.Utils.settingsHelper;
//import awaisomereport.LogCollector;
//import static awais.instagrabber.utils.Utils.logCollector;
public class StoryViewerFragment extends Fragment {
private static final String TAG = "StoryViewerFragment";
@ -137,14 +135,14 @@ public class StoryViewerFragment extends Fragment {
private String[] mentions;
private QuizModel quiz;
private SliderModel slider;
private MenuItem menuDownload;
private MenuItem menuDm;
private MenuItem menuDownload, menuDm, menuProfile;
private SimpleExoPlayer player;
// private boolean isHashtag;
// private boolean isLoc;
// private String highlight;
private String actionBarTitle;
private String actionBarTitle, actionBarSubtitle;
private boolean fetching = false, sticking = false, shouldRefresh = true;
private boolean downloadVisible = false, dmVisible = false, profileVisible = true;
private int currentFeedStoryIndex;
private double sliderValue;
private StoriesViewModel storiesViewModel;
@ -195,8 +193,10 @@ public class StoryViewerFragment extends Fragment {
menuInflater.inflate(R.menu.story_menu, menu);
menuDownload = menu.findItem(R.id.action_download);
menuDm = menu.findItem(R.id.action_dms);
menuDownload.setVisible(false);
menuDm.setVisible(false);
menuProfile = menu.findItem(R.id.action_profile);
menuDownload.setVisible(downloadVisible);
menuDm.setVisible(dmVisible);
menuProfile.setVisible(profileVisible);
}
@Override
@ -215,7 +215,8 @@ public class StoryViewerFragment extends Fragment {
else
ActivityCompat.requestPermissions(requireActivity(), DownloadUtils.PERMS, 8020);
return true;
} else if (itemId == R.id.action_dms) {
}
if (itemId == R.id.action_dms) {
final EditText input = new EditText(context);
input.setHint(R.string.reply_hint);
new AlertDialog.Builder(context)
@ -259,6 +260,9 @@ public class StoryViewerFragment extends Fragment {
.show();
return true;
}
if (itemId == R.id.action_profile) {
openProfile("@" + currentStory.getUsername());
}
return false;
}
@ -281,7 +285,9 @@ public class StoryViewerFragment extends Fragment {
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(actionBarTitle);
actionBar.setSubtitle(actionBarSubtitle);
}
setHasOptionsMenu(true);
}
@Override
@ -697,6 +703,10 @@ public class StoryViewerFragment extends Fragment {
lastSlidePos = 0;
if (menuDownload != null) menuDownload.setVisible(false);
if (menuDm != null) menuDm.setVisible(false);
if (menuProfile != null) menuProfile.setVisible(false);
downloadVisible = false;
dmVisible = false;
profileVisible = false;
binding.imageViewer.setController(null);
releasePlayer();
String currentStoryMediaId = null;
@ -846,7 +856,6 @@ public class StoryViewerFragment extends Fragment {
final MediaItemType itemType = currentStory.getItemType();
if (menuDownload != null) menuDownload.setVisible(false);
url = itemType == MediaItemType.MEDIA_TYPE_IMAGE ? currentStory.getStoryUrl() : currentStory.getVideoUrl();
if (itemType != MediaItemType.MEDIA_TYPE_LIVE) {
@ -900,9 +909,10 @@ public class StoryViewerFragment extends Fragment {
else setupImage();
final ActionBar actionBar = fragmentActivity.getSupportActionBar();
actionBarSubtitle = Utils.datetimeParser.format(new Date(currentStory.getTimestamp() * 1000L));
if (actionBar != null) {
try {
actionBar.setSubtitle(Utils.datetimeParser.format(new Date(currentStory.getTimestamp() * 1000L)));
actionBar.setSubtitle(actionBarSubtitle);
} catch (Exception e) {
Log.e(TAG, "refreshStory: ", e);
}
@ -948,11 +958,17 @@ public class StoryViewerFragment extends Fragment {
final ImageInfo imageInfo,
final Animatable animatable) {
if (menuDownload != null) {
downloadVisible = true;
menuDownload.setVisible(true);
}
if (currentStory.canReply() && menuDm != null) {
dmVisible = true;
menuDm.setVisible(true);
}
if (!TextUtils.isEmpty(currentStory.getUsername())) {
profileVisible = true;
menuProfile.setVisible(true);
}
binding.progressView.setVisibility(View.GONE);
}
})
@ -982,9 +998,18 @@ public class StoryViewerFragment extends Fragment {
@Nullable final MediaSource.MediaPeriodId mediaPeriodId,
@NonNull final LoadEventInfo loadEventInfo,
@NonNull final MediaLoadData mediaLoadData) {
if (menuDownload != null) menuDownload.setVisible(true);
if (currentStory.canReply() && menuDm != null)
if (menuDownload != null) {
downloadVisible = true;
menuDownload.setVisible(true);
}
if (currentStory.canReply() && menuDm != null) {
dmVisible = true;
menuDm.setVisible(true);
}
if (!TextUtils.isEmpty(currentStory.getUsername()) && menuProfile != null) {
profileVisible = true;
menuProfile.setVisible(true);
}
binding.progressView.setVisibility(View.GONE);
}
@ -993,9 +1018,18 @@ public class StoryViewerFragment extends Fragment {
@Nullable final MediaSource.MediaPeriodId mediaPeriodId,
@NonNull final LoadEventInfo loadEventInfo,
@NonNull final MediaLoadData mediaLoadData) {
if (menuDownload != null) menuDownload.setVisible(true);
if (currentStory.canReply() && menuDm != null)
if (menuDownload != null) {
downloadVisible = true;
menuDownload.setVisible(true);
}
if (currentStory.canReply() && menuDm != null) {
dmVisible = true;
menuDm.setVisible(true);
}
if (!TextUtils.isEmpty(currentStory.getUsername()) && menuProfile != null) {
profileVisible = true;
menuProfile.setVisible(true);
}
binding.progressView.setVisibility(View.VISIBLE);
}
@ -1014,8 +1048,18 @@ public class StoryViewerFragment extends Fragment {
@NonNull final MediaLoadData mediaLoadData,
@NonNull final IOException error,
final boolean wasCanceled) {
if (menuDownload != null) menuDownload.setVisible(false);
if (menuDm != null) menuDm.setVisible(false);
if (menuDownload != null) {
downloadVisible = false;
menuDownload.setVisible(false);
}
if (menuDm != null) {
dmVisible = false;
menuDm.setVisible(false);
}
if (menuProfile != null) {
profileVisible = false;
menuProfile.setVisible(false);
}
binding.progressView.setVisibility(View.GONE);
}
});

View File

@ -83,9 +83,12 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
new FeedStoriesAdapter.OnFeedStoryClickListener() {
@Override
public void onFeedStoryClick(FeedStoryModel model, int position) {
final NavDirections action = FeedFragmentDirections
.actionFeedFragmentToStoryViewerFragment(StoryViewerOptions.forFeedStoryPosition(position));
NavHostFragment.findNavController(FeedFragment.this).navigate(action);
final NavController navController = NavHostFragment.findNavController(FeedFragment.this);
if (isSafeToNavigate(navController)) {
final NavDirections action = FeedFragmentDirections
.actionFeedFragmentToStoryViewerFragment(StoryViewerOptions.forFeedStoryPosition(position));
navController.navigate(action);
}
}
@Override
@ -437,4 +440,9 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre
binding.feedRecyclerView.smoothScrollToPosition(0);
// binding.storiesContainer.setExpanded(true);
}
private boolean isSafeToNavigate(final NavController navController) {
return navController.getCurrentDestination() != null
&& navController.getCurrentDestination().getId() == R.id.feedFragment;
}
}

View File

@ -306,6 +306,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
private AccountRepository accountRepository;
private FavoriteRepository favoriteRepository;
private AppStateViewModel appStateViewModel;
private boolean disableDm = false;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
@ -321,8 +322,10 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
mediaService = isLoggedIn ? MediaService.getInstance(null, null, 0) : null;
userService = isLoggedIn ? UserService.getInstance() : null;
graphQLService = isLoggedIn ? null : GraphQLService.getInstance();
accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(getContext()));
favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext()));
final Context context = getContext();
if (context == null) return;
accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(context));
favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(context));
appStateViewModel = new ViewModelProvider(fragmentActivity).get(AppStateViewModel.class);
setHasOptionsMenu(true);
}
@ -577,6 +580,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
}
private void init() {
disableDm = !Utils.isNavRootInCurrentTabs("direct_messages_nav_graph");
if (getArguments() != null) {
final ProfileFragmentArgs fragmentArgs = ProfileFragmentArgs.fromBundle(getArguments());
username = fragmentArgs.getUsername();
@ -938,7 +942,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
}
profileDetailsBinding.btnSaved.setVisibility(View.GONE);
profileDetailsBinding.btnLiked.setVisibility(View.GONE);
profileDetailsBinding.btnDM.setVisibility(View.VISIBLE);
profileDetailsBinding.btnDM.setVisibility(disableDm ? View.GONE : View.VISIBLE);
profileDetailsBinding.btnFollow.setVisibility(View.VISIBLE);
final Context context = getContext();
if (context == null) return;
@ -1116,23 +1120,25 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe
PostItemType.TAGGED);
NavHostFragment.findNavController(this).navigate(action);
});
profileDetailsBinding.btnDM.setOnClickListener(v -> {
profileDetailsBinding.btnDM.setEnabled(false);
new CreateThreadAction(cookie, profileModel.getPk(), thread -> {
if (thread == null) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
if (!disableDm) {
profileDetailsBinding.btnDM.setOnClickListener(v -> {
profileDetailsBinding.btnDM.setEnabled(false);
new CreateThreadAction(cookie, profileModel.getPk(), thread -> {
if (thread == null) {
Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show();
profileDetailsBinding.btnDM.setEnabled(true);
return;
}
final InboxManager inboxManager = DirectMessagesManager.getInstance().getInboxManager();
if (!inboxManager.containsThread(thread.getThreadId())) {
thread.setTemp(true);
inboxManager.addThread(thread, 0);
}
fragmentActivity.navigateToThread(thread.getThreadId(), profileModel.getUsername());
profileDetailsBinding.btnDM.setEnabled(true);
return;
}
final InboxManager inboxManager = DirectMessagesManager.getInstance().getInboxManager();
if (!inboxManager.containsThread(thread.getThreadId())) {
thread.setTemp(true);
inboxManager.addThread(thread, 0);
}
fragmentActivity.navigateToThread(thread.getThreadId(), profileModel.getUsername());
profileDetailsBinding.btnDM.setEnabled(true);
}).execute();
});
}).execute();
});
}
profileDetailsBinding.mainProfileImage.setOnClickListener(v -> {
if (!hasStories) {
// show profile pic

View File

@ -38,6 +38,7 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment {
if (context == null) return;
screen.addPreference(getDownloadUserFolderPreference(context));
screen.addPreference(getSaveToCustomFolderPreference(context));
screen.addPreference(getPrependUsernameToFilenamePreference(context));
}
private Preference getDownloadUserFolderPreference(@NonNull final Context context) {
@ -84,6 +85,14 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment {
// Log.d(TAG, "onActivityResult: " + root);
}
private Preference getPrependUsernameToFilenamePreference(@NonNull final Context context) {
final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
preference.setKey(Constants.DOWNLOAD_PREPEND_USER_NAME);
preference.setTitle(R.string.download_prepend_username);
preference.setIconSpaceReserved(false);
return preference;
}
public static class SaveToCustomFolderPreference extends Preference {
private AppCompatTextView customPathTextView;
private final OnSelectFolderButtonClickListener onSelectFolderButtonClickListener;

View File

@ -1,7 +1,7 @@
package awais.instagrabber.fragments.settings;
import android.content.Context;
import android.content.res.TypedArray;
import android.util.Pair;
import androidx.annotation.NonNull;
import androidx.preference.ListPreference;
@ -12,13 +12,17 @@ import androidx.preference.SwitchPreferenceCompat;
import java.util.List;
import awais.instagrabber.R;
import awais.instagrabber.dialogs.ConfirmDialogFragment;
import awais.instagrabber.dialogs.TabOrderPreferenceDialogFragment;
import awais.instagrabber.models.Tab;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class GeneralPreferencesFragment extends BasePreferencesFragment {
public class GeneralPreferencesFragment extends BasePreferencesFragment implements TabOrderPreferenceDialogFragment.Callback {
@Override
void setupPreferenceScreen(final PreferenceScreen screen) {
@ -28,12 +32,14 @@ public class GeneralPreferencesFragment extends BasePreferencesFragment {
final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0;
if (isLoggedIn) {
screen.addPreference(getDefaultTabPreference(context));
screen.addPreference(getTabOrderPreference(context));
}
screen.addPreference(getUpdateCheckPreference(context));
screen.addPreference(getFlagSecurePreference(context));
final List<Preference> preferences = FlavorSettings.getInstance().getPreferences(context,
getChildFragmentManager(),
SettingCategory.GENERAL);
final List<Preference> preferences = FlavorSettings.getInstance()
.getPreferences(context,
getChildFragmentManager(),
SettingCategory.GENERAL);
if (preferences != null) {
for (final Preference preference : preferences) {
screen.addPreference(preference);
@ -44,24 +50,36 @@ public class GeneralPreferencesFragment extends BasePreferencesFragment {
private Preference getDefaultTabPreference(@NonNull final Context context) {
final ListPreference preference = new ListPreference(context);
preference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance());
final TypedArray mainNavIds = getResources().obtainTypedArray(R.array.main_nav_ids);
final int length = mainNavIds.length();
final String[] values = new String[length];
for (int i = 0; i < length; i++) {
final int resourceId = mainNavIds.getResourceId(i, -1);
if (resourceId < 0) continue;
values[i] = getResources().getResourceEntryName(resourceId);
}
mainNavIds.recycle();
final Pair<List<Tab>, List<Tab>> listPair = Utils.getNavTabList(context);
final List<Tab> tabs = listPair.first;
final String[] titles = tabs.stream()
.map(Tab::getTitle)
.toArray(String[]::new);
final String[] navGraphFileNames = tabs.stream()
.map(Tab::getGraphName)
.toArray(String[]::new);
preference.setKey(Constants.DEFAULT_TAB);
preference.setTitle(R.string.pref_start_screen);
preference.setDialogTitle(R.string.pref_start_screen);
preference.setEntries(R.array.main_nav_ids_values);
preference.setEntryValues(values);
preference.setEntries(titles);
preference.setEntryValues(navGraphFileNames);
preference.setIconSpaceReserved(false);
return preference;
}
@NonNull
private Preference getTabOrderPreference(@NonNull final Context context) {
final Preference preference = new Preference(context);
preference.setTitle(R.string.tab_order);
preference.setIconSpaceReserved(false);
preference.setOnPreferenceClickListener(preference1 -> {
final TabOrderPreferenceDialogFragment dialogFragment = TabOrderPreferenceDialogFragment.newInstance();
dialogFragment.show(getChildFragmentManager(), "tab_order_dialog");
return true;
});
return preference;
}
private Preference getUpdateCheckPreference(@NonNull final Context context) {
final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
preference.setKey(Constants.CHECK_UPDATES);
@ -82,4 +100,22 @@ public class GeneralPreferencesFragment extends BasePreferencesFragment {
return true;
});
}
@Override
public void onSave(final boolean orderHasChanged) {
if (!orderHasChanged) return;
final ConfirmDialogFragment dialogFragment = ConfirmDialogFragment.newInstance(
111,
0,
R.string.tab_order_start_next_launch,
R.string.ok,
0,
0);
dialogFragment.show(getChildFragmentManager(), "tab_order_set_dialog");
}
@Override
public void onCancel() {
}
}

View File

@ -11,8 +11,6 @@ import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.fragment.app.FragmentActivity;
import androidx.fragment.app.FragmentManager;
import androidx.navigation.NavController;
import androidx.navigation.NavDirections;
@ -27,6 +25,7 @@ import java.util.List;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.activities.Login;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.databinding.PrefAccountSwitcherBinding;
import awais.instagrabber.db.datasources.AccountDataSource;
import awais.instagrabber.db.entities.Account;
@ -34,10 +33,13 @@ import awais.instagrabber.db.repositories.AccountRepository;
import awais.instagrabber.db.repositories.RepositoryCallback;
import awais.instagrabber.dialogs.AccountSwitcherDialogFragment;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.utils.AppExecutors;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.CookieUtils;
import awais.instagrabber.utils.FlavorTown;
import awais.instagrabber.utils.ProcessPhoenix;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.webservices.ServiceCallback;
import awais.instagrabber.webservices.UserService;
@ -55,8 +57,10 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
void setupPreferenceScreen(final PreferenceScreen screen) {
final String cookie = settingsHelper.getString(Constants.COOKIE);
final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0;
final MainActivity activity = (MainActivity) getActivity();
// screen.addPreference(new MoreHeaderPreference(getContext()));
final Context context = getContext();
final Resources resources = context.getResources();
if (context == null) return;
accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(context));
final PreferenceCategory accountCategory = new PreferenceCategory(context);
@ -67,11 +71,15 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
accountCategory.setSummary(R.string.account_hint);
accountCategory.addPreference(getAccountSwitcherPreference(cookie, context));
accountCategory.addPreference(getPreference(R.string.logout, R.string.logout_summary, R.drawable.ic_logout_24, preference -> {
if (getContext() == null) return false;
final Context context1 = getContext();
if (context1 == null) return false;
CookieUtils.setupCookies("LOGOUT");
shouldRecreate();
Toast.makeText(context, R.string.logout_success, Toast.LENGTH_SHORT).show();
// shouldRecreate();
Toast.makeText(context1, R.string.logout_success, Toast.LENGTH_SHORT).show();
settingsHelper.putString(Constants.COOKIE, "");
AppExecutors.getInstance().mainThread().execute(() -> {
ProcessPhoenix.triggerRebirth(context1);
}, 200);
return true;
}));
}
@ -99,9 +107,14 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
CookieUtils.removeAllAccounts(context, new RepositoryCallback<Void>() {
@Override
public void onSuccess(final Void result) {
shouldRecreate();
Toast.makeText(context, R.string.logout_success, Toast.LENGTH_SHORT).show();
// shouldRecreate();
final Context context1 = getContext();
if (context1 == null) return;
Toast.makeText(context1, R.string.logout_success, Toast.LENGTH_SHORT).show();
settingsHelper.putString(Constants.COOKIE, "");
AppExecutors.getInstance().mainThread().execute(() -> {
ProcessPhoenix.triggerRebirth(context1);
}, 200);
}
@Override
@ -135,13 +148,30 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
screen.addPreference(getDivider(context));
final NavController navController = NavHostFragment.findNavController(this);
if (isLoggedIn) {
screen.addPreference(getPreference(R.string.action_notif, R.drawable.ic_not_liked, preference -> {
if (isSafeToNavigate(navController)) {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("notif");
navController.navigate(navDirections);
}
return true;
}));
boolean showActivity = true;
boolean showExplore = false;
if (activity != null) {
showActivity = !Utils.isNavRootInCurrentTabs("notification_viewer_nav_graph");
showExplore = !Utils.isNavRootInCurrentTabs("discover_nav_graph");
}
if (showActivity) {
screen.addPreference(getPreference(R.string.action_notif, R.drawable.ic_not_liked, preference -> {
if (isSafeToNavigate(navController)) {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("notif");
navController.navigate(navDirections);
}
return true;
}));
}
if (showExplore) {
screen.addPreference(getPreference(R.string.title_discover, R.drawable.ic_explore_24, preference -> {
if (isSafeToNavigate(navController)) {
navController.navigate(R.id.discover_nav_graph);
}
return true;
}));
}
screen.addPreference(getPreference(R.string.action_ayml, R.drawable.ic_suggested_users, preference -> {
if (isSafeToNavigate(navController)) {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("ayml");
@ -157,13 +187,21 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
return true;
}));
}
screen.addPreference(getPreference(R.string.title_favorites, R.drawable.ic_star_24, preference -> {
if (isSafeToNavigate(navController)) {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToFavoritesFragment();
navController.navigate(navDirections);
}
return true;
}));
// Check if favorites has been added as a tab. And if so, do not add in this list
boolean showFavorites = true;
if (activity != null) {
showFavorites = !Utils.isNavRootInCurrentTabs("favorites_nav_graph");
}
if (showFavorites) {
screen.addPreference(getPreference(R.string.title_favorites, R.drawable.ic_star_24, preference -> {
if (isSafeToNavigate(navController)) {
final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToFavoritesFragment();
navController.navigate(navDirections);
}
return true;
}));
}
screen.addPreference(getDivider(context));
screen.addPreference(getPreference(R.string.action_settings, R.drawable.ic_outline_settings_24, preference -> {
@ -193,7 +231,8 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")",
-1,
preference -> {
FlavorTown.updateCheck((AppCompatActivity) requireActivity(), true);
if (BuildConfig.isPre) return true;
FlavorTown.updateCheck(activity, true);
return true;
}));
screen.addPreference(getDivider(context));
@ -235,9 +274,14 @@ public class MorePreferencesFragment extends BasePreferencesFragment {
new RepositoryCallback<Account>() {
@Override
public void onSuccess(final Account result) {
final FragmentActivity activity = getActivity();
if (activity == null) return;
activity.recreate();
// final FragmentActivity activity = getActivity();
// if (activity == null) return;
// activity.recreate();
AppExecutors.getInstance().mainThread().execute(() -> {
final Context context = getContext();
if (context == null) return;
ProcessPhoenix.triggerRebirth(context);
}, 200);
}
@Override

View File

@ -6,4 +6,5 @@ public final class PreferenceKeys {
public static final String PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT = "enable_dm_auto_refresh_freq_unit";
public static final String PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER = "enable_dm_auto_refresh_freq_number";
public static final String PREF_ENABLE_SENTRY = "enable_sentry";
public static final String PREF_TAB_ORDER = "tab_order";
}

View File

@ -37,6 +37,14 @@ public class StoriesPreferencesFragment extends BasePreferencesFragment {
return preference;
}
private Preference getHideMutedReelsPreference(@NonNull final Context context) {
final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
preference.setKey(Constants.HIDE_MUTED_REELS);
preference.setTitle(R.string.hide_muted_reels_setting);
preference.setIconSpaceReserved(false);
return preference;
}
private Preference getMarkStoriesSeenPreference(@NonNull final Context context) {
final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context);
preference.setKey(Constants.MARK_AS_SEEN);

View File

@ -0,0 +1,110 @@
package awais.instagrabber.models;
import androidx.annotation.DrawableRes;
import androidx.annotation.IdRes;
import androidx.annotation.NavigationRes;
import androidx.annotation.NonNull;
import java.util.Objects;
public class Tab {
private final int iconResId;
private final String title;
private final boolean removable;
/**
* This is name part of the navigation resource
* eg: @navigation/<b>graphName</b>
*/
private final String graphName;
/**
* This is the actual resource id of the navigation resource (R.navigation.graphName = navigationResId)
*/
private final int navigationResId;
/**
* This is the resource id of the root navigation tag of the navigation resource.
* <p>eg: inside R.navigation.direct_messages_nav_graph, the id of the root tag is R.id.direct_messages_nav_graph.
* <p>So this field would equal to the value of R.id.direct_messages_nav_graph
*/
private final int navigationRootId;
/**
* This is the start destination of the nav graph
*/
private final int startDestinationFragmentId;
public Tab(@DrawableRes final int iconResId,
@NonNull final String title,
final boolean removable,
@NonNull final String graphName,
@NavigationRes final int navigationResId,
@IdRes final int navigationRootId,
@IdRes final int startDestinationFragmentId) {
this.iconResId = iconResId;
this.title = title;
this.removable = removable;
this.graphName = graphName;
this.navigationResId = navigationResId;
this.navigationRootId = navigationRootId;
this.startDestinationFragmentId = startDestinationFragmentId;
}
public int getIconResId() {
return iconResId;
}
public String getTitle() {
return title;
}
public boolean isRemovable() {
return removable;
}
public String getGraphName() {
return graphName;
}
public int getNavigationResId() {
return navigationResId;
}
public int getNavigationRootId() {
return navigationRootId;
}
public int getStartDestinationFragmentId() {
return startDestinationFragmentId;
}
@Override
public boolean equals(final Object o) {
if (this == o) return true;
if (o == null || getClass() != o.getClass()) return false;
final Tab tab = (Tab) o;
return iconResId == tab.iconResId &&
removable == tab.removable &&
navigationResId == tab.navigationResId &&
navigationRootId == tab.navigationRootId &&
startDestinationFragmentId == tab.startDestinationFragmentId &&
Objects.equals(title, tab.title) &&
Objects.equals(graphName, tab.graphName);
}
@Override
public int hashCode() {
return Objects.hash(iconResId, title, removable, graphName, navigationResId, navigationRootId, startDestinationFragmentId);
}
@NonNull
@Override
public String toString() {
return "Tab{" +
"title='" + title + '\'' +
", removable=" + removable +
", graphName='" + graphName + '\'' +
'}';
}
}

View File

@ -3,7 +3,6 @@ package awais.instagrabber.repositories;
import java.util.Map;
import awais.instagrabber.repositories.responses.search.SearchResponse;
import retrofit2.Call;
import retrofit2.http.GET;
import retrofit2.http.QueryMap;

View File

@ -1,7 +1,5 @@
package awais.instagrabber.repositories.responses.notification;
import androidx.annotation.NonNull;
public class NotificationCounts {
private final int commentLikes;
private final int usertags;

View File

@ -2,8 +2,6 @@ package awais.instagrabber.repositories.responses.search;
import java.util.List;
import awais.instagrabber.repositories.responses.User;
public class SearchResponse {
// app
private final List<SearchItem> list;

View File

@ -20,6 +20,7 @@ public final class Constants {
// boolean prefs
public static final String DOWNLOAD_USER_FOLDER = "download_user_folder";
public static final String TOGGLE_KEYWORD_FILTER = "toggle_keyword_filter";
public static final String DOWNLOAD_PREPEND_USER_NAME = "download_user_name";
// deprecated: public static final String BOTTOM_TOOLBAR = "bottom_toolbar";
public static final String FOLDER_SAVE_TO = "saved_to";
public static final String AUTOPLAY_VIDEOS = "autoplay_videos";
@ -28,6 +29,7 @@ public final class Constants {
public static final String CUSTOM_DATE_TIME_FORMAT_ENABLED = "data_time_custom_enabled";
public static final String SWAP_DATE_TIME_FORMAT_ENABLED = "swap_date_time_enabled";
public static final String MARK_AS_SEEN = "mark_as_seen";
public static final String HIDE_MUTED_REELS = "hide_muted_reels";
public static final String DM_MARK_AS_SEEN = "dm_mark_as_seen";
// deprecated: public static final String INSTADP = "instadp";
// deprecated: public static final String STORIESIG = "storiesig";
@ -79,7 +81,6 @@ public final class Constants {
// public static final String SIGNATURE_KEY = "9193488027538fd3450b83b7d05286d4ca9599a0f7eeed90d8c85925698a05dc";
public static final String BREADCRUMB_KEY = "iN4$aGr0m";
public static final int LOGIN_RESULT_CODE = 5000;
public static final String FDROID_SHA1_FINGERPRINT = "C1661EB8FD09F618307E687786D5E5056F65084D";
public static final String SKIPPED_VERSION = "skipped_version";
public static final String DEFAULT_TAB = "default_tab";
public static final String PREF_DARK_THEME = "dark_theme";
@ -113,6 +114,7 @@ public final class Constants {
public static final int SHOW_ACTIVITY_REQUEST_CODE = 1738;
public static final int SHOW_DM_THREAD = 2000;
public static final int DM_SYNC_SERVICE_REQUEST_CODE = 3000;
public static final int GLOBAL_NETWORK_ERROR_DIALOG_REQUEST_CODE = 7777;
public static final String ACTION_SHOW_ACTIVITY = "show_activity";
public static final String ACTION_SHOW_DM_THREAD = "show_dm_thread";

View File

@ -157,12 +157,21 @@ public final class ExportImportUtils {
query,
favoriteType,
favsObject.optString("s"),
favoriteType == FavoriteType.HASHTAG ? null
: favsObject.optString("pic_url"),
favoriteType == FavoriteType.USER ? favsObject.optString("pic_url") : null,
new Date(favsObject.getLong("d")));
// Log.d(TAG, "importJson: favoriteModel: " + favoriteModel);
FavoriteRepository.getInstance(FavoriteDataSource.getInstance(context))
.insertOrUpdateFavorite(favorite, null);
final FavoriteRepository favRepo = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(context));
favRepo.getFavorite(query, favoriteType, new RepositoryCallback<Favorite>() {
@Override
public void onSuccess(final Favorite result) {
// local has priority since it's more frequently updated
}
@Override
public void onDataNotAvailable() {
favRepo.insertOrUpdateFavorite(favorite, null);
}
});
}
}

View File

@ -1,104 +1,72 @@
package awais.instagrabber.utils;
import android.annotation.SuppressLint;
import android.content.ActivityNotFoundException;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.Signature;
import android.content.res.Resources;
import android.net.Uri;
import android.os.AsyncTask;
import android.util.Log;
import android.widget.Toast;
import androidx.annotation.NonNull;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.Objects;
import java.util.concurrent.ThreadLocalRandom;
import javax.security.cert.CertificateException;
import javax.security.cert.X509Certificate;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import awais.instagrabber.databinding.DialogUpdateBinding;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class FlavorTown {
private static final String TAG = "FlavorTown";
private static final char[] HEX_ARRAY = "0123456789ABCDEF".toCharArray();
private static AlertDialog dialog;
private static final UpdateChecker UPDATE_CHECKER = UpdateChecker.getInstance();
private static final Pattern VERSION_NAME_PATTERN = Pattern.compile("v?(\\d+\\.\\d+\\.\\d+)(?:_?)(\\w*)(?:-?)(\\w*)");
private static boolean checking = false;
public static void updateCheck(@NonNull final AppCompatActivity context) {
updateCheck(context, false);
}
@SuppressLint("PackageManagerGetSignatures")
public static void updateCheck(@NonNull final AppCompatActivity context, final boolean force) {
boolean isInstalledFromFdroid = false;
final PackageInfo packageInfo;
try {
packageInfo = context.getPackageManager().getPackageInfo(context.getPackageName(), PackageManager.GET_SIGNATURES);
for (Signature signature : packageInfo.signatures) {
final X509Certificate cert = X509Certificate.getInstance(signature.toByteArray());
final String fingerprint = bytesToHex(MessageDigest.getInstance("SHA-1").digest(cert.getEncoded()));
isInstalledFromFdroid = fingerprint.equals(Constants.FDROID_SHA1_FINGERPRINT);
// Log.d(TAG, "fingerprint:" + fingerprint);
}
} catch (PackageManager.NameNotFoundException | NoSuchAlgorithmException | CertificateException e) {
Log.e(TAG, "Error", e);
}
if (isInstalledFromFdroid) return;
final DialogUpdateBinding binding = DialogUpdateBinding.inflate(context.getLayoutInflater(), null, false);
binding.skipUpdate.setOnCheckedChangeListener((buttonView, isChecked) -> {
if (dialog == null) return;
dialog.getButton(DialogInterface.BUTTON_POSITIVE).setEnabled(!isChecked);
});
Resources res = context.getResources();
new UpdateChecker(version -> {
if (force && version.equals(BuildConfig.VERSION_NAME)) {
Toast.makeText(context, "You're already on the latest version", Toast.LENGTH_SHORT).show();
public static void updateCheck(@NonNull final AppCompatActivity context,
final boolean force) {
if (checking) return;
checking = true;
AppExecutors.getInstance().networkIO().execute(() -> {
final String onlineVersionName = UPDATE_CHECKER.getLatestVersion();
if (onlineVersionName == null) return;
final String onlineVersion = getVersion(onlineVersionName);
final String localVersion = getVersion(BuildConfig.VERSION_NAME);
if (Objects.equals(onlineVersion, localVersion)) {
if (force) {
AppExecutors.getInstance().mainThread().execute(() -> {
final Context applicationContext = context.getApplicationContext();
// Check if app was closed or crashed before reaching here
if (applicationContext == null) return;
// Show toast if version number preference was tapped
Toast.makeText(applicationContext, R.string.on_latest_version, Toast.LENGTH_SHORT).show();
});
}
return;
}
final String skippedVersion = settingsHelper.getString(Constants.SKIPPED_VERSION);
final boolean shouldShowDialog = force || (!version.equals(BuildConfig.VERSION_NAME) && !BuildConfig.DEBUG && !skippedVersion
.equals(version));
final boolean shouldShowDialog = UpdateCheckCommon.shouldShowUpdateDialog(force, onlineVersionName);
if (!shouldShowDialog) return;
dialog = new AlertDialog.Builder(context)
.setTitle(res.getString(R.string.update_available, version))
.setView(binding.getRoot())
.setNeutralButton(R.string.cancel, (dialog, which) -> {
if (binding.skipUpdate.isChecked()) {
settingsHelper.putString(Constants.SKIPPED_VERSION, version);
}
dialog.dismiss();
})
.setPositiveButton(R.string.action_github, (dialog1, which) -> {
try {
context.startActivity(new Intent(Intent.ACTION_VIEW).setData(
Uri.parse("https://github.com/austinhuang0131/instagrabber/releases/latest")));
} catch (final ActivityNotFoundException e) {
// do nothing
}
})
// if we don't show dialog for fdroid users, is the below required?
.setNegativeButton(R.string.action_fdroid, (dialog, which) -> {
try {
context.startActivity(new Intent(Intent.ACTION_VIEW).setData(
Uri.parse("https://f-droid.org/packages/me.austinhuang.instagrabber/")));
} catch (final ActivityNotFoundException e) {
// do nothing
}
})
.show();
}).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
UpdateCheckCommon.showUpdateDialog(context, onlineVersionName, (dialog, which) -> {
UPDATE_CHECKER.onDownload(context);
dialog.dismiss();
});
});
}
private static String getVersion(@NonNull final String versionName) {
final Matcher matcher = VERSION_NAME_PATTERN.matcher(versionName);
if (!matcher.matches()) return versionName;
try {
return matcher.group(1);
} catch (Exception e) {
Log.e(TAG, "getVersion: ", e);
}
return versionName;
}
public static void changelogCheck(@NonNull final Context context) {
@ -121,14 +89,4 @@ public final class FlavorTown {
settingsHelper.putInteger(Constants.PREV_INSTALL_VERSION, BuildConfig.VERSION_CODE);
}
}
public static String bytesToHex(byte[] bytes) {
char[] hexChars = new char[bytes.length * 2];
for (int j = 0; j < bytes.length; j++) {
int v = bytes[j] & 0xFF;
hexChars[j * 2] = HEX_ARRAY[v >>> 4];
hexChars[j * 2 + 1] = HEX_ARRAY[v & 0x0F];
}
return new String(hexChars);
}
}

View File

@ -16,7 +16,7 @@ import java.util.Map;
import awais.instagrabber.models.UploadPhotoOptions;
import awais.instagrabber.models.UploadVideoOptions;
import awais.instagrabber.webservices.AddCookiesInterceptor;
import awais.instagrabber.webservices.interceptors.AddCookiesInterceptor;
import okhttp3.Call;
import okhttp3.Headers;
import okhttp3.MediaType;

View File

@ -0,0 +1,111 @@
/*
* Copyright (C) 2014 Jake Wharton
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package awais.instagrabber.utils;
import android.app.Activity;
import android.app.ActivityManager;
import android.content.Context;
import android.content.Intent;
import android.os.Bundle;
import android.os.Process;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import static android.content.Intent.FLAG_ACTIVITY_CLEAR_TASK;
import static android.content.Intent.FLAG_ACTIVITY_NEW_TASK;
/**
* Process Phoenix facilitates restarting your application process. This should only be used for
* things like fundamental state changes in your debug builds (e.g., changing from staging to
* production).
* <p>
* Trigger process recreation by calling {@link #triggerRebirth} with a {@link Context} instance.
*/
public final class ProcessPhoenix extends Activity {
private static final String KEY_RESTART_INTENTS = "phoenix_restart_intents";
/**
* Call to restart the application process using the {@linkplain Intent#CATEGORY_DEFAULT default}
* activity as an intent.
* <p>
* Behavior of the current process after invoking this method is undefined.
*/
public static void triggerRebirth(Context context) {
triggerRebirth(context, getRestartIntent(context));
}
/**
* Call to restart the application process using the specified intents.
* <p>
* Behavior of the current process after invoking this method is undefined.
*/
public static void triggerRebirth(Context context, Intent... nextIntents) {
Intent intent = new Intent(context, ProcessPhoenix.class);
intent.addFlags(FLAG_ACTIVITY_NEW_TASK); // In case we are called with non-Activity context.
intent.putParcelableArrayListExtra(KEY_RESTART_INTENTS, new ArrayList<>(Arrays.asList(nextIntents)));
context.startActivity(intent);
if (context instanceof Activity) {
((Activity) context).finish();
}
Runtime.getRuntime().exit(0); // Kill kill kill!
}
private static Intent getRestartIntent(Context context) {
String packageName = context.getPackageName();
Intent defaultIntent = context.getPackageManager().getLaunchIntentForPackage(packageName);
if (defaultIntent != null) {
defaultIntent.addFlags(FLAG_ACTIVITY_NEW_TASK | FLAG_ACTIVITY_CLEAR_TASK);
return defaultIntent;
}
throw new IllegalStateException("Unable to determine default activity for "
+ packageName
+ ". Does an activity specify the DEFAULT category in its intent filter?");
}
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
ArrayList<Intent> intents = getIntent().getParcelableArrayListExtra(KEY_RESTART_INTENTS);
startActivities(intents.toArray(new Intent[intents.size()]));
finish();
Runtime.getRuntime().exit(0); // Kill kill kill!
}
/**
* Checks if the current process is a temporary Phoenix Process.
* This can be used to avoid initialisation of unused resources or to prevent running code that
* is not multi-process ready.
*
* @return true if the current process is a temporary Phoenix Process
*/
public static boolean isPhoenixProcess(Context context) {
int currentPid = Process.myPid();
ActivityManager manager = (ActivityManager) context.getSystemService(Context.ACTIVITY_SERVICE);
List<ActivityManager.RunningAppProcessInfo> runningProcesses = manager.getRunningAppProcesses();
if (runningProcesses != null) {
for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) {
if (processInfo.pid == currentPid && processInfo.processName.endsWith(":phoenix")) {
return true;
}
}
}
return false;
}
}

View File

@ -1089,12 +1089,19 @@ public final class ResponseBodyUtils {
if (imageVersions2 == null) return null;
final List<MediaCandidate> candidates = imageVersions2.getCandidates();
if (candidates == null || candidates.isEmpty()) return null;
final List<MediaCandidate> sortedCandidates = candidates
.stream()
final boolean isSquare = Integer.compare(media.getOriginalWidth(), media.getOriginalHeight()) == 0;
final List<MediaCandidate> sortedCandidates = candidates.stream()
.sorted((c1, c2) -> Integer.compare(c2.getWidth(), c1.getWidth()))
// .filter(c -> c.getWidth() < type.getValue())
.collect(Collectors.toList());
final MediaCandidate candidate = sortedCandidates.get(0);
if (sortedCandidates.size() == 1) return sortedCandidates.get(0).getUrl();
final List<MediaCandidate> filteredCandidates = sortedCandidates.stream()
.filter(c ->
c.getWidth() <= media.getOriginalWidth()
&& c.getWidth() <= type.getValue()
&& (isSquare || Integer.compare(c.getWidth(), c.getHeight()) != 0)
)
.collect(Collectors.toList());
final MediaCandidate candidate = filteredCandidates.get(0);
if (candidate == null) return null;
return candidate.getUrl();
}

View File

@ -16,6 +16,7 @@ import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_D
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT;
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_NOTIFICATIONS;
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_SENTRY;
import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_TAB_ORDER;
import static awais.instagrabber.utils.Constants.APP_LANGUAGE;
import static awais.instagrabber.utils.Constants.APP_THEME;
import static awais.instagrabber.utils.Constants.APP_UA;
@ -33,16 +34,18 @@ import static awais.instagrabber.utils.Constants.DATE_TIME_SELECTION;
import static awais.instagrabber.utils.Constants.DEFAULT_TAB;
import static awais.instagrabber.utils.Constants.DEVICE_UUID;
import static awais.instagrabber.utils.Constants.DM_MARK_AS_SEEN;
import static awais.instagrabber.utils.Constants.DOWNLOAD_PREPEND_USER_NAME;
import static awais.instagrabber.utils.Constants.DOWNLOAD_USER_FOLDER;
import static awais.instagrabber.utils.Constants.FLAG_SECURE;
import static awais.instagrabber.utils.Constants.FOLDER_PATH;
import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO;
import static awais.instagrabber.utils.Constants.HIDE_MUTED_REELS;
import static awais.instagrabber.utils.Constants.KEYWORD_FILTERS;
import static awais.instagrabber.utils.Constants.MARK_AS_SEEN;
import static awais.instagrabber.utils.Constants.MUTED_VIDEOS;
import static awais.instagrabber.utils.Constants.PREF_DARK_THEME;
import static awais.instagrabber.utils.Constants.PREF_EMOJI_VARIANTS;
import static awais.instagrabber.utils.Constants.PREF_HASHTAG_POSTS_LAYOUT;
import static awais.instagrabber.utils.Constants.KEYWORD_FILTERS;
import static awais.instagrabber.utils.Constants.PREF_LIGHT_THEME;
import static awais.instagrabber.utils.Constants.PREF_LIKED_POSTS_LAYOUT;
import static awais.instagrabber.utils.Constants.PREF_LOCATION_POSTS_LAYOUT;
@ -155,13 +158,13 @@ public final class SettingsHelper {
CUSTOM_DATE_TIME_FORMAT, DEVICE_UUID, SKIPPED_VERSION, DEFAULT_TAB, PREF_DARK_THEME, PREF_LIGHT_THEME,
PREF_POSTS_LAYOUT, PREF_PROFILE_POSTS_LAYOUT, PREF_TOPIC_POSTS_LAYOUT, PREF_HASHTAG_POSTS_LAYOUT,
PREF_LOCATION_POSTS_LAYOUT, PREF_LIKED_POSTS_LAYOUT, PREF_TAGGED_POSTS_LAYOUT, PREF_SAVED_POSTS_LAYOUT,
STORY_SORT, PREF_EMOJI_VARIANTS, PREF_REACTIONS, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT})
STORY_SORT, PREF_EMOJI_VARIANTS, PREF_REACTIONS, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT, PREF_TAB_ORDER})
public @interface StringSettings {}
@StringDef({DOWNLOAD_USER_FOLDER, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS,
@StringDef({DOWNLOAD_USER_FOLDER, DOWNLOAD_PREPEND_USER_NAME, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS,
SHOW_CAPTIONS, CUSTOM_DATE_TIME_FORMAT_ENABLED, MARK_AS_SEEN, DM_MARK_AS_SEEN, CHECK_ACTIVITY,
CHECK_UPDATES, SWAP_DATE_TIME_FORMAT_ENABLED, PREF_ENABLE_DM_NOTIFICATIONS, PREF_ENABLE_DM_AUTO_REFRESH,
FLAG_SECURE, TOGGLE_KEYWORD_FILTER, PREF_ENABLE_SENTRY})
FLAG_SECURE, TOGGLE_KEYWORD_FILTER, PREF_ENABLE_SENTRY, HIDE_MUTED_REELS})
public @interface BooleanSettings {}
@StringDef({PREV_INSTALL_VERSION, BROWSER_UA_CODE, APP_UA_CODE, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER})

View File

@ -2,8 +2,6 @@ package awais.instagrabber.utils;
import android.content.Context;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.format.DateFormat;
import android.text.format.DateUtils;
import android.text.style.URLSpan;
@ -11,17 +9,12 @@ import android.util.Patterns;
import androidx.annotation.NonNull;
import java.io.UnsupportedEncodingException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Date;
import java.util.List;
import java.util.Locale;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import awais.instagrabber.customviews.CommentMentionClickSpan;
public final class TextUtils {
// extracted from String class

View File

@ -0,0 +1,38 @@
package awais.instagrabber.utils;
import android.content.Context;
import android.content.DialogInterface;
import androidx.annotation.NonNull;
import com.google.android.material.dialog.MaterialAlertDialogBuilder;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.R;
import static awais.instagrabber.utils.Utils.settingsHelper;
public final class UpdateCheckCommon {
public static boolean shouldShowUpdateDialog(final boolean force,
@NonNull final String version) {
final String skippedVersion = settingsHelper.getString(Constants.SKIPPED_VERSION);
return force || (!BuildConfig.DEBUG && !skippedVersion.equals(version));
}
public static void showUpdateDialog(@NonNull final Context context,
@NonNull final String version,
@NonNull final DialogInterface.OnClickListener onDownloadClickListener) {
AppExecutors.getInstance().mainThread().execute(() -> {
new MaterialAlertDialogBuilder(context)
.setTitle(context.getString(R.string.update_available, version))
.setNeutralButton(R.string.skip_update, (dialog, which) -> {
settingsHelper.putString(Constants.SKIPPED_VERSION, version);
dialog.dismiss();
})
.setPositiveButton(R.string.action_download, onDownloadClickListener)
.setNegativeButton(R.string.cancel, null)
.show();
});
}
}

View File

@ -1,58 +0,0 @@
package awais.instagrabber.utils;
import android.os.AsyncTask;
import android.util.Log;
import androidx.annotation.NonNull;
import org.json.JSONObject;
import java.net.HttpURLConnection;
import java.net.URL;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.interfaces.FetchListener;
public final class UpdateChecker extends AsyncTask<Void, Void, Boolean> {
private final FetchListener<String> fetchListener;
private String version;
public UpdateChecker(final FetchListener<String> fetchListener) {
this.fetchListener = fetchListener;
}
@NonNull
@Override
protected Boolean doInBackground(final Void... voids) {
try {
version = "";
HttpURLConnection conn =
(HttpURLConnection) new URL("https://f-droid.org/api/v1/packages/me.austinhuang.instagrabber").openConnection();
conn.setUseCaches(false);
conn.setRequestProperty("User-Agent", "https://Barinsta.AustinHuang.me / mailto:Barinsta@AustinHuang.me");
conn.connect();
final int responseCode = conn.getResponseCode();
if (responseCode == HttpURLConnection.HTTP_OK) {
final JSONObject data = new JSONObject(NetworkUtils.readFromConnection(conn));
if (BuildConfig.VERSION_CODE < data.getInt("suggestedVersionCode")) {
version = data.getJSONArray("packages").getJSONObject(0).getString("versionName");
return true;
}
}
conn.disconnect();
} catch (final Exception e) {
if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e);
}
return false;
}
@Override
protected void onPostExecute(final Boolean result) {
if (result != null && result && fetchListener != null)
fetchListener.onResult("v"+version);
}
}

View File

@ -8,6 +8,7 @@ import android.content.ContentResolver;
import android.content.Context;
import android.content.Intent;
import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
@ -44,6 +45,8 @@ import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat;
import com.google.android.exoplayer2.database.ExoDatabaseProvider;
import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor;
import com.google.android.exoplayer2.upstream.cache.SimpleCache;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Ordering;
import com.google.common.io.Files;
import org.json.JSONObject;
@ -53,18 +56,25 @@ import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.stream.Collectors;
import awais.instagrabber.R;
import awais.instagrabber.fragments.settings.PreferenceKeys;
import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.models.Tab;
import awais.instagrabber.models.enums.FavoriteType;
public final class Utils {
private static final String TAG = "Utils";
private static final int VIDEO_CACHE_MAX_BYTES = 10 * 1024 * 1024;
// public static LogCollector logCollector;
// public static LogCollector logCollector;
public static SettingsHelper settingsHelper;
public static boolean sessionVolumeFull = false;
public static final MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton();
@ -76,6 +86,7 @@ public final class Utils {
private static int actionBarHeight;
public static Handler applicationHandler;
public static String cacheDir;
public static String tabOrderString;
private static int defaultStatusBarColor;
private static Object[] volumes;
@ -375,6 +386,123 @@ public final class Utils {
window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON);
}
public static <T> void moveItem(int sourceIndex, int targetIndex, List<T> list) {
if (sourceIndex <= targetIndex) {
Collections.rotate(list.subList(sourceIndex, targetIndex + 1), -1);
} else {
Collections.rotate(list.subList(targetIndex, sourceIndex + 1), 1);
}
}
private static final List<Integer> NON_REMOVABLE_NAV_ROOT_IDS = ImmutableList.of(R.id.profile_nav_graph, R.id.more_nav_graph);
@NonNull
public static Pair<List<Tab>, List<Tab>> getNavTabList(@NonNull final Context context) {
final Resources resources = context.getResources();
final String[] titleArray = resources.getStringArray(R.array.main_nav_titles);
TypedArray typedArray = resources.obtainTypedArray(R.array.main_nav_graphs);
int length = typedArray.length();
final String[] navGraphNames = new String[length];
final int[] navigationResIds = new int[length];
for (int i = 0; i < length; i++) {
final int resourceId = typedArray.getResourceId(i, 0);
if (resourceId == 0) continue;
navigationResIds[i] = resourceId;
navGraphNames[i] = resources.getResourceEntryName(resourceId);
}
typedArray.recycle();
typedArray = resources.obtainTypedArray(R.array.main_nav_graph_root_ids);
length = typedArray.length();
final int[] navRootIds = new int[length];
for (int i = 0; i < length; i++) {
final int resourceId = typedArray.getResourceId(i, 0);
if (resourceId == 0) continue;
navRootIds[i] = resourceId;
}
typedArray.recycle();
typedArray = resources.obtainTypedArray(R.array.main_nav_drawables);
length = typedArray.length();
final int[] iconIds = new int[length];
for (int i = 0; i < length; i++) {
final int resourceId = typedArray.getResourceId(i, 0);
if (resourceId == 0) continue;
iconIds[i] = resourceId;
}
typedArray.recycle();
typedArray = resources.obtainTypedArray(R.array.main_nav_start_dest_frag_ids);
length = typedArray.length();
final int[] startDestFragIds = new int[length];
for (int i = 0; i < length; i++) {
final int resourceId = typedArray.getResourceId(i, 0);
if (resourceId == 0) continue;
startDestFragIds[i] = resourceId;
}
typedArray.recycle();
final List<String> currentOrderGraphNames = getCurrentOrderOfGraphNamesFromPref(navGraphNames);
if (titleArray.length != iconIds.length || titleArray.length != navGraphNames.length) {
throw new RuntimeException(String.format("Array lengths don't match!: titleArray%s, navGraphNames: %s, iconIds: %s",
Arrays.toString(titleArray), Arrays.toString(navGraphNames), Arrays.toString(iconIds)));
}
final List<Tab> tabs = new ArrayList<>();
final List<Tab> otherTabs = new ArrayList<>(); // Will contain tabs not in current list
for (int i = 0; i < length; i++) {
final String navGraphName = navGraphNames[i];
final int navRootId = navRootIds[i];
final Tab tab = new Tab(iconIds[i],
titleArray[i],
!NON_REMOVABLE_NAV_ROOT_IDS.contains(navRootId),
navGraphName,
navigationResIds[i],
navRootId,
startDestFragIds[i]);
if (!currentOrderGraphNames.contains(navGraphName)) {
otherTabs.add(tab);
continue;
}
tabs.add(tab);
}
Collections.sort(tabs, Ordering.explicit(currentOrderGraphNames).onResultOf(tab -> {
if (tab == null) return null;
return tab.getGraphName();
}));
return new Pair<>(tabs, otherTabs);
}
@NonNull
private static List<String> getCurrentOrderOfGraphNamesFromPref(@NonNull final String[] navGraphNames) {
tabOrderString = settingsHelper.getString(PreferenceKeys.PREF_TAB_ORDER);
final List<String> navGraphNameList = Arrays.asList(navGraphNames);
if (TextUtils.isEmpty(tabOrderString)) {
// Use top 5 entries for default list
final List<String> top5navGraphNames = navGraphNameList.subList(0, 5);
final String newOrderString = android.text.TextUtils.join(",", top5navGraphNames);
Utils.settingsHelper.putString(PreferenceKeys.PREF_TAB_ORDER, newOrderString);
tabOrderString = newOrderString;
return top5navGraphNames;
}
// Make sure that the list from preference does not contain any invalid values
final List<String> orderGraphNames = Arrays.stream(tabOrderString.split(","))
.filter(s -> !TextUtils.isEmpty(s))
.filter(navGraphNameList::contains)
.collect(Collectors.toList());
if (orderGraphNames.isEmpty()) {
// Use top 5 entries for default list
return navGraphNameList.subList(0, 5);
}
return orderGraphNames;
}
public static boolean isNavRootInCurrentTabs(final String navRootString) {
if (navRootString == null || tabOrderString == null) return false;
return tabOrderString.contains(navRootString);
}
public static void scanDocumentFile(@NonNull final Context context,
@NonNull final DocumentFile documentFile,
@NonNull final OnScanCompletedListener callback) {

View File

@ -1,50 +1,8 @@
package awais.instagrabber.webservices;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.File;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.repositories.responses.Caption;
import awais.instagrabber.utils.Utils;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;
public abstract class BaseService {
private static final String TAG = "BaseService";
private Retrofit.Builder builder;
private final int cacheSize = 10 * 1024 * 1024; // 10 MB
private final Cache cache = new Cache(new File(Utils.cacheDir), cacheSize);
Retrofit.Builder getRetrofitBuilder() {
if (builder == null) {
final OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder()
.addInterceptor(new AddCookiesInterceptor())
.followRedirects(false)
.followSslRedirects(false)
.cache(cache);
if (BuildConfig.DEBUG) {
// clientBuilder.addInterceptor(new LoggingInterceptor());
}
final Gson gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapter(Caption.class, new Caption.CaptionDeserializer())
.setLenient()
.create();
builder = new Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.client(clientBuilder.build());
}
return builder;
}
// protected String userBreadcrumb(final int size) {
// final long term = (random(2, 4) * 1000) + size + (random(15, 21) * 1000);
// final float div = (float) size / random(2, 4);

View File

@ -1,5 +1,7 @@
package awais.instagrabber.webservices;
import android.text.TextUtils;
import androidx.annotation.NonNull;
import java.util.HashMap;
@ -14,7 +16,6 @@ import awais.instagrabber.utils.Utils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class CollectionService extends BaseService {
private static final String TAG = "ProfileService";
@ -31,10 +32,9 @@ public class CollectionService extends BaseService {
this.deviceUuid = deviceUuid;
this.csrfToken = csrfToken;
this.userId = userId;
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(CollectionRepository.class);
repository = RetrofitFactory.getInstance()
.getRetrofit()
.create(CollectionRepository.class);
}
public String getCsrfToken() {
@ -66,10 +66,10 @@ public class CollectionService extends BaseService {
form.put("module_name", "feed_saved_add_to_collection");
final List<String> ids;
ids = posts.stream()
.map(Media::getPk)
.filter(Objects::nonNull)
.collect(Collectors.toList());
form.put("added_media_ids", "[" + String.join(",", ids) + "]");
.map(Media::getPk)
.filter(Objects::nonNull)
.collect(Collectors.toList());
form.put("added_media_ids", "[" + TextUtils.join(",", ids) + "]");
changeCollection(collectionId, "edit", form, callback);
}

View File

@ -41,7 +41,6 @@ import awais.instagrabber.repositories.responses.giphy.GiphyGif;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import retrofit2.Call;
import retrofit2.Retrofit;
public class DirectMessagesService extends BaseService {
private static final String TAG = "DiscoverService";
@ -59,10 +58,9 @@ public class DirectMessagesService extends BaseService {
this.csrfToken = csrfToken;
this.userId = userId;
this.deviceUuid = deviceUuid;
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(DirectMessagesRepository.class);
repository = RetrofitFactory.getInstance()
.getRetrofit()
.create(DirectMessagesRepository.class);
}
public String getCsrfToken() {

View File

@ -12,7 +12,6 @@ import awais.instagrabber.utils.TextUtils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class DiscoverService extends BaseService {
@ -23,10 +22,9 @@ public class DiscoverService extends BaseService {
private static DiscoverService instance;
private DiscoverService() {
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(DiscoverRepository.class);
repository = RetrofitFactory.getInstance()
.getRetrofit()
.create(DiscoverRepository.class);
}
public static DiscoverService getInstance() {

View File

@ -12,17 +12,16 @@ import java.util.Map;
import java.util.UUID;
import awais.instagrabber.repositories.FeedRepository;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.repositories.responses.feed.EndOfFeedDemarcator;
import awais.instagrabber.repositories.responses.feed.EndOfFeedGroup;
import awais.instagrabber.repositories.responses.feed.EndOfFeedGroupSet;
import awais.instagrabber.repositories.responses.feed.FeedFetchResponse;
import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.repositories.responses.PostsFetchResponse;
import awais.instagrabber.utils.TextUtils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class FeedService extends BaseService {
private static final String TAG = "FeedService";
@ -32,10 +31,9 @@ public class FeedService extends BaseService {
private static FeedService instance;
private FeedService() {
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(FeedRepository.class);
repository = RetrofitFactory.getInstance()
.getRetrofit()
.create(FeedRepository.class);
}
public static FeedService getInstance() {

View File

@ -25,7 +25,6 @@ import awais.instagrabber.utils.Utils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class FriendshipService extends BaseService {
private static final String TAG = "FriendshipService";
@ -42,10 +41,9 @@ public class FriendshipService extends BaseService {
this.deviceUuid = deviceUuid;
this.csrfToken = csrfToken;
this.userId = userId;
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(FriendshipRepository.class);
repository = RetrofitFactory.getInstance()
.getRetrofit()
.create(FriendshipRepository.class);
}
public String getCsrfToken() {
@ -168,8 +166,8 @@ public class FriendshipService extends BaseService {
form.put("_uuid", deviceUuid);
form.put(story ? "target_reel_author_id" : "target_posts_author_id", String.valueOf(targetUserId));
final Call<FriendshipChangeResponse> request = repository.changeMute(unmute ?
"unmute_posts_or_story_from_follow" :
"mute_posts_or_story_from_follow",
"unmute_posts_or_story_from_follow" :
"mute_posts_or_story_from_follow",
form);
request.enqueue(new Callback<FriendshipChangeResponse>() {
@Override
@ -198,8 +196,8 @@ public class FriendshipService extends BaseService {
if (maxId != null) queryMap.put("max_id", maxId);
final Call<String> request = repository.getList(
targetUserId,
follower ? "followers" : "following",
queryMap);
follower ? "followers" : "following",
queryMap);
request.enqueue(new Callback<String>() {
@Override
public void onResponse(@NonNull final Call<String> call, @NonNull final Response<String> response) {

View File

@ -3,7 +3,6 @@ package awais.instagrabber.webservices;
import awais.instagrabber.repositories.GifRepository;
import awais.instagrabber.repositories.responses.giphy.GiphyGifResponse;
import retrofit2.Call;
import retrofit2.Retrofit;
public class GifService extends BaseService {
@ -12,10 +11,9 @@ public class GifService extends BaseService {
private static GifService instance;
private GifService() {
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(GifRepository.class);
repository = RetrofitFactory.getInstance()
.getRetrofit()
.create(GifRepository.class);
}
public static GifService getInstance() {

View File

@ -29,7 +29,6 @@ import awais.instagrabber.utils.TextUtils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class GraphQLService extends BaseService {
private static final String TAG = "GraphQLService";
@ -40,10 +39,9 @@ public class GraphQLService extends BaseService {
private static GraphQLService instance;
private GraphQLService() {
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://www.instagram.com")
.build();
repository = retrofit.create(GraphQLRepository.class);
repository = RetrofitFactory.getInstance()
.getRetrofitWeb()
.create(GraphQLRepository.class);
}
public static GraphQLService getInstance() {

View File

@ -13,7 +13,6 @@ import awais.instagrabber.utils.TextUtils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class LocationService extends BaseService {
private static final String TAG = "LocationService";
@ -23,10 +22,9 @@ public class LocationService extends BaseService {
private static LocationService instance;
private LocationService() {
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(LocationRepository.class);
repository = RetrofitFactory.getInstance()
.getRetrofit()
.create(LocationRepository.class);
}
public static LocationService getInstance() {

View File

@ -31,7 +31,6 @@ import awais.instagrabber.utils.Utils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class MediaService extends BaseService {
private static final String TAG = "MediaService";
@ -51,10 +50,9 @@ public class MediaService extends BaseService {
this.deviceUuid = deviceUuid;
this.csrfToken = csrfToken;
this.userId = userId;
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(MediaRepository.class);
repository = RetrofitFactory.getInstance()
.getRetrofit()
.create(MediaRepository.class);
}
public String getCsrfToken() {

View File

@ -12,17 +12,16 @@ import java.util.stream.Collectors;
import awais.instagrabber.repositories.NewsRepository;
import awais.instagrabber.repositories.responses.AymlResponse;
import awais.instagrabber.repositories.responses.AymlUser;
import awais.instagrabber.repositories.responses.notification.NotificationCounts;
import awais.instagrabber.repositories.responses.UserSearchResponse;
import awais.instagrabber.repositories.responses.NewsInboxResponse;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.UserSearchResponse;
import awais.instagrabber.repositories.responses.notification.Notification;
import awais.instagrabber.repositories.responses.notification.NotificationArgs;
import awais.instagrabber.repositories.responses.User;
import awais.instagrabber.repositories.responses.notification.NotificationCounts;
import awais.instagrabber.utils.Constants;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class NewsService extends BaseService {
private static final String TAG = "NewsService";
@ -32,10 +31,9 @@ public class NewsService extends BaseService {
private static NewsService instance;
private NewsService() {
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(NewsRepository.class);
repository = RetrofitFactory.getInstance()
.getRetrofit()
.create(NewsRepository.class);
}
public static NewsService getInstance() {
@ -56,9 +54,11 @@ public class NewsService extends BaseService {
callback.onSuccess(null);
return;
}
final List<Notification> result = new ArrayList<>();
result.addAll(body.getNewStories());
result.addAll(body.getOldStories());
final List<Notification> result = new ArrayList<Notification>();
final List<Notification> newStories = body.getNewStories();
if (newStories != null) result.addAll(newStories);
final List<Notification> oldStories = body.getOldStories();
if (oldStories != null) result.addAll(oldStories);
callback.onSuccess(result);
}
@ -120,7 +120,8 @@ public class NewsService extends BaseService {
aymlUsers.addAll(oldSuggestions);
}
final List<Notification> newsItems = aymlUsers.stream()
final List<Notification> newsItems = aymlUsers
.stream()
.map(i -> {
final User u = i.getUser();
return new Notification(
@ -162,7 +163,9 @@ public class NewsService extends BaseService {
return;
}
final List<Notification> newsItems = body.getUsers().stream()
final List<Notification> newsItems = body
.getUsers()
.stream()
.map(u -> {
return new Notification(
new NotificationArgs(

View File

@ -23,7 +23,6 @@ import awais.instagrabber.utils.Utils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class ProfileService extends BaseService {
private static final String TAG = "ProfileService";
@ -33,10 +32,9 @@ public class ProfileService extends BaseService {
private static ProfileService instance;
private ProfileService() {
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(ProfileRepository.class);
repository = RetrofitFactory.getInstance()
.getRetrofit()
.create(ProfileRepository.class);
}
public static ProfileService getInstance() {
@ -104,9 +102,9 @@ public class ProfileService extends BaseService {
posts = Collections.emptyList();
} else {
posts = items.stream()
.map(WrappedMedia::getMedia)
.filter(Objects::nonNull)
.collect(Collectors.toList());
.map(WrappedMedia::getMedia)
.filter(Objects::nonNull)
.collect(Collectors.toList());
}
callback.onSuccess(new PostsFetchResponse(
posts,

View File

@ -0,0 +1,112 @@
package awais.instagrabber.webservices;
import androidx.annotation.NonNull;
import com.google.gson.FieldNamingPolicy;
import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import java.io.File;
import awais.instagrabber.BuildConfig;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.repositories.responses.Caption;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.webservices.interceptors.AddCookiesInterceptor;
import awais.instagrabber.webservices.interceptors.IgErrorsInterceptor;
import okhttp3.Cache;
import okhttp3.OkHttpClient;
import retrofit2.Retrofit;
import retrofit2.converter.gson.GsonConverterFactory;
import retrofit2.converter.scalars.ScalarsConverterFactory;
public final class RetrofitFactory {
private static final Object LOCK = new Object();
private static RetrofitFactory instance;
private final int cacheSize = 10 * 1024 * 1024; // 10 MB
private final Cache cache = new Cache(new File(Utils.cacheDir), cacheSize);
private IgErrorsInterceptor igErrorsInterceptor;
private MainActivity mainActivity;
private Retrofit.Builder builder;
private Retrofit retrofit;
private Retrofit retrofitWeb;
public static void setup(@NonNull final MainActivity mainActivity) {
if (instance == null) {
synchronized (LOCK) {
if (instance == null) {
instance = new RetrofitFactory(mainActivity);
}
}
}
}
public static RetrofitFactory getInstance() {
if (instance == null) {
throw new RuntimeException("Setup not done!");
}
return instance;
}
private RetrofitFactory(@NonNull final MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
private Retrofit.Builder getRetrofitBuilder() {
if (builder == null) {
igErrorsInterceptor = new IgErrorsInterceptor(mainActivity);
final OkHttpClient.Builder clientBuilder = new OkHttpClient.Builder()
.followRedirects(false)
.followSslRedirects(false)
.cache(cache);
if (BuildConfig.DEBUG) {
// clientBuilder.addInterceptor(new LoggingInterceptor());
}
clientBuilder.addInterceptor(new AddCookiesInterceptor())
.addInterceptor(igErrorsInterceptor);
final Gson gson = new GsonBuilder()
.setFieldNamingPolicy(FieldNamingPolicy.LOWER_CASE_WITH_UNDERSCORES)
.registerTypeAdapter(Caption.class, new Caption.CaptionDeserializer())
.setLenient()
.create();
builder = new Retrofit.Builder()
.addConverterFactory(ScalarsConverterFactory.create())
.addConverterFactory(GsonConverterFactory.create(gson))
.client(clientBuilder.build());
}
return builder;
}
public Retrofit getRetrofit() {
if (retrofit == null) {
retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com")
.build();
}
return retrofit;
}
public Retrofit getRetrofitWeb() {
if (retrofitWeb == null) {
retrofitWeb = getRetrofitBuilder()
.baseUrl("https://www.instagram.com")
.build();
}
return retrofitWeb;
}
public void destroy() {
if (igErrorsInterceptor != null) {
igErrorsInterceptor.destroy();
}
igErrorsInterceptor = null;
mainActivity = null;
retrofit = null;
retrofitWeb = null;
builder = null;
instance = null;
}
}

View File

@ -1,16 +1,10 @@
package awais.instagrabber.webservices;
import androidx.annotation.NonNull;
import com.google.common.collect.ImmutableMap;
import awais.instagrabber.repositories.SearchRepository;
import awais.instagrabber.repositories.responses.search.SearchResponse;
import awais.instagrabber.utils.TextUtils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class SearchService extends BaseService {
private static final String TAG = "LocationService";
@ -20,10 +14,9 @@ public class SearchService extends BaseService {
private static SearchService instance;
private SearchService() {
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://www.instagram.com")
.build();
repository = retrofit.create(SearchRepository.class);
repository = RetrofitFactory.getInstance()
.getRetrofitWeb()
.create(SearchRepository.class);
}
public static SearchService getInstance() {
@ -43,8 +36,8 @@ public class SearchService extends BaseService {
builder.put("context", context);
builder.put("count", "50");
return repository.search(isLoggedIn
? "https://i.instagram.com/api/v1/fbsearch/topsearch_flat/"
: "https://www.instagram.com/web/search/topsearch/",
builder.build());
? "https://i.instagram.com/api/v1/fbsearch/topsearch_flat/"
: "https://www.instagram.com/web/search/topsearch/",
builder.build());
}
}

View File

@ -32,7 +32,6 @@ import awais.instagrabber.utils.Utils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class StoriesService extends BaseService {
private static final String TAG = "StoriesService";
@ -50,10 +49,9 @@ public class StoriesService extends BaseService {
this.csrfToken = csrfToken;
this.userId = userId;
this.deviceUuid = deviceUuid;
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(StoriesRepository.class);
repository = RetrofitFactory.getInstance()
.getRetrofit()
.create(StoriesRepository.class);
}
public String getCsrfToken() {
@ -137,7 +135,7 @@ public class StoriesService extends BaseService {
final JSONArray feedStoriesReel = new JSONObject(body).getJSONArray("tray");
for (int i = 0; i < feedStoriesReel.length(); ++i) {
final JSONObject node = feedStoriesReel.getJSONObject(i);
if (node.optBoolean("hide_from_feed_unit")) continue;
if (node.optBoolean("hide_from_feed_unit") && Utils.settingsHelper.getBoolean(Constants.HIDE_MUTED_REELS)) continue;
final JSONObject userJson = node.getJSONObject(node.has("user") ? "user" : "owner");
try {
final User user = new User(userJson.getLong("pk"),
@ -179,17 +177,22 @@ public class StoriesService extends BaseService {
null,
null
);
final String id = node.getString("id");
final long timestamp = node.getLong("latest_reel_media");
final int mediaCount = node.getInt("media_count");
final boolean fullyRead = !node.isNull("seen") && node.getLong("seen") == timestamp;
final JSONObject itemJson = node.has("items") ? node.getJSONArray("items").optJSONObject(0) : null;
final boolean isBestie = node.optBoolean("has_besties_media", false);
StoryModel firstStoryModel = null;
if (itemJson != null) {
firstStoryModel = ResponseBodyUtils.parseStoryItem(itemJson, false, null);
}
feedStoryModels.add(new FeedStoryModel(id, user, fullyRead, timestamp, firstStoryModel, mediaCount, false, isBestie));
feedStoryModels.add(new FeedStoryModel(
node.getString("id"),
user,
fullyRead,
timestamp,
firstStoryModel,
node.getInt("media_count"),
false,
node.optBoolean("has_besties_media")));
}
catch (Exception e) {} // to cover promotional reels with non-long user pk's
}
@ -242,13 +245,16 @@ public class StoriesService extends BaseService {
null,
null
);
final String id = node.getString("id");
final long timestamp = node.getLong("published_time");
// final JSONObject itemJson = node.has("items") ? node.getJSONArray("items").getJSONObject(0) : null;
final StoryModel firstStoryModel = ResponseBodyUtils.parseBroadcastItem(node);
// if (itemJson != null) {
// }
feedStoryModels.add(new FeedStoryModel(id, user, false, timestamp, firstStoryModel, 1, true, false));
feedStoryModels.add(new FeedStoryModel(
node.getString("id"),
user,
false,
node.getLong("published_time"),
ResponseBodyUtils.parseBroadcastItem(node),
1,
true,
false
));
}
callback.onSuccess(sort(feedStoryModels));
} catch (JSONException e) {

View File

@ -21,7 +21,6 @@ import awais.instagrabber.utils.Utils;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class TagsService extends BaseService {
@ -32,10 +31,9 @@ public class TagsService extends BaseService {
private final TagsRepository repository;
private TagsService() {
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com/")
.build();
repository = retrofit.create(TagsRepository.class);
repository = RetrofitFactory.getInstance()
.getRetrofit()
.create(TagsRepository.class);
}
public static TagsService getInstance() {

View File

@ -12,7 +12,6 @@ import awais.instagrabber.repositories.responses.WrappedUser;
import retrofit2.Call;
import retrofit2.Callback;
import retrofit2.Response;
import retrofit2.Retrofit;
public class UserService extends BaseService {
private static final String TAG = UserService.class.getSimpleName();
@ -22,10 +21,9 @@ public class UserService extends BaseService {
private static UserService instance;
private UserService() {
final Retrofit retrofit = getRetrofitBuilder()
.baseUrl("https://i.instagram.com")
.build();
repository = retrofit.create(UserRepository.class);
repository = RetrofitFactory.getInstance()
.getRetrofit()
.create(UserRepository.class);
}
public static UserService getInstance() {

View File

@ -1,4 +1,4 @@
package awais.instagrabber.webservices;
package awais.instagrabber.webservices.interceptors;
import androidx.annotation.NonNull;

View File

@ -0,0 +1,136 @@
package awais.instagrabber.webservices.interceptors;
import android.util.Log;
import android.view.View;
import androidx.annotation.NonNull;
import androidx.annotation.StringRes;
import com.google.android.material.snackbar.Snackbar;
import org.json.JSONObject;
import java.io.IOException;
import awais.instagrabber.R;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.dialogs.ConfirmDialogFragment;
import awais.instagrabber.utils.Constants;
import awais.instagrabber.utils.TextUtils;
import okhttp3.Interceptor;
import okhttp3.Request;
import okhttp3.Response;
import okhttp3.ResponseBody;
public class IgErrorsInterceptor implements Interceptor {
private static final String TAG = IgErrorsInterceptor.class.getSimpleName();
private MainActivity mainActivity;
public IgErrorsInterceptor(@NonNull final MainActivity mainActivity) {
this.mainActivity = mainActivity;
}
@NonNull
@Override
public Response intercept(@NonNull final Chain chain) throws IOException {
final Request request = chain.request();
final Response response = chain.proceed(request);
if (response.isSuccessful()) {
return response;
}
checkError(response);
return response;
}
private void checkError(@NonNull final Response response) {
final int errorCode = response.code();
switch (errorCode) {
case 429: // "429 Too Many Requests"
// ('Throttled by Instagram because of too many API requests.');
showErrorDialog(R.string.throttle_error);
return;
case 431: // "431 Request Header Fields Too Large"
// show dialog?
Log.e(TAG, "Network error: " + getMessage(errorCode, "The request start-line and/or headers are too large to process."));
return;
case 404:
showErrorDialog(R.string.not_found);
return;
case 302: // redirect
final String location = response.header("location");
if (location.equals("https://www.instagram.com/accounts/login/")) {
// rate limited
showErrorDialog(R.string.rate_limit);
}
return;
}
final ResponseBody body = response.body();
if (body == null) return;
try {
final String bodyString = body.string();
final JSONObject jsonObject = new JSONObject(bodyString);
String message = jsonObject.optString("message", null);
if (!TextUtils.isEmpty(message)) {
message = message.toLowerCase();
switch (message) {
case "user_has_logged_out":
showErrorDialog(R.string.account_logged_out);
return;
case "login_required":
showErrorDialog(R.string.login_required);
return;
case "execution failure":
showSnackbar(message);
return;
case "not authorized to view user": // Do we handle this in profile view fragment?
case "challenge_required": // Since we make users login using browser, we should not be getting this error in api requests
default:
showSnackbar(message);
Log.e(TAG, "checkError: " + bodyString);
return;
}
}
final String errorType = jsonObject.optString("error_type", null);
if (TextUtils.isEmpty(errorType)) return;
if (errorType.equals("sentry_block")) {
showErrorDialog(R.string.sentry_block);
return;
}
if (errorType.equals("inactive user")) {
showErrorDialog(R.string.inactive_user);
}
} catch (Exception e) {
Log.e(TAG, "checkError: ", e);
}
}
private void showSnackbar(final String message) {
final View view = mainActivity.getRootView();
if (view == null) return;
Snackbar.make(view, message, Snackbar.LENGTH_LONG).show();
}
@NonNull
private String getMessage(final int errorCode, final String message) {
return String.format("code: %s, internalMessage: %s", errorCode, message);
}
private void showErrorDialog(@StringRes final int messageResId) {
if (mainActivity == null) return;
if (messageResId == 0) return;
final ConfirmDialogFragment dialogFragment = ConfirmDialogFragment.newInstance(
Constants.GLOBAL_NETWORK_ERROR_DIALOG_REQUEST_CODE,
R.string.error,
messageResId,
R.string.ok,
0,
0
);
dialogFragment.show(mainActivity.getSupportFragmentManager(), "network_error_dialog");
}
public void destroy() {
mainActivity = null;
}
}

View File

@ -1,4 +1,4 @@
package awais.instagrabber.webservices;
package awais.instagrabber.webservices.interceptors;
import android.util.Log;

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM11,19.93c-3.95,-0.49 -7,-3.85 -7,-7.93 0,-0.62 0.08,-1.21 0.21,-1.79L9,15v1c0,1.1 0.9,2 2,2v1.93zM17.9,17.39c-0.26,-0.81 -1,-1.39 -1.9,-1.39h-1v-3c0,-0.55 -0.45,-1 -1,-1L8,12v-2h2c0.55,0 1,-0.45 1,-1L11,7h2c1.1,0 2,-0.9 2,-2v-0.41c2.93,1.19 5,4.06 5,7.41 0,2.08 -0.8,3.97 -2.1,5.39z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,10.9c-0.61,0 -1.1,0.49 -1.1,1.1s0.49,1.1 1.1,1.1c0.61,0 1.1,-0.49 1.1,-1.1s-0.49,-1.1 -1.1,-1.1zM12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM14.19,14.19L6,18l3.81,-8.19L18,6l-3.81,8.19z"/>
</vector>

View File

@ -1,9 +0,0 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24">
<path
android:fillColor="@android:color/white"
android:pathData="M13.5,0.67s0.74,2.65 0.74,4.8c0,2.06 -1.35,3.73 -3.41,3.73 -2.07,0 -3.63,-1.67 -3.63,-3.73l0.03,-0.36C5.21,7.51 4,10.62 4,14c0,4.42 3.58,8 8,8s8,-3.58 8,-8C20,8.61 17.41,3.8 13.5,0.67zM11.71,19c-1.78,0 -3.22,-1.4 -3.22,-3.14 0,-1.62 1.05,-2.76 2.81,-3.12 1.77,-0.36 3.6,-1.21 4.62,-2.58 0.39,1.29 0.59,2.65 0.59,4.04 0,2.65 -2.15,4.8 -4.8,4.8z" />
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M10,20v-6h4v6h5v-8h3L12,3 2,12h3v8z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M20,2L4,2c-1.1,0 -1.99,0.9 -1.99,2L2,22l4,-4h14c1.1,0 2,-0.9 2,-2L22,4c0,-1.1 -0.9,-2 -2,-2zM18,14L6,14v-2h12v2zM18,11L6,11L6,9h12v2zM18,8L6,8L6,6h12v2z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,12c2.21,0 4,-1.79 4,-4s-1.79,-4 -4,-4 -4,1.79 -4,4 1.79,4 4,4zM12,14c-2.67,0 -8,1.34 -8,4v2h16v-2c0,-2.66 -5.33,-4 -8,-4z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM16,13h-3v3c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1v-3L8,13c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h3L11,8c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v3h3c0.55,0 1,0.45 1,1s-0.45,1 -1,1z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M19,9H5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1h14c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1zM5,15h14c0.55,0 1,-0.45 1,-1s-0.45,-1 -1,-1H5c-0.55,0 -1,0.45 -1,1s0.45,1 1,1z"/>
</vector>

View File

@ -0,0 +1,10 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24"
android:viewportHeight="24"
android:tint="?attr/colorControlNormal">
<path
android:fillColor="@android:color/white"
android:pathData="M12,2C6.48,2 2,6.48 2,12s4.48,10 10,10 10,-4.48 10,-10S17.52,2 12,2zM16,13L8,13c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h8c0.55,0 1,0.45 1,1s-0.45,1 -1,1z"/>
</vector>

View File

@ -47,7 +47,6 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_gravity="bottom"
app:labelVisibilityMode="labeled"
app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior"
app:menu="@menu/main_bottom_navigation_menu" />
app:labelVisibilityMode="auto"
app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior" />
</androidx.coordinatorlayout.widget.CoordinatorLayout>

View File

@ -0,0 +1,45 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="56dp"
android:orientation="horizontal">
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/add_remove"
android:layout_width="24dp"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginEnd="8dp"
android:scaleType="centerInside"
tools:srcCompat="@drawable/ic_round_add_circle_24"
tools:tint="@color/green_500" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/icon"
android:layout_width="24dp"
android:layout_height="match_parent"
android:layout_marginStart="8dp"
android:layout_marginEnd="16dp"
android:scaleType="centerInside"
tools:srcCompat="@drawable/ic_home_24" />
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/title"
android:layout_width="0dp"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical"
android:textAppearance="@style/TextAppearance.MaterialComponents.Body1"
tools:text="@string/feed" />
<androidx.appcompat.widget.AppCompatImageView
android:id="@+id/handle"
android:layout_width="24dp"
android:layout_height="match_parent"
android:layout_marginStart="16dp"
android:layout_marginEnd="16dp"
android:scaleType="centerInside"
app:srcCompat="@drawable/ic_round_drag_handle_24" />
</LinearLayout>

View File

@ -326,6 +326,7 @@
<androidx.constraintlayout.widget.Barrier
android:id="@+id/highlights_barrier"
android:layout_width="wrap_content"
app:constraint_referenced_ids="mainPostCount, mainFollowers, mainFollowing"
android:layout_height="wrap_content"
app:barrierDirection="bottom" />

View File

@ -1,12 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/profile_nav_graph"
android:icon="@drawable/ic_profile_24"
android:title="@string/profile" />
<item
android:id="@+id/more_nav_graph"
android:icon="@drawable/ic_more_horiz_24"
android:title="@string/more" />
</menu>

View File

@ -1,30 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/direct_messages_nav_graph"
android:icon="@drawable/ic_round_send_24"
android:title="@string/title_dm" />
<item
android:id="@+id/feed_nav_graph"
android:icon="@drawable/ic_feed"
android:title="@string/feed" />
<item
android:id="@+id/profile_nav_graph"
android:icon="@drawable/ic_profile_24"
android:title="@string/profile" />
<item
android:id="@+id/discover_nav_graph"
android:icon="@drawable/ic_discover"
android:title="@string/title_discover" />
<!--<item-->
<!-- android:id="@+id/favouritesFragment"-->
<!-- android:icon="@drawable/ic_star_24"-->
<!-- android:title="@string/title_favorites"/>-->
<item
android:id="@+id/more_nav_graph"
android:icon="@drawable/ic_more_horiz_24"
android:title="@string/more" />
</menu>

View File

@ -5,13 +5,18 @@
<item
android:id="@+id/action_dms"
android:icon="@drawable/ic_round_send_24"
android:title="@string/action_dms"
android:titleCondensed="@string/action_dms"
app:showAsAction="always" />
android:title="@string/reply_story"
android:titleCondensed="@string/reply_story"
app:showAsAction="never" />
<item
android:id="@+id/action_profile"
android:title="@string/open_profile"
android:titleCondensed="@string/open_profile"
app:showAsAction="never" />
<item
android:id="@+id/action_download"
android:icon="@drawable/ic_download"
android:title="@string/action_download"
android:titleCondensed="@string/action_download"
app:showAsAction="always" />
app:showAsAction="never" />
</menu>

View File

@ -0,0 +1,43 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:id="@+id/favorites_nav_graph"
app:startDestination="@id/favoritesFragment">
<include app:graph="@navigation/profile_nav_graph" />
<include app:graph="@navigation/hashtag_nav_graph" />
<include app:graph="@navigation/location_nav_graph" />
<include app:graph="@navigation/comments_nav_graph" />
<include app:graph="@navigation/likes_nav_graph" />
<action
android:id="@+id/action_global_profileFragment"
app:destination="@id/profile_nav_graph">
<argument
android:name="username"
app:argType="string"
app:nullable="true" />
</action>
<action
android:id="@+id/action_global_hashTagFragment"
app:destination="@id/hashtag_nav_graph">
<argument
android:name="hashtag"
app:argType="string"
app:nullable="false" />
</action>
<action
android:id="@+id/action_global_locationFragment"
app:destination="@id/location_nav_graph">
<argument
android:name="locationId"
app:argType="long" />
</action>
<fragment
android:id="@+id/favoritesFragment"
android:name="awais.instagrabber.fragments.FavoritesFragment"
android:label="@string/title_favorites" />
</navigation>

View File

@ -11,6 +11,7 @@
<include app:graph="@navigation/likes_nav_graph" />
<include app:graph="@navigation/notification_viewer_nav_graph" />
<include app:graph="@navigation/story_list_nav_graph" />
<include app:graph="@navigation/discover_nav_graph" />
<action
android:id="@+id/action_global_profileFragment"

View File

@ -5,23 +5,16 @@
android:id="@+id/notification_viewer_nav_graph"
app:startDestination="@id/notificationsViewer">
<fragment
android:id="@+id/notificationsViewer"
android:name="awais.instagrabber.fragments.NotificationsViewerFragment"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications_viewer">
<include app:graph="@navigation/profile_nav_graph" />
<action
android:id="@+id/action_global_profileFragment"
app:destination="@id/profile_nav_graph">
<argument
android:name="type"
android:name="username"
app:argType="string"
app:nullable="false" />
<argument
android:name="targetId"
android:defaultValue="0L"
app:argType="long" />
<action
android:id="@+id/action_notificationsViewerFragment_to_storyViewerFragment"
app:destination="@id/storyViewerFragment" />
</fragment>
app:nullable="true" />
</action>
<action
android:id="@+id/action_global_notificationsViewerFragment"
@ -88,4 +81,23 @@
android:name="options"
app:argType="awais.instagrabber.repositories.requests.StoryViewerOptions" />
</fragment>
<fragment
android:id="@+id/notificationsViewer"
android:name="awais.instagrabber.fragments.NotificationsViewerFragment"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications_viewer">
<argument
android:name="type"
app:argType="string"
app:nullable="false"
android:defaultValue="notif"/>
<argument
android:name="targetId"
android:defaultValue="0L"
app:argType="long" />
<action
android:id="@+id/action_notifications_to_story"
app:destination="@id/storyViewerFragment" />
</fragment>
</navigation>

View File

@ -71,11 +71,9 @@
app:argType="long" />
</action>
<include app:graph="@navigation/notification_viewer_nav_graph" />
<action
android:id="@+id/action_global_notificationsViewerFragment"
app:destination="@id/notification_viewer_nav_graph">
app:destination="@id/notificationsViewer">
<argument
android:name="type"
app:argType="string"
@ -86,6 +84,22 @@
app:argType="long" />
</action>
<fragment
android:id="@+id/notificationsViewer"
android:name="awais.instagrabber.fragments.NotificationsViewerFragment"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications_viewer">
<argument
android:name="type"
app:argType="string"
app:nullable="false"
android:defaultValue="notif"/>
<argument
android:name="targetId"
android:defaultValue="0L"
app:argType="long" />
</fragment>
<include app:graph="@navigation/saved_nav_graph" />
<action

View File

@ -23,6 +23,7 @@
<string name="update_check">Cerca actualitzacions a l\'inici</string>
<string name="flag_secure">Block screenshots &amp; app preview</string>
<string name="download_user_folder">Descarrega les publicacions a carpetes de nom d\'usuari</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">Marca les històries com a vistes després de visualitzar-es</string>
<string name="mark_as_seen_setting_summary">L\'autor de la història sabrà que l\'has vista</string>
<string name="dm_mark_as_seen_setting">Marca els missatges com a vists després de visualitzar-los</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">Zkontrolovat aktualizace při spuštění</string>
<string name="flag_secure">Block screenshots &amp; app preview</string>
<string name="download_user_folder">Stáhnout příspěvky do složek s uživatelským jménem</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">Označit příběhy po zhlédnutí jako zobrazené</string>
<string name="mark_as_seen_setting_summary">Autor příběhu bude vědět, že jsi si ho zobrazili</string>
<string name="dm_mark_as_seen_setting">Označovat přímou zprávu po zobrazení jako zobrazenou</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">Beim Start auf Aktualisierungen prüfen</string>
<string name="flag_secure">Block screenshots &amp; app preview</string>
<string name="download_user_folder">Beiträge in Benutzernamen-Ordner herunterladen</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">Stories nach dem Ansehen als gesehen markieren</string>
<string name="mark_as_seen_setting_summary">Die Person wird wissen, dass du dir die Story angesehen hast</string>
<string name="dm_mark_as_seen_setting">Direktnachrichten nach dem Ansehen als gesehen markieren</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">Έλεγχος για ενημερώσεις στο ξεκίνημα</string>
<string name="flag_secure">Block screenshots &amp; app preview</string>
<string name="download_user_folder">Λήψη δημοσίευσης στους φακέλους με ονόματα χρηστών</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">Επισήμανση ιστοριών ως προβληθέντων μετά την προβολή</string>
<string name="mark_as_seen_setting_summary">Ο συντάκτης της ιστορίας θα ξέρει ότι την προβάλατε</string>
<string name="dm_mark_as_seen_setting">Σήμανση ΠΜ ως αναγνωσμένου μετά την προβολή</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">Buscar actualizaciones al inicio</string>
<string name="flag_secure">Bloquea capturas de pantalla &amp; vista previa de aplicaciones</string>
<string name="download_user_folder">Usar subcarpetas con el nombre de usuario</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">Marcar historias como vistas después de verlas</string>
<string name="mark_as_seen_setting_summary">El autor de la historia sabrá que lo has visto</string>
<string name="dm_mark_as_seen_setting">Marcar Mensaje Directo como visto después de verlo</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">Bilatu eguneratzeak abioan</string>
<string name="flag_secure">Block screenshots &amp; app preview</string>
<string name="download_user_folder">Deskargatu bidalketak erabiltzaile-izena duten karpetetara</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">Markatu istorioak ikusita gisa ikusi ondoren</string>
<string name="mark_as_seen_setting_summary">Istorioaren egileak ikusi duzula jakingo du</string>
<string name="dm_mark_as_seen_setting">Markatu MZ ikusita gisa ikusi ondoren</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">بررسی بروزرسانی هنگام آغاز برنامه</string>
<string name="flag_secure">Block screenshots &amp; app preview</string>
<string name="download_user_folder">بارگیری پست ها در پوشه های به نام کاربر</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">نشان کرد استوری ها به عنوان دیده شده بعد از دیدن</string>
<string name="mark_as_seen_setting_summary">نویسنده استوری می داند که شما آن را دیده اید</string>
<string name="dm_mark_as_seen_setting">نشان کردن پیام خصوصی بعنوان دیده شده بعد از دیدن</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">Rechercher les mises à jours au démarrage</string>
<string name="flag_secure">Bloquer les captures d\'écran &amp; l\'aperçu de l\'application</string>
<string name="download_user_folder">Télécharger les messages dans les dossiers des noms d\'utilisateurs</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">Marquer les stories comme vues après consultation</string>
<string name="mark_as_seen_setting_summary">L\'auteur de la story saura que vous l\'avez vue</string>
<string name="dm_mark_as_seen_setting">Marquer les messages privés comme vus après consultation</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">खुलने पर अपडेट के लिए जाँच करें</string>
<string name="flag_secure">Block screenshots &amp; app preview</string>
<string name="download_user_folder">पोस्ट को ब्यबहारकारी के नाम पर किये फोल्डरस में रखें</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">स्टोरि को दिखने के बाद \"दिखा गया\" दिखादें</string>
<string name="mark_as_seen_setting_summary">सटोरि के लेखक जानेगा कि तुम देखे हो इसको</string>
<string name="dm_mark_as_seen_setting">तुम देखने के बाद सीधा संदेश को \"दिखागया\" लिखा जाएगा</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">Cek pembaruan saat memulai</string>
<string name="flag_secure">Block screenshots &amp; app preview</string>
<string name="download_user_folder">Unduh kiriman ke folder nama pengguna</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">Tandai cerita dibaca setelah melihat</string>
<string name="mark_as_seen_setting_summary">Pembuat cerita akan tahu Anda melihatnya</string>
<string name="dm_mark_as_seen_setting">Tandai DM dibaca setelah melihat</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">Verifica per aggiornamenti all\'avvio</string>
<string name="flag_secure">Blocca screenshot &amp; anteprima app</string>
<string name="download_user_folder">Scarica i post nelle cartelle del nome utente</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">Segna le storie come viste dopo la visualizzazione</string>
<string name="mark_as_seen_setting_summary">L\'autore della storia saprà che l\'hai visualizzata</string>
<string name="dm_mark_as_seen_setting">Segna il DM come visto dopo la visualizzazione</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">起動時にアップデートを確認</string>
<string name="flag_secure">Block screenshots &amp; app preview</string>
<string name="download_user_folder">ユーザ名のフォルダに投稿をダウンロード</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">ストーリーズを表示後に既読にする</string>
<string name="mark_as_seen_setting_summary">ストーリーの作成者は、あなたが閲覧したことを知ることができます。</string>
<string name="dm_mark_as_seen_setting">DMを表示後に既読にする</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">Check for updates at startup</string>
<string name="flag_secure">Block screenshots &amp; app preview</string>
<string name="download_user_folder">Download posts to username folders</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">Mark stories as seen after viewing</string>
<string name="mark_as_seen_setting_summary">Story author will know you viewed it</string>
<string name="dm_mark_as_seen_setting">Mark DM as seen after viewing</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">Провери за ажурирање</string>
<string name="flag_secure">Block screenshots &amp; app preview</string>
<string name="download_user_folder">Превземи објави во папката со кориснички имиња</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">Означи ги приказните како видени</string>
<string name="mark_as_seen_setting_summary">Авторот на приказната ќе знае дека сте ја погледнале приказната</string>
<string name="dm_mark_as_seen_setting">Означи порака како видена</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">Controleer op updates bij het opstarten</string>
<string name="flag_secure">Block screenshots &amp; app preview</string>
<string name="download_user_folder">Download berichten naar gebruikersnaam mappen</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">Markeer verhalen als gelezen na bekijken</string>
<string name="mark_as_seen_setting_summary">Verhaalmaker zal het weten als je het bekeken hebt</string>
<string name="dm_mark_as_seen_setting">Markeer privéberichten als gelezen na bekijken</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">ଖୋଲିବା ସମୟରେ ଅପଡେଟ ପାଇଁ ଯାଞ୍ଚ କରନ୍ତୁ</string>
<string name="flag_secure">Block screenshots &amp; app preview</string>
<string name="download_user_folder">ଡାଉନଲୋଡ ପୋଷ୍ଟକୁ ବ୍ୟବହାରକାରୀଙ୍କ ନାମରେ ହୋଇଥିବା ସ୍ଥାନ ରେ ରଖ</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">କାହାଣୀଗୁଡିକ ଦେଖିବା ପରେ \'ଦେଖାଗଲା\' ଚିହ୍ନିତ କରନ୍ତୁ |</string>
<string name="mark_as_seen_setting_summary">କାହାଣୀ ପ୍ରେରକ ଜାଣିବେ ତୁମେ ଏହାକୁ ଦେଖିଛ</string>
<string name="dm_mark_as_seen_setting">ବାର୍ତା ଦେଖିବା ପରେ \'ଦେଖାଗଲା\' ଚିହ୍ନିତ କରନ୍ତୁ |</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">Sprawdź aktualizacje przy starcie</string>
<string name="flag_secure">Blokuj zrzuty ekranu &amp; podgląd aplikacji</string>
<string name="download_user_folder">Pobierz posty do folderów o nazwie użytkownika</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">Oznacz relacje jako widoczne po wyświetleniu</string>
<string name="mark_as_seen_setting_summary">Autor relacji będzie widział, że to wyświetliłeś</string>
<string name="dm_mark_as_seen_setting">Oznacz wiadomość jako przeczytaną</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">Verificar se há atualizações ao iniciar</string>
<string name="flag_secure">Block screenshots &amp; app preview</string>
<string name="download_user_folder">Baixar publicações para pastas com o nome de usuário</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">Marcar stories como vistos após a visualização</string>
<string name="mark_as_seen_setting_summary">O autor do story saberá que você viu</string>
<string name="dm_mark_as_seen_setting">Marcar DM como vista após a visualização</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">Проверять наличие обновлений при запуске</string>
<string name="flag_secure">Block screenshots &amp; app preview</string>
<string name="download_user_folder">Скачать публикации в папки с именем пользователя</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">Отметить истории как увиденные после просмотра</string>
<string name="mark_as_seen_setting_summary">Автор истории узнает, что вы просмотрели её</string>
<string name="dm_mark_as_seen_setting">Отметить ЛС как увиденные после просмотра</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">Kontrolovať aktualizácie pri štarte</string>
<string name="flag_secure">Block screenshots &amp; app preview</string>
<string name="download_user_folder">Ukľadať do priečinkov podľa mena</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">Označiť príbehy po videní ako videné</string>
<string name="mark_as_seen_setting_summary">Autor príbehu bude vedieť že ste ho videli</string>
<string name="dm_mark_as_seen_setting">Po prečítaní, označiť správu ako prečítanú</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">Güncellemeleri başlangıçta kontrol et</string>
<string name="flag_secure">Block screenshots &amp; app preview</string>
<string name="download_user_folder">İndirmeleri kullanıcı adından oluşan bir alt klasörün içine yap</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">Hikayeleri gördükten sonra görüldü olarak işaretle</string>
<string name="mark_as_seen_setting_summary">Hikayeyi paylaşan gördüğünüzü bilecek</string>
<string name="dm_mark_as_seen_setting">DM\'leri gördükten sonra görüldü olarak işaretle</string>

View File

@ -23,6 +23,7 @@
<string name="update_check">Kiểm tra cập nhật khi khởi động</string>
<string name="flag_secure">Block screenshots &amp; app preview</string>
<string name="download_user_folder">Tải bài viết xuống theo thư mục tên người dùng trong Downloads</string>
<string name="download_prepend_username">Prepend Username to Filename</string>
<string name="mark_as_seen_setting">Đánh dấu story là đã xem sau khi xem</string>
<string name="mark_as_seen_setting_summary">Người đăng story sẽ biết bạn đã xem nó</string>
<string name="dm_mark_as_seen_setting">Đánh dấu DM là đã xem sau khi xem</string>

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