diff --git a/app/build.gradle b/app/build.gradle index 498d167a..3048fb50 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -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 } diff --git a/app/src/fdroid/java/awais/instagrabber/utils/UpdateChecker.java b/app/src/fdroid/java/awais/instagrabber/utils/UpdateChecker.java new file mode 100644 index 00000000..f25b8aa4 --- /dev/null +++ b/app/src/fdroid/java/awais/instagrabber/utils/UpdateChecker.java @@ -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/"); + } +} diff --git a/app/src/github/java/awais/instagrabber/utils/UpdateChecker.java b/app/src/github/java/awais/instagrabber/utils/UpdateChecker.java new file mode 100644 index 00000000..70bc2699 --- /dev/null +++ b/app/src/github/java/awais/instagrabber/utils/UpdateChecker.java @@ -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"); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index ce5e77c9..a958a9a3 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -130,6 +130,9 @@ android:name="android.support.PARENT_ACTIVITY" android:value=".activities.MainActivity" /> + requestListeners = new HashSet<>(); // requestListeners.add(new RequestLoggingListener()); final ImagePipelineConfig imagePipelineConfig = ImagePipelineConfig diff --git a/app/src/main/java/awais/instagrabber/activities/Login.java b/app/src/main/java/awais/instagrabber/activities/Login.java index 37083794..f0681486 100755 --- a/app/src/main/java/awais/instagrabber/activities/Login.java +++ b/app/src/main/java/awais/instagrabber/activities/Login.java @@ -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); diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.java b/app/src/main/java/awais/instagrabber/activities/MainActivity.java index 23a9929f..3459d632 100644 --- a/app/src/main/java/awais/instagrabber/activities/MainActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.java @@ -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 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 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 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 behavior; + private List currentTabs; + private List 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 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 result = new ArrayList(); + final List 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 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 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 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 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 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 getMainNavList(final int main_nav_ids) { - final TypedArray navIds = getResources().obtainTypedArray(main_nav_ids); - final List 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 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 setupMainBottomNav() { + final Menu menu = binding.bottomNavView.getMenu(); + menu.clear(); + final List 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 getMainNavList(final int main_nav_ids) { + // final TypedArray navIds = getResources().obtainTypedArray(main_nav_ids); + // final List 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 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 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; diff --git a/app/src/main/java/awais/instagrabber/adapters/SuggestionsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/SuggestionsAdapter.java index 6c4fa4c7..6c51f1f7 100755 --- a/app/src/main/java/awais/instagrabber/adapters/SuggestionsAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/SuggestionsAdapter.java @@ -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; diff --git a/app/src/main/java/awais/instagrabber/adapters/TabsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/TabsAdapter.java new file mode 100644 index 00000000..474e4190 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/adapters/TabsAdapter.java @@ -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 { + private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() { + @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 current = new ArrayList<>(); + private List 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 current, final List others, final Runnable commitCallback) { + final ImmutableList.Builder 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 current, final List others) { + submitList(current, others, null); + } + + public void moveItem(final int from, final int to) { + final List 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 newOrderTabs); + + void onAdd(Tab tab); + + void onRemove(Tab tab); + } +} diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/TabViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/TabViewHolder.java new file mode 100644 index 00000000..d4be8726 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/TabViewHolder.java @@ -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); + } +} diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java index 949b2a9a..6198faf1 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java @@ -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; diff --git a/app/src/main/java/awais/instagrabber/db/datasources/FavoriteDataSource.java b/app/src/main/java/awais/instagrabber/db/datasources/FavoriteDataSource.java index 36798b08..bc013926 100644 --- a/app/src/main/java/awais/instagrabber/db/datasources/FavoriteDataSource.java +++ b/app/src/main/java/awais/instagrabber/db/datasources/FavoriteDataSource.java @@ -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; } diff --git a/app/src/main/java/awais/instagrabber/dialogs/AccountSwitcherDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/AccountSwitcherDialogFragment.java index 91afa7d1..e551c66c 100644 --- a/app/src/main/java/awais/instagrabber/dialogs/AccountSwitcherDialogFragment.java +++ b/app/src/main/java/awais/instagrabber/dialogs/AccountSwitcherDialogFragment.java @@ -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) -> { diff --git a/app/src/main/java/awais/instagrabber/dialogs/TabOrderPreferenceDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/TabOrderPreferenceDialogFragment.java new file mode 100644 index 00000000..0ac2dd23 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/dialogs/TabOrderPreferenceDialogFragment.java @@ -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 tabsInPref; + private ItemTouchHelper itemTouchHelper; + private AlertDialog dialog; + private List newOrderTabs; + private List 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 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.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.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 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> 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(); + } +} diff --git a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java index 65db4826..c42809c5 100644 --- a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java @@ -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; diff --git a/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java index d9b56ce7..15d5306d 100644 --- a/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java @@ -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); } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java index 82438999..6de66afe 100644 --- a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java @@ -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); } }); diff --git a/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java index f4b8d47e..bfc1dee3 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java @@ -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; + } } diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java index c0998f13..cc2ce13a 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java @@ -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 diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java index 01cc0bd4..8a2a03b3 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java @@ -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; diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/GeneralPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/GeneralPreferencesFragment.java index f63c4a7a..2eba325e 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/GeneralPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/GeneralPreferencesFragment.java @@ -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 preferences = FlavorSettings.getInstance().getPreferences(context, - getChildFragmentManager(), - SettingCategory.GENERAL); + final List 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> listPair = Utils.getNavTabList(context); + final List 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() { + + } } diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java index 54bf518f..8348123b 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java @@ -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() { @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() { @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 diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.java b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.java index 3482cf7f..5287a1a5 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.java @@ -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"; } diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/StoriesPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/StoriesPreferencesFragment.java index 8337f7c3..99eee7c7 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/StoriesPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/StoriesPreferencesFragment.java @@ -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); diff --git a/app/src/main/java/awais/instagrabber/models/Tab.java b/app/src/main/java/awais/instagrabber/models/Tab.java new file mode 100644 index 00000000..e4e608be --- /dev/null +++ b/app/src/main/java/awais/instagrabber/models/Tab.java @@ -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/graphName + */ + 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. + *

eg: inside R.navigation.direct_messages_nav_graph, the id of the root tag is R.id.direct_messages_nav_graph. + *

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 + '\'' + + '}'; + } +} diff --git a/app/src/main/java/awais/instagrabber/repositories/SearchRepository.java b/app/src/main/java/awais/instagrabber/repositories/SearchRepository.java index 7dda390d..148e8f43 100644 --- a/app/src/main/java/awais/instagrabber/repositories/SearchRepository.java +++ b/app/src/main/java/awais/instagrabber/repositories/SearchRepository.java @@ -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; diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/notification/NotificationCounts.java b/app/src/main/java/awais/instagrabber/repositories/responses/notification/NotificationCounts.java index 10074f70..dfad97e0 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/notification/NotificationCounts.java +++ b/app/src/main/java/awais/instagrabber/repositories/responses/notification/NotificationCounts.java @@ -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; diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/search/SearchResponse.java b/app/src/main/java/awais/instagrabber/repositories/responses/search/SearchResponse.java index 04db0286..d3f0e143 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/search/SearchResponse.java +++ b/app/src/main/java/awais/instagrabber/repositories/responses/search/SearchResponse.java @@ -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 list; diff --git a/app/src/main/java/awais/instagrabber/utils/Constants.java b/app/src/main/java/awais/instagrabber/utils/Constants.java index 969f5d6a..a2a5fd13 100644 --- a/app/src/main/java/awais/instagrabber/utils/Constants.java +++ b/app/src/main/java/awais/instagrabber/utils/Constants.java @@ -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"; diff --git a/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java b/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java index 1110e58a..b44c7d3f 100755 --- a/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java @@ -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() { + @Override + public void onSuccess(final Favorite result) { + // local has priority since it's more frequently updated + } + + @Override + public void onDataNotAvailable() { + favRepo.insertOrUpdateFavorite(favorite, null); + } + }); } } diff --git a/app/src/main/java/awais/instagrabber/utils/FlavorTown.java b/app/src/main/java/awais/instagrabber/utils/FlavorTown.java index ee859700..8f06eb8b 100755 --- a/app/src/main/java/awais/instagrabber/utils/FlavorTown.java +++ b/app/src/main/java/awais/instagrabber/utils/FlavorTown.java @@ -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); - } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/MediaUploader.java b/app/src/main/java/awais/instagrabber/utils/MediaUploader.java index 30570663..e5bacd98 100644 --- a/app/src/main/java/awais/instagrabber/utils/MediaUploader.java +++ b/app/src/main/java/awais/instagrabber/utils/MediaUploader.java @@ -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; diff --git a/app/src/main/java/awais/instagrabber/utils/ProcessPhoenix.java b/app/src/main/java/awais/instagrabber/utils/ProcessPhoenix.java new file mode 100644 index 00000000..5dcc6e19 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/utils/ProcessPhoenix.java @@ -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). + *

+ * 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. + *

+ * 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. + *

+ * 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 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 runningProcesses = manager.getRunningAppProcesses(); + if (runningProcesses != null) { + for (ActivityManager.RunningAppProcessInfo processInfo : runningProcesses) { + if (processInfo.pid == currentPid && processInfo.processName.endsWith(":phoenix")) { + return true; + } + } + } + return false; + } +} diff --git a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java index 906d3945..a4bb68bc 100644 --- a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java @@ -1089,12 +1089,19 @@ public final class ResponseBodyUtils { if (imageVersions2 == null) return null; final List candidates = imageVersions2.getCandidates(); if (candidates == null || candidates.isEmpty()) return null; - final List sortedCandidates = candidates - .stream() + final boolean isSquare = Integer.compare(media.getOriginalWidth(), media.getOriginalHeight()) == 0; + final List 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 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(); } diff --git a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java index 1ff49767..fc568d9e 100755 --- a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java +++ b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java @@ -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}) diff --git a/app/src/main/java/awais/instagrabber/utils/TextUtils.java b/app/src/main/java/awais/instagrabber/utils/TextUtils.java index 64eddd22..448c2a90 100644 --- a/app/src/main/java/awais/instagrabber/utils/TextUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/TextUtils.java @@ -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 diff --git a/app/src/main/java/awais/instagrabber/utils/UpdateCheckCommon.java b/app/src/main/java/awais/instagrabber/utils/UpdateCheckCommon.java new file mode 100644 index 00000000..285fbb13 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/utils/UpdateCheckCommon.java @@ -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(); + }); + } +} diff --git a/app/src/main/java/awais/instagrabber/utils/UpdateChecker.java b/app/src/main/java/awais/instagrabber/utils/UpdateChecker.java deleted file mode 100755 index 3ea67ba1..00000000 --- a/app/src/main/java/awais/instagrabber/utils/UpdateChecker.java +++ /dev/null @@ -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 { - private final FetchListener fetchListener; - private String version; - - public UpdateChecker(final FetchListener 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); - } -} diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index 789133b8..b7c8e429 100644 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/app/src/main/java/awais/instagrabber/utils/Utils.java @@ -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 void moveItem(int sourceIndex, int targetIndex, List 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 NON_REMOVABLE_NAV_ROOT_IDS = ImmutableList.of(R.id.profile_nav_graph, R.id.more_nav_graph); + + @NonNull + public static Pair, List> 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 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 tabs = new ArrayList<>(); + final List 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 getCurrentOrderOfGraphNamesFromPref(@NonNull final String[] navGraphNames) { + tabOrderString = settingsHelper.getString(PreferenceKeys.PREF_TAB_ORDER); + final List navGraphNameList = Arrays.asList(navGraphNames); + if (TextUtils.isEmpty(tabOrderString)) { + // Use top 5 entries for default list + final List 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 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) { diff --git a/app/src/main/java/awais/instagrabber/webservices/BaseService.java b/app/src/main/java/awais/instagrabber/webservices/BaseService.java index e30aa992..a6cdf060 100644 --- a/app/src/main/java/awais/instagrabber/webservices/BaseService.java +++ b/app/src/main/java/awais/instagrabber/webservices/BaseService.java @@ -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); diff --git a/app/src/main/java/awais/instagrabber/webservices/CollectionService.java b/app/src/main/java/awais/instagrabber/webservices/CollectionService.java index faafb6d0..e0c50dc2 100644 --- a/app/src/main/java/awais/instagrabber/webservices/CollectionService.java +++ b/app/src/main/java/awais/instagrabber/webservices/CollectionService.java @@ -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 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); } diff --git a/app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java b/app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java index 5e4afea5..0688c761 100644 --- a/app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java +++ b/app/src/main/java/awais/instagrabber/webservices/DirectMessagesService.java @@ -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() { diff --git a/app/src/main/java/awais/instagrabber/webservices/DiscoverService.java b/app/src/main/java/awais/instagrabber/webservices/DiscoverService.java index 294a6a7d..880ae526 100644 --- a/app/src/main/java/awais/instagrabber/webservices/DiscoverService.java +++ b/app/src/main/java/awais/instagrabber/webservices/DiscoverService.java @@ -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() { diff --git a/app/src/main/java/awais/instagrabber/webservices/FeedService.java b/app/src/main/java/awais/instagrabber/webservices/FeedService.java index 3496f8b7..3ed9e7b6 100644 --- a/app/src/main/java/awais/instagrabber/webservices/FeedService.java +++ b/app/src/main/java/awais/instagrabber/webservices/FeedService.java @@ -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() { diff --git a/app/src/main/java/awais/instagrabber/webservices/FriendshipService.java b/app/src/main/java/awais/instagrabber/webservices/FriendshipService.java index 7c5da8cf..ced49305 100644 --- a/app/src/main/java/awais/instagrabber/webservices/FriendshipService.java +++ b/app/src/main/java/awais/instagrabber/webservices/FriendshipService.java @@ -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 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() { @Override @@ -198,8 +196,8 @@ public class FriendshipService extends BaseService { if (maxId != null) queryMap.put("max_id", maxId); final Call request = repository.getList( targetUserId, - follower ? "followers" : "following", - queryMap); + follower ? "followers" : "following", + queryMap); request.enqueue(new Callback() { @Override public void onResponse(@NonNull final Call call, @NonNull final Response response) { diff --git a/app/src/main/java/awais/instagrabber/webservices/GifService.java b/app/src/main/java/awais/instagrabber/webservices/GifService.java index 6485efd1..e783f689 100644 --- a/app/src/main/java/awais/instagrabber/webservices/GifService.java +++ b/app/src/main/java/awais/instagrabber/webservices/GifService.java @@ -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() { diff --git a/app/src/main/java/awais/instagrabber/webservices/GraphQLService.java b/app/src/main/java/awais/instagrabber/webservices/GraphQLService.java index b4ca9293..6246cd67 100644 --- a/app/src/main/java/awais/instagrabber/webservices/GraphQLService.java +++ b/app/src/main/java/awais/instagrabber/webservices/GraphQLService.java @@ -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() { diff --git a/app/src/main/java/awais/instagrabber/webservices/LocationService.java b/app/src/main/java/awais/instagrabber/webservices/LocationService.java index 7b3648f4..aca6ef47 100644 --- a/app/src/main/java/awais/instagrabber/webservices/LocationService.java +++ b/app/src/main/java/awais/instagrabber/webservices/LocationService.java @@ -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() { diff --git a/app/src/main/java/awais/instagrabber/webservices/MediaService.java b/app/src/main/java/awais/instagrabber/webservices/MediaService.java index e1990e87..d820b954 100644 --- a/app/src/main/java/awais/instagrabber/webservices/MediaService.java +++ b/app/src/main/java/awais/instagrabber/webservices/MediaService.java @@ -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() { diff --git a/app/src/main/java/awais/instagrabber/webservices/NewsService.java b/app/src/main/java/awais/instagrabber/webservices/NewsService.java index 5eec9e8c..580e2c04 100644 --- a/app/src/main/java/awais/instagrabber/webservices/NewsService.java +++ b/app/src/main/java/awais/instagrabber/webservices/NewsService.java @@ -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 result = new ArrayList<>(); - result.addAll(body.getNewStories()); - result.addAll(body.getOldStories()); + final List result = new ArrayList(); + final List newStories = body.getNewStories(); + if (newStories != null) result.addAll(newStories); + final List 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 newsItems = aymlUsers.stream() + final List 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 newsItems = body.getUsers().stream() + final List newsItems = body + .getUsers() + .stream() .map(u -> { return new Notification( new NotificationArgs( diff --git a/app/src/main/java/awais/instagrabber/webservices/ProfileService.java b/app/src/main/java/awais/instagrabber/webservices/ProfileService.java index 65f787a6..cc8199fd 100644 --- a/app/src/main/java/awais/instagrabber/webservices/ProfileService.java +++ b/app/src/main/java/awais/instagrabber/webservices/ProfileService.java @@ -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, diff --git a/app/src/main/java/awais/instagrabber/webservices/RetrofitFactory.java b/app/src/main/java/awais/instagrabber/webservices/RetrofitFactory.java new file mode 100644 index 00000000..5062e5c7 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/webservices/RetrofitFactory.java @@ -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; + } +} diff --git a/app/src/main/java/awais/instagrabber/webservices/SearchService.java b/app/src/main/java/awais/instagrabber/webservices/SearchService.java index 39b5bd65..4144f85d 100644 --- a/app/src/main/java/awais/instagrabber/webservices/SearchService.java +++ b/app/src/main/java/awais/instagrabber/webservices/SearchService.java @@ -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()); } } diff --git a/app/src/main/java/awais/instagrabber/webservices/StoriesService.java b/app/src/main/java/awais/instagrabber/webservices/StoriesService.java index 9b47839b..09a3a6b7 100644 --- a/app/src/main/java/awais/instagrabber/webservices/StoriesService.java +++ b/app/src/main/java/awais/instagrabber/webservices/StoriesService.java @@ -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) { diff --git a/app/src/main/java/awais/instagrabber/webservices/TagsService.java b/app/src/main/java/awais/instagrabber/webservices/TagsService.java index 615d72af..276f0dc7 100644 --- a/app/src/main/java/awais/instagrabber/webservices/TagsService.java +++ b/app/src/main/java/awais/instagrabber/webservices/TagsService.java @@ -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() { diff --git a/app/src/main/java/awais/instagrabber/webservices/UserService.java b/app/src/main/java/awais/instagrabber/webservices/UserService.java index 52bb064d..1266662f 100644 --- a/app/src/main/java/awais/instagrabber/webservices/UserService.java +++ b/app/src/main/java/awais/instagrabber/webservices/UserService.java @@ -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() { diff --git a/app/src/main/java/awais/instagrabber/webservices/AddCookiesInterceptor.java b/app/src/main/java/awais/instagrabber/webservices/interceptors/AddCookiesInterceptor.java similarity index 96% rename from app/src/main/java/awais/instagrabber/webservices/AddCookiesInterceptor.java rename to app/src/main/java/awais/instagrabber/webservices/interceptors/AddCookiesInterceptor.java index 3b5f709f..610b5f89 100644 --- a/app/src/main/java/awais/instagrabber/webservices/AddCookiesInterceptor.java +++ b/app/src/main/java/awais/instagrabber/webservices/interceptors/AddCookiesInterceptor.java @@ -1,4 +1,4 @@ -package awais.instagrabber.webservices; +package awais.instagrabber.webservices.interceptors; import androidx.annotation.NonNull; diff --git a/app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java b/app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java new file mode 100644 index 00000000..e27eeae2 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/webservices/interceptors/IgErrorsInterceptor.java @@ -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; + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/webservices/LoggingInterceptor.java b/app/src/main/java/awais/instagrabber/webservices/interceptors/LoggingInterceptor.java similarity index 96% rename from app/src/main/java/awais/instagrabber/webservices/LoggingInterceptor.java rename to app/src/main/java/awais/instagrabber/webservices/interceptors/LoggingInterceptor.java index c9358a72..e7bedd3b 100644 --- a/app/src/main/java/awais/instagrabber/webservices/LoggingInterceptor.java +++ b/app/src/main/java/awais/instagrabber/webservices/interceptors/LoggingInterceptor.java @@ -1,4 +1,4 @@ -package awais.instagrabber.webservices; +package awais.instagrabber.webservices.interceptors; import android.util.Log; diff --git a/app/src/main/res/drawable/ic_discover.xml b/app/src/main/res/drawable/ic_discover.xml deleted file mode 100755 index 7a1ab400..00000000 --- a/app/src/main/res/drawable/ic_discover.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_explore_24.xml b/app/src/main/res/drawable/ic_explore_24.xml new file mode 100644 index 00000000..0ac168a2 --- /dev/null +++ b/app/src/main/res/drawable/ic_explore_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_feed.xml b/app/src/main/res/drawable/ic_feed.xml deleted file mode 100755 index c163a23d..00000000 --- a/app/src/main/res/drawable/ic_feed.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_home_24.xml b/app/src/main/res/drawable/ic_home_24.xml new file mode 100644 index 00000000..3a4c7dac --- /dev/null +++ b/app/src/main/res/drawable/ic_home_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_message_24.xml b/app/src/main/res/drawable/ic_message_24.xml new file mode 100644 index 00000000..a7adce5a --- /dev/null +++ b/app/src/main/res/drawable/ic_message_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_person_24.xml b/app/src/main/res/drawable/ic_person_24.xml new file mode 100644 index 00000000..6bdced2d --- /dev/null +++ b/app/src/main/res/drawable/ic_person_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_round_add_circle_24.xml b/app/src/main/res/drawable/ic_round_add_circle_24.xml new file mode 100644 index 00000000..1906afe5 --- /dev/null +++ b/app/src/main/res/drawable/ic_round_add_circle_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_round_drag_handle_24.xml b/app/src/main/res/drawable/ic_round_drag_handle_24.xml new file mode 100644 index 00000000..3f4f79ca --- /dev/null +++ b/app/src/main/res/drawable/ic_round_drag_handle_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_round_remove_circle_24.xml b/app/src/main/res/drawable/ic_round_remove_circle_24.xml new file mode 100644 index 00000000..68d85995 --- /dev/null +++ b/app/src/main/res/drawable/ic_round_remove_circle_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 866770aa..97bcb0bb 100755 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -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" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_tab_order_pref.xml b/app/src/main/res/layout/item_tab_order_pref.xml new file mode 100644 index 00000000..8c2045c9 --- /dev/null +++ b/app/src/main/res/layout/item_tab_order_pref.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_profile_details.xml b/app/src/main/res/layout/layout_profile_details.xml index 035a04b0..ea4df164 100644 --- a/app/src/main/res/layout/layout_profile_details.xml +++ b/app/src/main/res/layout/layout_profile_details.xml @@ -326,6 +326,7 @@ diff --git a/app/src/main/res/menu/logged_out_bottom_navigation_menu.xml b/app/src/main/res/menu/logged_out_bottom_navigation_menu.xml deleted file mode 100644 index e93193a0..00000000 --- a/app/src/main/res/menu/logged_out_bottom_navigation_menu.xml +++ /dev/null @@ -1,12 +0,0 @@ - -

- - - - \ No newline at end of file diff --git a/app/src/main/res/menu/main_bottom_navigation_menu.xml b/app/src/main/res/menu/main_bottom_navigation_menu.xml deleted file mode 100644 index 6e981c7c..00000000 --- a/app/src/main/res/menu/main_bottom_navigation_menu.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/menu/story_menu.xml b/app/src/main/res/menu/story_menu.xml index a7293f91..8169fd62 100644 --- a/app/src/main/res/menu/story_menu.xml +++ b/app/src/main/res/menu/story_menu.xml @@ -5,13 +5,18 @@ + android:title="@string/reply_story" + android:titleCondensed="@string/reply_story" + app:showAsAction="never" /> + + app:showAsAction="never" /> \ No newline at end of file diff --git a/app/src/main/res/navigation/favorites_nav_graph.xml b/app/src/main/res/navigation/favorites_nav_graph.xml new file mode 100644 index 00000000..1a288bf1 --- /dev/null +++ b/app/src/main/res/navigation/favorites_nav_graph.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/more_nav_graph.xml b/app/src/main/res/navigation/more_nav_graph.xml index 84ae9423..0274390e 100644 --- a/app/src/main/res/navigation/more_nav_graph.xml +++ b/app/src/main/res/navigation/more_nav_graph.xml @@ -11,6 +11,7 @@ + - + + + - - - + app:nullable="true" /> + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/profile_nav_graph.xml b/app/src/main/res/navigation/profile_nav_graph.xml index 8ef07dca..8bff84b8 100644 --- a/app/src/main/res/navigation/profile_nav_graph.xml +++ b/app/src/main/res/navigation/profile_nav_graph.xml @@ -71,11 +71,9 @@ app:argType="long" /> - - + app:destination="@id/notificationsViewer"> + + + + + Cerca actualitzacions a l\'inici Block screenshots & app preview Descarrega les publicacions a carpetes de nom d\'usuari + Prepend Username to Filename Marca les històries com a vistes després de visualitzar-es L\'autor de la història sabrà que l\'has vista Marca els missatges com a vists després de visualitzar-los diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 0bb9e0ac..c5bc0f11 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -23,6 +23,7 @@ Zkontrolovat aktualizace při spuštění Block screenshots & app preview Stáhnout příspěvky do složek s uživatelským jménem + Prepend Username to Filename Označit příběhy po zhlédnutí jako zobrazené Autor příběhu bude vědět, že jsi si ho zobrazili Označovat přímou zprávu po zobrazení jako zobrazenou diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 1f541fcd..e023c6b9 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -23,6 +23,7 @@ Beim Start auf Aktualisierungen prüfen Block screenshots & app preview Beiträge in Benutzernamen-Ordner herunterladen + Prepend Username to Filename Stories nach dem Ansehen als gesehen markieren Die Person wird wissen, dass du dir die Story angesehen hast Direktnachrichten nach dem Ansehen als gesehen markieren diff --git a/app/src/main/res/values-el/strings.xml b/app/src/main/res/values-el/strings.xml index f9817a0a..b8c7df56 100644 --- a/app/src/main/res/values-el/strings.xml +++ b/app/src/main/res/values-el/strings.xml @@ -23,6 +23,7 @@ Έλεγχος για ενημερώσεις στο ξεκίνημα Block screenshots & app preview Λήψη δημοσίευσης στους φακέλους με ονόματα χρηστών + Prepend Username to Filename Επισήμανση ιστοριών ως προβληθέντων μετά την προβολή Ο συντάκτης της ιστορίας θα ξέρει ότι την προβάλατε Σήμανση ΠΜ ως αναγνωσμένου μετά την προβολή diff --git a/app/src/main/res/values-es/strings.xml b/app/src/main/res/values-es/strings.xml index f392ec86..9ee7d1d0 100755 --- a/app/src/main/res/values-es/strings.xml +++ b/app/src/main/res/values-es/strings.xml @@ -23,6 +23,7 @@ Buscar actualizaciones al inicio Bloquea capturas de pantalla & vista previa de aplicaciones Usar subcarpetas con el nombre de usuario + Prepend Username to Filename Marcar historias como vistas después de verlas El autor de la historia sabrá que lo has visto Marcar Mensaje Directo como visto después de verlo diff --git a/app/src/main/res/values-eu/strings.xml b/app/src/main/res/values-eu/strings.xml index dd0321e2..8f217f17 100644 --- a/app/src/main/res/values-eu/strings.xml +++ b/app/src/main/res/values-eu/strings.xml @@ -23,6 +23,7 @@ Bilatu eguneratzeak abioan Block screenshots & app preview Deskargatu bidalketak erabiltzaile-izena duten karpetetara + Prepend Username to Filename Markatu istorioak ikusita gisa ikusi ondoren Istorioaren egileak ikusi duzula jakingo du Markatu MZ ikusita gisa ikusi ondoren diff --git a/app/src/main/res/values-fa/strings.xml b/app/src/main/res/values-fa/strings.xml index c6da89cf..75371835 100644 --- a/app/src/main/res/values-fa/strings.xml +++ b/app/src/main/res/values-fa/strings.xml @@ -23,6 +23,7 @@ بررسی بروزرسانی هنگام آغاز برنامه Block screenshots & app preview بارگیری پست ها در پوشه های به نام کاربر + Prepend Username to Filename نشان کرد استوری ها به عنوان دیده شده بعد از دیدن نویسنده استوری می داند که شما آن را دیده اید نشان کردن پیام خصوصی بعنوان دیده شده بعد از دیدن diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 15f8a5cc..d2fd93dd 100755 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -23,6 +23,7 @@ Rechercher les mises à jours au démarrage Bloquer les captures d\'écran & l\'aperçu de l\'application Télécharger les messages dans les dossiers des noms d\'utilisateurs + Prepend Username to Filename Marquer les stories comme vues après consultation L\'auteur de la story saura que vous l\'avez vue Marquer les messages privés comme vus après consultation diff --git a/app/src/main/res/values-hi/strings.xml b/app/src/main/res/values-hi/strings.xml index bc923135..623bc2ce 100644 --- a/app/src/main/res/values-hi/strings.xml +++ b/app/src/main/res/values-hi/strings.xml @@ -23,6 +23,7 @@ खुलने पर अपडेट के लिए जाँच करें Block screenshots & app preview पोस्ट को ब्यबहारकारी के नाम पर किये फोल्डरस में रखें + Prepend Username to Filename स्टोरि को दिखने के बाद \"दिखा गया\" दिखादें सटोरि के लेखक जानेगा कि तुम देखे हो इसको तुम देखने के बाद सीधा संदेश को \"दिखागया\" लिखा जाएगा diff --git a/app/src/main/res/values-in/strings.xml b/app/src/main/res/values-in/strings.xml index 462c1a2e..6801759b 100644 --- a/app/src/main/res/values-in/strings.xml +++ b/app/src/main/res/values-in/strings.xml @@ -23,6 +23,7 @@ Cek pembaruan saat memulai Block screenshots & app preview Unduh kiriman ke folder nama pengguna + Prepend Username to Filename Tandai cerita dibaca setelah melihat Pembuat cerita akan tahu Anda melihatnya Tandai DM dibaca setelah melihat diff --git a/app/src/main/res/values-it/strings.xml b/app/src/main/res/values-it/strings.xml index d8202389..eefead25 100755 --- a/app/src/main/res/values-it/strings.xml +++ b/app/src/main/res/values-it/strings.xml @@ -23,6 +23,7 @@ Verifica per aggiornamenti all\'avvio Blocca screenshot & anteprima app Scarica i post nelle cartelle del nome utente + Prepend Username to Filename Segna le storie come viste dopo la visualizzazione L\'autore della storia saprà che l\'hai visualizzata Segna il DM come visto dopo la visualizzazione diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 5a11dc06..178f3726 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -23,6 +23,7 @@ 起動時にアップデートを確認 Block screenshots & app preview ユーザ名のフォルダに投稿をダウンロード + Prepend Username to Filename ストーリーズを表示後に既読にする ストーリーの作成者は、あなたが閲覧したことを知ることができます。 DMを表示後に既読にする diff --git a/app/src/main/res/values-kn/strings.xml b/app/src/main/res/values-kn/strings.xml index 80d8b915..85c0390c 100644 --- a/app/src/main/res/values-kn/strings.xml +++ b/app/src/main/res/values-kn/strings.xml @@ -23,6 +23,7 @@ Check for updates at startup Block screenshots & app preview Download posts to username folders + Prepend Username to Filename Mark stories as seen after viewing Story author will know you viewed it Mark DM as seen after viewing diff --git a/app/src/main/res/values-mk/strings.xml b/app/src/main/res/values-mk/strings.xml index abb89551..118e07f4 100644 --- a/app/src/main/res/values-mk/strings.xml +++ b/app/src/main/res/values-mk/strings.xml @@ -23,6 +23,7 @@ Провери за ажурирање Block screenshots & app preview Превземи објави во папката со кориснички имиња + Prepend Username to Filename Означи ги приказните како видени Авторот на приказната ќе знае дека сте ја погледнале приказната Означи порака како видена diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml index fda4c863..b904bed6 100644 --- a/app/src/main/res/values-nl/strings.xml +++ b/app/src/main/res/values-nl/strings.xml @@ -23,6 +23,7 @@ Controleer op updates bij het opstarten Block screenshots & app preview Download berichten naar gebruikersnaam mappen + Prepend Username to Filename Markeer verhalen als gelezen na bekijken Verhaalmaker zal het weten als je het bekeken hebt Markeer privéberichten als gelezen na bekijken diff --git a/app/src/main/res/values-or/strings.xml b/app/src/main/res/values-or/strings.xml index ce4427ec..12b95d7b 100644 --- a/app/src/main/res/values-or/strings.xml +++ b/app/src/main/res/values-or/strings.xml @@ -23,6 +23,7 @@ ଖୋଲିବା ସମୟରେ ଅପଡେଟ ପାଇଁ ଯାଞ୍ଚ କରନ୍ତୁ Block screenshots & app preview ଡାଉନଲୋଡ ପୋଷ୍ଟକୁ ବ୍ୟବହାରକାରୀଙ୍କ ନାମରେ ହୋଇଥିବା ସ୍ଥାନ ରେ ରଖ + Prepend Username to Filename କାହାଣୀଗୁଡିକ ଦେଖିବା ପରେ \'ଦେଖାଗଲା\' ଚିହ୍ନିତ କରନ୍ତୁ | କାହାଣୀ ପ୍ରେରକ ଜାଣିବେ ତୁମେ ଏହାକୁ ଦେଖିଛ ବାର୍ତା ଦେଖିବା ପରେ \'ଦେଖାଗଲା\' ଚିହ୍ନିତ କରନ୍ତୁ | diff --git a/app/src/main/res/values-pl/strings.xml b/app/src/main/res/values-pl/strings.xml index 93fb3ff8..2c449634 100644 --- a/app/src/main/res/values-pl/strings.xml +++ b/app/src/main/res/values-pl/strings.xml @@ -23,6 +23,7 @@ Sprawdź aktualizacje przy starcie Blokuj zrzuty ekranu & podgląd aplikacji Pobierz posty do folderów o nazwie użytkownika + Prepend Username to Filename Oznacz relacje jako widoczne po wyświetleniu Autor relacji będzie widział, że to wyświetliłeś Oznacz wiadomość jako przeczytaną diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index 4c7bbbc4..f8c3f401 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -23,6 +23,7 @@ Verificar se há atualizações ao iniciar Block screenshots & app preview Baixar publicações para pastas com o nome de usuário + Prepend Username to Filename Marcar stories como vistos após a visualização O autor do story saberá que você viu Marcar DM como vista após a visualização diff --git a/app/src/main/res/values-ru/strings.xml b/app/src/main/res/values-ru/strings.xml index b491a1c5..ca3155ef 100644 --- a/app/src/main/res/values-ru/strings.xml +++ b/app/src/main/res/values-ru/strings.xml @@ -23,6 +23,7 @@ Проверять наличие обновлений при запуске Block screenshots & app preview Скачать публикации в папки с именем пользователя + Prepend Username to Filename Отметить истории как увиденные после просмотра Автор истории узнает, что вы просмотрели её Отметить ЛС как увиденные после просмотра diff --git a/app/src/main/res/values-sk/strings.xml b/app/src/main/res/values-sk/strings.xml index f12178ec..7b1c175a 100644 --- a/app/src/main/res/values-sk/strings.xml +++ b/app/src/main/res/values-sk/strings.xml @@ -23,6 +23,7 @@ Kontrolovať aktualizácie pri štarte Block screenshots & app preview Ukľadať do priečinkov podľa mena + Prepend Username to Filename Označiť príbehy po videní ako videné Autor príbehu bude vedieť že ste ho videli Po prečítaní, označiť správu ako prečítanú diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 4ca6836d..fea3a299 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -23,6 +23,7 @@ Güncellemeleri başlangıçta kontrol et Block screenshots & app preview İndirmeleri kullanıcı adından oluşan bir alt klasörün içine yap + Prepend Username to Filename Hikayeleri gördükten sonra görüldü olarak işaretle Hikayeyi paylaşan gördüğünüzü bilecek DM\'leri gördükten sonra görüldü olarak işaretle diff --git a/app/src/main/res/values-vi/strings.xml b/app/src/main/res/values-vi/strings.xml index debb92fc..858193f6 100644 --- a/app/src/main/res/values-vi/strings.xml +++ b/app/src/main/res/values-vi/strings.xml @@ -23,6 +23,7 @@ Kiểm tra cập nhật khi khởi động Block screenshots & app preview Tải bài viết xuống theo thư mục tên người dùng trong Downloads + Prepend Username to Filename Đánh dấu story là đã xem sau khi xem Người đăng story sẽ biết bạn đã xem nó Đánh dấu DM là đã xem sau khi xem diff --git a/app/src/main/res/values-zh-rCN/strings.xml b/app/src/main/res/values-zh-rCN/strings.xml index a6fe9c77..0fb2193f 100644 --- a/app/src/main/res/values-zh-rCN/strings.xml +++ b/app/src/main/res/values-zh-rCN/strings.xml @@ -23,6 +23,7 @@ 启动时检查更新 屏蔽截图及应用预览 下载帖子到用户名文件夹 + Prepend Username to Filename 查看快拍后将其标记为已读 快拍作者会知道您已看过 查看私信后将其标记为已读 diff --git a/app/src/main/res/values-zh-rTW/strings.xml b/app/src/main/res/values-zh-rTW/strings.xml index 26371e94..3ce706fa 100644 --- a/app/src/main/res/values-zh-rTW/strings.xml +++ b/app/src/main/res/values-zh-rTW/strings.xml @@ -23,6 +23,7 @@ 啟動時檢查更新 Block screenshots & app preview 將貼文下載到用戶名資料夾 + Prepend Username to Filename 檢視完限時動態後標記為已讀 限時動態的作者會知道您已查看了此限時動態 檢視完訊息後標記為已讀 diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 0ba42065..d01e496f 100755 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -88,24 +88,61 @@ HH:mm:ss H:mm:ss - + + @id/direct_messages_nav_graph + @id/feed_nav_graph + @id/profile_nav_graph + @id/discover_nav_graph + @id/more_nav_graph + + @id/favorites_nav_graph + @id/notification_viewer_nav_graph + + + @navigation/direct_messages_nav_graph @navigation/feed_nav_graph @navigation/profile_nav_graph @navigation/discover_nav_graph @navigation/more_nav_graph + @navigation/favorites_nav_graph + @navigation/notification_viewer_nav_graph - + + @string/title_dm @string/feed @string/profile @string/title_discover @string/more + @string/title_favorites + @string/title_notifications - - @navigation/profile_nav_graph - @navigation/more_nav_graph + + + @drawable/ic_message_24 + @drawable/ic_home_24 + @drawable/ic_person_24 + @drawable/ic_explore_24 + @drawable/ic_more_horiz_24 + @drawable/ic_star_24 + @drawable/ic_not_liked + + + + @id/directMessagesInboxFragment + @id/feedFragment + @id/profileFragment + @id/discoverFragment + @id/morePreferencesFragment + @id/favoritesFragment + @id/notificationsViewer + + + + + @string/light_white_theme @string/light_barinsta_theme diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index bd79862c..3adc84b8 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -26,8 +26,10 @@ Check for updates at startup Block screenshots & app preview Download posts to username folders + Prepend Username to Filename Mark stories as seen after viewing Story author will know you viewed it + Hide muted stories from feed Mark DM as seen after viewing Other members will know you viewed it Enable activity notifications @@ -473,8 +475,22 @@ Removed keyword: %s from filter list Marked as seen Delete unsuccessful + Throttled by Instagram because of too many API requests. Wait for some time before retrying. + Error + This account has been logged out. + Login required! + Sentry block. + User is inactive! Barinsta Crash Report Select an email app to send crash logs + Not found! + Your IP has been rate limited by Instagram. Wait for an hour and try again. + Skip this update + You\'re already on the latest version + Screen order + Other tabs + The tab order will be reflected on next launch + If saved, all DM related features will be disabled on next launch Copy caption Copy reply Restore diff --git a/app/src/main/resources/feed_response.json b/app/src/main/resources/feed_response.json deleted file mode 100644 index 9e26dfee..00000000 --- a/app/src/main/resources/feed_response.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file diff --git a/app/src/main/resources/stories_response.json b/app/src/main/resources/stories_response.json deleted file mode 100644 index 9e26dfee..00000000 --- a/app/src/main/resources/stories_response.json +++ /dev/null @@ -1 +0,0 @@ -{} \ No newline at end of file