diff --git a/.all-contributorsrc b/.all-contributorsrc index c0f4f8aa..927b5cba 100644 --- a/.all-contributorsrc +++ b/.all-contributorsrc @@ -4,6 +4,7 @@ ], "imageSize": 100, "commit": false, + "badgeTemplate": "[![All Contributors](https://img.shields.io/badge/all_contributors-<%= contributors.length %>-orange.svg)](#contributors)", "contributors": [ { "login": "austinhuang0131", @@ -79,8 +80,7 @@ "blog", "bug", "ideas", - "question", - "userTesting" + "question" ] }, { @@ -89,8 +89,18 @@ "avatar_url": "https://avatars0.githubusercontent.com/u/53869451", "profile": "https://airikr.me/", "contributions": [ - "question", - "ideas" + "ideas", + "question" + ] + }, + { + "login": "Akrai", + "name": "Akrai", + "avatar_url": "https://avatars1.githubusercontent.com/u/5624597?v=4", + "profile": "https://github.com/Akrai", + "contributions": [ + "ideas", + "translation" ] }, { @@ -111,6 +121,15 @@ "translation" ] }, + { + "login": "faydin", + "name": "Fatih Aydın", + "avatar_url": "https://avatars2.githubusercontent.com/u/22706676?v=4", + "profile": "https://github.com/faydin", + "contributions": [ + "translation" + ] + }, { "login": "kernoeb", "name": "kernoeb", @@ -167,10 +186,17 @@ } ], "contributorsPerLine": 6, - "projectName": "instagrabber", + "projectName": "barinsta", "projectOwner": "austinhuang0131", "repoType": "github", "repoHost": "https://github.com", "skipCi": true, + "types": { + "translation": { + "symbol": "🌍", + "description": "Translation", + "link": "https://crowdin.com/project/instagrabber" + } + }, "commitConvention": "none" -} +} \ No newline at end of file diff --git a/.gitignore b/.gitignore index 57870dac..4d79357f 100755 --- a/.gitignore +++ b/.gitignore @@ -9,11 +9,11 @@ /.idea/workspace.xml /.idea/navEditor.xml /.idea/assetWizardSettings.xml +/.idea/git_toolbox_prj.xml +/.idea/dbnavigator.xml .DS_Store /build /captures .externalNativeBuild .cxx app/release - -.idea/git_toolbox_prj.xml diff --git a/README.md b/README.md index d904bba3..cf06c689 100755 --- a/README.md +++ b/README.md @@ -6,7 +6,9 @@ [![Open Source Love](https://badges.frapsoft.com/os/v3/open-source.svg?v=103)](https://github.com/ellerbrock/open-source-badges/) [![PRs Welcome](https://img.shields.io/badge/PRs-welcome-brightgreen.svg)](http://makeapullrequest.com) [![GPLv3 license](https://img.shields.io/badge/License-GPLv3-blue.svg)](./LICENSE) -[![GitHub stars](https://img.shields.io/github/stars/austinhuang0131/instagrabber.svg?style=social&label=Star)](https://GitHub.com/austinhuang0131/barinsta/stargazers/) +[![GitHub stars](https://img.shields.io/github/stars/austinhuang0131/instagrabber.svg?style=social&label=Star)](https://GitHub.com/austinhuang0131/barinsta/stargazers/) +[![All Contributors](https://img.shields.io/badge/all_contributors-18-orange.svg)](#contributors) + We're previously known as InstaGrabber and still in process of rebranding. For documentation, visit [InstaGrabber.AustinHuang.me](https://instagrabber.austinhuang.me). @@ -34,37 +36,35 @@ Version status: ![F-Droid](https://img.shields.io/f-droid/v/me.austinhuang.insta ### Contributors - -[![All Contributors](https://img.shields.io/badge/all_contributors-16-orange.svg?style=flat-square)](#contributors-) - - -Prominent contributors are listed here in the [all-contributors](https://allcontributors.org/) specifications. See [emoji key](https://allcontributors.org/docs/en/emoji-key). +Prominent contributors are listed here in the [all-contributors](https://allcontributors.org/) specifications, see [emoji key](https://allcontributors.org/docs/en/emoji-key). [Want to contribute to Barinsta?](https://github.com/austinhuang0131/barinsta/blob/master/.github/CONTRIBUTING.md) - - - - - + + + + + - - - - - - + + + + + + - - - - + + + + + +

Austin Huang

πŸ’» πŸ“– πŸ’¬ 🌍 πŸ€”

Ammar Githam

πŸ’» 🎨 πŸ€” 🚧 πŸ’¬

Anderson Mesquita

πŸ’» πŸ›

AWAiS

πŸ’» πŸ€”

Stefan Najdovski

🎨 🌍

Austin Huang

πŸ’» πŸ“– πŸ’¬ 🌍 πŸ€”

Ammar Githam

πŸ’» 🎨 πŸ€” 🚧 πŸ’¬

Anderson Mesquita

πŸ’» πŸ›

AWAiS

πŸ’» πŸ€”

Stefan Najdovski

🎨 🌍

Kevin Thomas

πŸ’΅

Shadowspear123

πŸ“ πŸ› πŸ€” πŸ’¬ πŸ““

Airikr

πŸ’¬ πŸ€”

Galang23

🌍

farzadx

🌍

kernoeb

🌍

Ten_Lego

🌍

Shadowspear123

πŸ“ πŸ› πŸ€” πŸ’¬

Airikr

πŸ€” πŸ’¬

Akrai

πŸ€” 🌍

Galang23

🌍

farzadx

🌍

Fatih AydΔ±n

🌍

MoaufmKlo

🌍

peterge1998

🌍

RAMAR-RAR

🌍

wagnim

🌍

kernoeb

🌍

Ten_Lego

🌍

MoaufmKlo

🌍

peterge1998

🌍

RAMAR-RAR

🌍

wagnim

🌍
@@ -99,7 +99,7 @@ Logo by [Stefan Najdovski](https://stefannajdovski.com/). Used under license. [![Snyk Vulnerabilities](https://img.shields.io/snyk/vulnerabilities/github/austinhuang0131/instagrabber)](https://snyk.io/test/github/austinhuang0131/instagrabber) [![LGTM Alerts](https://img.shields.io/lgtm/alerts/github/austinhuang0131/instagrabber)](https://lgtm.com/projects/g/austinhuang0131/instagrabber) [![LGTM Grade](https://img.shields.io/lgtm/grade/java/github/austinhuang0131/instagrabber)](https://lgtm.com/projects/g/austinhuang0131/instagrabber) -[![CodeFactor](https://www.codefactor.io/repository/github/austinhuang0131/instagrabber/badge)](https://www.codefactor.io/repository/github/austinhuang0131/instagrabber) +[![CodeFactor](https://www.codefactor.io/repository/github/austinhuang0131/barinsta/badge)](https://www.codefactor.io/repository/github/austinhuang0131/barinsta) [![Codacy Badge](https://app.codacy.com/project/badge/Grade/e9cfcb7733f8477d92e5c0f30cac137a)](https://www.codacy.com/manual/austinhuang0131/instagrabber) [![Crowdin](https://badges.crowdin.net/instagrabber/localized.svg)](https://crowdin.com/project/instagrabber) diff --git a/app/build.gradle b/app/build.gradle index 9eb639d7..3fa4bc3f 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -7,11 +7,11 @@ android { defaultConfig { applicationId 'me.austinhuang.instagrabbr' - minSdkVersion 16 + minSdkVersion 21 targetSdkVersion 29 - versionCode 50 - versionName '19.0-a1' + versionCode 51 + versionName '19.0-a2' multiDexEnabled true @@ -41,7 +41,7 @@ dependencies { def nav_version = "2.3.0" def preference_version = "1.1.1" - implementation 'com.google.android.material:material:1.2.1' + implementation 'com.google.android.material:material:1.3.0-alpha02' implementation 'com.google.android.exoplayer:exoplayer:2.11.1' implementation "androidx.appcompat:appcompat:$appcompat_version" @@ -54,6 +54,7 @@ dependencies { implementation "androidx.constraintlayout:constraintlayout:2.0.1" implementation "androidx.preference:preference:$preference_version" +// implementation 'com.github.hendrawd:StorageUtil:1.1.0' implementation 'org.jsoup:jsoup:1.13.1' implementation 'com.facebook.fresco:fresco:2.3.0' diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 23bc499b..d0847f2f 100755 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -21,7 +21,7 @@ android:name=".activities.MainActivity" android:launchMode="singleTop" android:taskAffinity=".Main" - android:windowSoftInputMode="adjustResize"> + android:windowSoftInputMode="adjustPan"> diff --git a/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java b/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java index dbff1a60..827db5ea 100644 --- a/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java +++ b/app/src/main/java/awais/instagrabber/InstaGrabberApplication.java @@ -1,11 +1,10 @@ package awais.instagrabber; +import android.app.Application; import android.content.ClipboardManager; import android.content.Context; import android.util.Log; -import androidx.multidex.MultiDexApplication; - import com.facebook.drawee.backends.pipeline.Fresco; import com.facebook.imagepipeline.core.ImagePipelineConfig; @@ -27,7 +26,7 @@ import static awais.instagrabber.utils.Utils.datetimeParser; import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.settingsHelper; -public final class InstaGrabberApplication extends MultiDexApplication { +public final class InstaGrabberApplication extends Application { private static final String TAG = "InstaGrabberApplication"; @Override diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.java b/app/src/main/java/awais/instagrabber/activities/MainActivity.java index 7f9338b8..79439bdc 100644 --- a/app/src/main/java/awais/instagrabber/activities/MainActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.java @@ -1,6 +1,5 @@ package awais.instagrabber.activities; - import android.annotation.SuppressLint; import android.app.NotificationChannel; import android.app.NotificationManager; @@ -29,6 +28,7 @@ import androidx.appcompat.widget.SearchView; import androidx.appcompat.widget.Toolbar; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.app.NotificationManagerCompat; +import androidx.fragment.app.FragmentManager; import androidx.lifecycle.LiveData; import androidx.navigation.NavBackStackEntry; import androidx.navigation.NavController; @@ -38,6 +38,7 @@ import androidx.navigation.ui.NavigationUI; import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.appbar.CollapsingToolbarLayout; +import com.google.android.material.bottomnavigation.BottomNavigationView; import java.util.ArrayList; import java.util.Arrays; @@ -67,7 +68,7 @@ import awais.instagrabber.utils.TextUtils; import static awais.instagrabber.utils.NavigationExtensions.setupWithNavController; import static awais.instagrabber.utils.Utils.settingsHelper; -public class MainActivity extends BaseLanguageActivity { +public class MainActivity extends BaseLanguageActivity implements FragmentManager.OnBackStackChangedListener { private static final String TAG = "MainActivity"; private static final List SHOW_BOTTOM_VIEW_DESTINATIONS = Arrays.asList( @@ -91,7 +92,9 @@ public class MainActivity extends BaseLanguageActivity { R.id.followViewerFragment, R.id.directMessagesSettingsFragment, R.id.notificationsViewer, - R.id.themePreferencesFragment); + R.id.themePreferencesFragment, + R.id.favoritesFragment, + R.id.backupPreferencesFragment); private static final Map NAV_TO_MENU_ID_MAP = new HashMap<>(); private static final List REMOVE_COLLAPSING_TOOLBAR_SCROLL_DESTINATIONS = Collections.singletonList(R.id.commentsViewerFragment); private static final String FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex"; @@ -106,6 +109,7 @@ public class MainActivity extends BaseLanguageActivity { private Handler suggestionsFetchHandler; private int firstFragmentGraphIndex; private boolean isActivityCheckerServiceBound = false; + private boolean isBackStackEmpty = false; private final ServiceConnection serviceConnection = new ServiceConnection() { @Override @@ -152,6 +156,11 @@ public class MainActivity extends BaseLanguageActivity { if (!TextUtils.isEmpty(cookie) && settingsHelper.getBoolean(Constants.CHECK_ACTIVITY)) { bindActivityCheckerService(); } + getSupportFragmentManager().addOnBackStackChangedListener(this); + + // Log.d("austin_debug", "dir: "+Arrays.toString(StorageUtil.getStorageDirectories(getApplicationContext()))); + // final File sdcard = new File(StorageUtil.getStorageDirectories(getApplicationContext())[0]); + // Log.d("austin_debug", "files: "+Arrays.toString(sdcard.listFiles())); } @Override @@ -211,6 +220,21 @@ public class MainActivity extends BaseLanguageActivity { unbindActivityCheckerService(); } + @Override + public void onBackPressed() { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP && isTaskRoot() && isBackStackEmpty) { + finishAfterTransition(); + } else { + super.onBackPressed(); + } + } + + @Override + public void onBackStackChanged() { + final int backStackEntryCount = getSupportFragmentManager().getBackStackEntryCount(); + isBackStackEmpty = backStackEntryCount == 0; + } + private void createNotificationChannels() { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { final NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getApplicationContext()); @@ -367,32 +391,26 @@ public class MainActivity extends BaseLanguageActivity { final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null; 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 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(); - if (setDefaultFromSettings || !isLoggedIn) { + final List mainNavList = getMainNavList(main_nav_ids); + if (setDefaultFromSettings) { final String defaultTabIdString = settingsHelper.getString(Constants.DEFAULT_TAB); try { - final int defaultNavId = TextUtils.isEmpty(defaultTabIdString) || !isLoggedIn + final int defaultNavId = TextUtils.isEmpty(defaultTabIdString) ? R.navigation.profile_nav_graph : Integer.parseInt(defaultTabIdString); final int index = mainNavList.indexOf(defaultNavId); - if (index >= 0) { - firstFragmentGraphIndex = index; - final Integer menuId = NAV_TO_MENU_ID_MAP.get(defaultNavId); - if (menuId != null) { - binding.bottomNavView.setSelectedItemId(menuId); - } - } + if (index >= 0) firstFragmentGraphIndex = index; + setBottomNavSelectedItem(defaultNavId); } catch (NumberFormatException e) { Log.e(TAG, "Error parsing id", e); } @@ -408,6 +426,27 @@ public class MainActivity extends BaseLanguageActivity { currentNavControllerLiveData = navControllerLiveData; } + private void setBottomNavSelectedItem(final int navId) { + final Integer menuId = NAV_TO_MENU_ID_MAP.get(navId); + if (menuId != null) { + binding.bottomNavView.setSelectedItemId(menuId); + } + } + + @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 NavController navController) { NavigationUI.setupWithNavController(binding.toolbar, navController); navController.addOnDestinationChangedListener((controller, destination, arguments) -> { @@ -582,4 +621,9 @@ public class MainActivity extends BaseLanguageActivity { unbindService(serviceConnection); isActivityCheckerServiceBound = false; } + + @NonNull + public BottomNavigationView getBottomNavView() { + return binding.bottomNavView; + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/DirectoryFilesAdapter.java b/app/src/main/java/awais/instagrabber/adapters/DirectoryFilesAdapter.java new file mode 100644 index 00000000..13fca194 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/adapters/DirectoryFilesAdapter.java @@ -0,0 +1,75 @@ +package awais.instagrabber.adapters; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; +import androidx.recyclerview.widget.RecyclerView; + +import java.io.File; + +import awais.instagrabber.R; +import awais.instagrabber.databinding.ItemDirListBinding; + +public final class DirectoryFilesAdapter extends ListAdapter { + private final OnFileClickListener onFileClickListener; + + private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(@NonNull final File oldItem, @NonNull final File newItem) { + return oldItem.getAbsolutePath().equals(newItem.getAbsolutePath()); + } + + @Override + public boolean areContentsTheSame(@NonNull final File oldItem, @NonNull final File newItem) { + return oldItem.getAbsolutePath().equals(newItem.getAbsolutePath()); + } + }; + + public DirectoryFilesAdapter(final OnFileClickListener onFileClickListener) { + super(DIFF_CALLBACK); + this.onFileClickListener = onFileClickListener; + } + + @NonNull + @Override + public ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { + final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + final ItemDirListBinding binding = ItemDirListBinding.inflate(inflater, parent, false); + return new ViewHolder(binding); + } + + @Override + public void onBindViewHolder(@NonNull final ViewHolder holder, final int position) { + final File file = getItem(position); + holder.bind(file, onFileClickListener); + } + + public interface OnFileClickListener { + void onFileClick(File file); + } + + static final class ViewHolder extends RecyclerView.ViewHolder { + private final ItemDirListBinding binding; + + private ViewHolder(final ItemDirListBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + + public void bind(final File file, final OnFileClickListener onFileClickListener) { + if (file == null) return; + if (onFileClickListener != null) { + itemView.setOnClickListener(v -> onFileClickListener.onFileClick(file)); + } + binding.text.setText(file.getName()); + if (file.isDirectory()) { + binding.icon.setImageResource(R.drawable.ic_folder_24); + return; + } + binding.icon.setImageResource(R.drawable.ic_file_24); + } + } +} diff --git a/app/src/main/java/awais/instagrabber/adapters/FavoritesAdapter.java b/app/src/main/java/awais/instagrabber/adapters/FavoritesAdapter.java new file mode 100644 index 00000000..4a4f47fb --- /dev/null +++ b/app/src/main/java/awais/instagrabber/adapters/FavoritesAdapter.java @@ -0,0 +1,202 @@ +package awais.instagrabber.adapters; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.util.ObjectsCompat; +import androidx.recyclerview.widget.AdapterListUpdateCallback; +import androidx.recyclerview.widget.AsyncDifferConfig; +import androidx.recyclerview.widget.AsyncListDiffer; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import awais.instagrabber.R; +import awais.instagrabber.adapters.viewholder.FavoriteViewHolder; +import awais.instagrabber.databinding.ItemFavSectionHeaderBinding; +import awais.instagrabber.databinding.ItemSuggestionBinding; +import awais.instagrabber.models.enums.FavoriteType; +import awais.instagrabber.utils.DataBox; + +public class FavoritesAdapter extends RecyclerView.Adapter { + + private final OnFavoriteClickListener clickListener; + private final OnFavoriteLongClickListener longClickListener; + private final AsyncListDiffer differ; + + private static final DiffUtil.ItemCallback diffCallback = new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(@NonNull final FavoriteModelOrHeader oldItem, @NonNull final FavoriteModelOrHeader newItem) { + boolean areSame = oldItem.isHeader() && newItem.isHeader(); + if (!areSame) { + return false; + } + if (oldItem.isHeader()) { + return ObjectsCompat.equals(oldItem.header, newItem.header); + } + if (oldItem.model != null && newItem.model != null) { + return oldItem.model.getId() == newItem.model.getId(); + } + return false; + } + + @Override + public boolean areContentsTheSame(@NonNull final FavoriteModelOrHeader oldItem, @NonNull final FavoriteModelOrHeader newItem) { + boolean areSame = oldItem.isHeader() && newItem.isHeader(); + if (!areSame) { + return false; + } + if (oldItem.isHeader()) { + return ObjectsCompat.equals(oldItem.header, newItem.header); + } + return ObjectsCompat.equals(oldItem.model, newItem.model); + } + }; + + public FavoritesAdapter(final OnFavoriteClickListener clickListener, final OnFavoriteLongClickListener longClickListener) { + this.clickListener = clickListener; + this.longClickListener = longClickListener; + differ = new AsyncListDiffer<>(new AdapterListUpdateCallback(this), + new AsyncDifferConfig.Builder<>(diffCallback).build()); + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { + final LayoutInflater inflater = LayoutInflater.from(parent.getContext()); + if (viewType == 0) { + // header + return new FavSectionViewHolder(ItemFavSectionHeaderBinding.inflate(inflater, parent, false)); + } + final ItemSuggestionBinding binding = ItemSuggestionBinding.inflate(inflater, parent, false); + return new FavoriteViewHolder(binding); + } + + @Override + public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) { + if (getItemViewType(position) == 0) { + final FavoriteModelOrHeader modelOrHeader = getItem(position); + if (!modelOrHeader.isHeader()) return; + ((FavSectionViewHolder) holder).bind(modelOrHeader.header); + return; + } + ((FavoriteViewHolder) holder).bind(getItem(position).model, clickListener, longClickListener); + } + + protected FavoriteModelOrHeader getItem(int position) { + return differ.getCurrentList().get(position); + } + + @Override + public int getItemCount() { + return differ.getCurrentList().size(); + } + + @Override + public int getItemViewType(final int position) { + return getItem(position).isHeader() ? 0 : 1; + } + + public void submitList(@Nullable final List list) { + if (list == null) { + differ.submitList(null); + return; + } + differ.submitList(sectionAndSort(list)); + } + + public void submitList(@Nullable final List list, @Nullable final Runnable commitCallback) { + if (list == null) { + differ.submitList(null, commitCallback); + return; + } + differ.submitList(sectionAndSort(list), commitCallback); + } + + @NonNull + private List sectionAndSort(@NonNull final List list) { + final List listCopy = new ArrayList<>(list); + Collections.sort(listCopy, (o1, o2) -> { + if (o1.getType() == o2.getType()) return 0; + // keep users at top + if (o1.getType() == FavoriteType.USER) return -1; + if (o2.getType() == FavoriteType.USER) return 1; + // keep locations at bottom + if (o1.getType() == FavoriteType.LOCATION) return 1; + if (o2.getType() == FavoriteType.LOCATION) return -1; + return 0; + }); + final List modelOrHeaders = new ArrayList<>(); + for (int i = 0; i < listCopy.size(); i++) { + final DataBox.FavoriteModel model = listCopy.get(i); + final FavoriteModelOrHeader prev = modelOrHeaders.isEmpty() ? null : modelOrHeaders.get(modelOrHeaders.size() - 1); + boolean prevWasSameType = prev != null && prev.model.getType() == model.getType(); + if (prevWasSameType) { + // just add model + final FavoriteModelOrHeader modelOrHeader = new FavoriteModelOrHeader(); + modelOrHeader.model = model; + modelOrHeaders.add(modelOrHeader); + continue; + } + // add header and model + FavoriteModelOrHeader modelOrHeader = new FavoriteModelOrHeader(); + modelOrHeader.header = model.getType(); + modelOrHeaders.add(modelOrHeader); + modelOrHeader = new FavoriteModelOrHeader(); + modelOrHeader.model = model; + modelOrHeaders.add(modelOrHeader); + } + return modelOrHeaders; + } + + private static class FavoriteModelOrHeader { + FavoriteType header; + DataBox.FavoriteModel model; + + boolean isHeader() { + return header != null; + } + } + + public interface OnFavoriteClickListener { + void onClick(final DataBox.FavoriteModel model); + } + + public interface OnFavoriteLongClickListener { + boolean onLongClick(final DataBox.FavoriteModel model); + } + + public static class FavSectionViewHolder extends RecyclerView.ViewHolder { + private final ItemFavSectionHeaderBinding binding; + + public FavSectionViewHolder(@NonNull final ItemFavSectionHeaderBinding binding) { + super(binding.getRoot()); + this.binding = binding; + } + + public void bind(final FavoriteType header) { + if (header == null) return; + final int headerText; + switch (header) { + case USER: + headerText = R.string.accounts; + break; + case HASHTAG: + headerText = R.string.hashtags; + break; + case LOCATION: + headerText = R.string.locations; + break; + default: + headerText = R.string.unknown; + break; + } + binding.getRoot().setText(headerText); + } + } +} diff --git a/app/src/main/java/awais/instagrabber/adapters/NotificationsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/NotificationsAdapter.java index dac5f477..10e5867a 100644 --- a/app/src/main/java/awais/instagrabber/adapters/NotificationsAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/NotificationsAdapter.java @@ -4,13 +4,19 @@ import android.view.LayoutInflater; import android.view.ViewGroup; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.recyclerview.widget.DiffUtil; import androidx.recyclerview.widget.ListAdapter; +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + import awais.instagrabber.adapters.viewholder.NotificationViewHolder; import awais.instagrabber.databinding.ItemNotificationBinding; import awais.instagrabber.interfaces.MentionClickListener; import awais.instagrabber.models.NotificationModel; +import awais.instagrabber.models.enums.NotificationType; public final class NotificationsAdapter extends ListAdapter { private final OnNotificationClickListener notificationClickListener; @@ -49,6 +55,36 @@ public final class NotificationsAdapter extends ListAdapter list, @Nullable final Runnable commitCallback) { + if (list == null) { + super.submitList(null, commitCallback); + return; + } + super.submitList(sort(list), commitCallback); + } + + @Override + public void submitList(@Nullable final List list) { + if (list == null) { + super.submitList(null); + return; + } + super.submitList(sort(list)); + } + + private List sort(final List list) { + final List listCopy = new ArrayList<>(list); + Collections.sort(listCopy, (o1, o2) -> { + if (o1.getType() == o2.getType()) return 0; + // keep requests at top + if (o1.getType() == NotificationType.REQUEST) return -1; + if (o2.getType() == NotificationType.REQUEST) return 1; + return 0; + }); + return listCopy; + } + public interface OnNotificationClickListener { void onNotificationClick(final NotificationModel model); } diff --git a/app/src/main/java/awais/instagrabber/adapters/SimpleAdapter.java b/app/src/main/java/awais/instagrabber/adapters/SimpleAdapter.java deleted file mode 100755 index bfdbd7b5..00000000 --- a/app/src/main/java/awais/instagrabber/adapters/SimpleAdapter.java +++ /dev/null @@ -1,75 +0,0 @@ -package awais.instagrabber.adapters; - -import android.content.Context; -import android.view.LayoutInflater; -import android.view.View; -import android.view.ViewGroup; -import android.widget.TextView; - -import androidx.annotation.NonNull; -import androidx.recyclerview.widget.RecyclerView; - -import java.util.List; - -import awais.instagrabber.R; -import awais.instagrabber.utils.DataBox; - -public final class SimpleAdapter extends RecyclerView.Adapter { - private List items; - private final LayoutInflater layoutInflater; - private final View.OnClickListener onClickListener; - private final View.OnLongClickListener longClickListener; - - public SimpleAdapter(final Context context, final List items, final View.OnClickListener onClickListener) { - this(context, items, onClickListener, null); - } - - public SimpleAdapter(final Context context, final List items, final View.OnClickListener onClickListener, - final View.OnLongClickListener longClickListener) { - this.layoutInflater = LayoutInflater.from(context); - this.items = items; - this.onClickListener = onClickListener; - this.longClickListener = longClickListener; - } - - public void setItems(final List items) { - this.items = items; - notifyDataSetChanged(); - } - - @NonNull - @Override - public SimpleViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { - return new SimpleViewHolder(layoutInflater. - inflate(R.layout.item_dir_list, parent, false), onClickListener, longClickListener); - } - - @Override - public void onBindViewHolder(@NonNull final SimpleViewHolder holder, final int position) { - final T item = items.get(position); - holder.itemView.setTag(item); - holder.text.setText(item.toString()); - if (item instanceof DataBox.CookieModel && ((DataBox.CookieModel) item).isSelected() || - item instanceof String && ((String) item).toLowerCase().endsWith(".zaai")) - holder.itemView.setBackgroundColor(0xF0_125687); - else - holder.itemView.setBackground(null); - } - - @Override - public int getItemCount() { - return items != null ? items.size() : 0; - } - - static final class SimpleViewHolder extends RecyclerView.ViewHolder { - private final TextView text; - - private SimpleViewHolder(@NonNull final View itemView, final View.OnClickListener onClickListener, - final View.OnLongClickListener longClickListener) { - super(itemView); - text = itemView.findViewById(android.R.id.text1); - itemView.setOnClickListener(onClickListener); - if (longClickListener != null) itemView.setOnLongClickListener(longClickListener); - } - } -} diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/DirectMessageInboxItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/DirectMessageInboxItemViewHolder.java index cd975fa7..95abd782 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/DirectMessageInboxItemViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/DirectMessageInboxItemViewHolder.java @@ -73,7 +73,14 @@ public final class DirectMessageInboxItemViewHolder extends RecyclerView.ViewHol } } binding.tvUsername.setText(model.getThreadTitle()); - final DirectItemModel lastItemModel = itemModels[itemModels.length - 1]; + final int length = itemModels.length; + DirectItemModel lastItemModel = null; + if (length != 0) { + lastItemModel = itemModels[length - 1]; + } + if (lastItemModel == null) { + return; + } final DirectItemType itemType = lastItemModel.getItemType(); // binding.notTextType.setVisibility(itemType != DirectItemType.TEXT ? View.VISIBLE : View.GONE); final Context context = itemView.getContext(); diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/FavoriteViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/FavoriteViewHolder.java new file mode 100644 index 00000000..360525f7 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/FavoriteViewHolder.java @@ -0,0 +1,61 @@ +package awais.instagrabber.adapters.viewholder; + +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import awais.instagrabber.adapters.FavoritesAdapter; +import awais.instagrabber.databinding.ItemSuggestionBinding; +import awais.instagrabber.models.enums.FavoriteType; +import awais.instagrabber.utils.Constants; +import awais.instagrabber.utils.DataBox; + +public class FavoriteViewHolder extends RecyclerView.ViewHolder { + private static final String TAG = "FavoriteViewHolder"; + + private final ItemSuggestionBinding binding; + + public FavoriteViewHolder(@NonNull final ItemSuggestionBinding binding) { + super(binding.getRoot()); + this.binding = binding; + binding.isVerified.setVisibility(View.GONE); + } + + public void bind(final DataBox.FavoriteModel model, + final FavoritesAdapter.OnFavoriteClickListener clickListener, + final FavoritesAdapter.OnFavoriteLongClickListener longClickListener) { + // Log.d(TAG, "bind: " + model); + if (model == null) return; + itemView.setOnClickListener(v -> { + if (clickListener == null) return; + clickListener.onClick(model); + }); + itemView.setOnLongClickListener(v -> { + if (clickListener == null) return false; + return longClickListener.onLongClick(model); + }); + if (model.getType() == FavoriteType.HASHTAG) { + binding.ivProfilePic.setImageURI(Constants.DEFAULT_HASH_TAG_PIC); + } else { + binding.ivProfilePic.setImageURI(model.getPicUrl()); + } + binding.tvFullName.setText(model.getDisplayName()); + binding.tvUsername.setVisibility(View.VISIBLE); + String query = model.getQuery(); + switch (model.getType()) { + case HASHTAG: + query = "#" + query; + break; + case USER: + query = "@" + query; + break; + case LOCATION: + binding.tvUsername.setVisibility(View.GONE); + break; + default: + // do nothing + } + binding.tvUsername.setText(query); + } +} diff --git a/app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java index 8f139ff7..855f7f17 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/FeedFetcher.java @@ -212,9 +212,10 @@ public final class FeedFetcher extends AsyncTask { feedModelsList.trimToSize(); final FeedModel[] feedModels = feedModelsList.toArray(new FeedModel[0]); - if (feedModels[feedModels.length - 1] != null) - feedModels[feedModels.length - 1].setPageCursor(hasNextPage, endCursor); - + final int length = feedModels.length; + if (length >= 1 && feedModels[length - 1] != null) { + feedModels[length - 1].setPageCursor(hasNextPage, endCursor); + } result = feedModels; } diff --git a/app/src/main/java/awais/instagrabber/asyncs/HashtagFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/HashtagFetcher.java index 95122ce4..96ff320d 100644 --- a/app/src/main/java/awais/instagrabber/asyncs/HashtagFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/HashtagFetcher.java @@ -8,6 +8,10 @@ import androidx.annotation.Nullable; import org.json.JSONArray; import org.json.JSONObject; +import java.io.BufferedReader; +import java.io.IOException; +import java.io.InputStream; +import java.io.InputStreamReader; import java.net.HttpURLConnection; import java.net.URL; @@ -21,6 +25,8 @@ import awaisomereport.LogCollector; import static awais.instagrabber.utils.Utils.logCollector; public final class HashtagFetcher extends AsyncTask { + private static final String TAG = "HashtagFetcher"; + private final FetchListener fetchListener; private final String hashtag; @@ -35,12 +41,14 @@ public final class HashtagFetcher extends AsyncTask { HashtagModel result = null; try { - final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/explore/tags/" + hashtag + "/?__a=1").openConnection(); + final HttpURLConnection conn = (HttpURLConnection) new URL("https://www.instagram.com/explore/tags/" + hashtag + "/?__a=1") + .openConnection(); conn.setUseCaches(true); conn.connect(); if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { - final JSONObject user = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("graphql").getJSONObject(Constants.EXTRAS_HASHTAG); + final JSONObject user = new JSONObject(NetworkUtils.readFromConnection(conn)).getJSONObject("graphql") + .getJSONObject(Constants.EXTRAS_HASHTAG); final JSONObject timelineMedia = user.getJSONObject("edge_hashtag_to_media"); if (timelineMedia.has("edges")) { @@ -53,13 +61,34 @@ public final class HashtagFetcher extends AsyncTask { user.getString("profile_pic_url"), timelineMedia.getLong("count"), user.optBoolean("is_following")); + } else { + BufferedReader bufferedReader = null; + try { + final InputStream responseInputStream = conn.getErrorStream(); + bufferedReader = new BufferedReader(new InputStreamReader(responseInputStream)); + final StringBuilder builder = new StringBuilder(); + for (String line = bufferedReader.readLine(); line != null; line = bufferedReader.readLine()) { + if (builder.length() != 0) { + builder.append("\n"); + } + builder.append(line); + } + Log.d(TAG, "doInBackground: " + builder.toString()); + } finally { + if (bufferedReader != null) { + try { + bufferedReader.close(); + } catch (IOException ignored) { + } + } + } } conn.disconnect(); } catch (final Exception e) { if (logCollector != null) logCollector.appendException(e, LogCollector.LogFile.ASYNC_HASHTAG_FETCHER, "doInBackground"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); } return result; diff --git a/app/src/main/java/awais/instagrabber/asyncs/HighlightsFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/HighlightsFetcher.java index b1e2868e..acb7b7c1 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/HighlightsFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/HighlightsFetcher.java @@ -19,25 +19,23 @@ import awais.instagrabber.utils.NetworkUtils; public final class HighlightsFetcher extends AsyncTask> { private final String id; - private final boolean storiesig; private final FetchListener> fetchListener; - public HighlightsFetcher(final String id, final boolean storiesig, final FetchListener> fetchListener) { + public HighlightsFetcher(final String id, final FetchListener> fetchListener) { this.id = id; - this.storiesig = storiesig; this.fetchListener = fetchListener; } @Override protected List doInBackground(final Void... voids) { List result = null; - String url = "https://" + (storiesig ? "storiesig" : "i.instagram") + ".com/api/v1/highlights/" + id + "/highlights_tray/"; + String url = "https://i.instagram.com/api/v1/highlights/" + id + "/highlights_tray/"; try { HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); conn.setInstanceFollowRedirects(false); conn.setUseCaches(false); - conn.setRequestProperty("User-Agent", storiesig ? Constants.A_USER_AGENT : Constants.I_USER_AGENT); + conn.setRequestProperty("User-Agent", Constants.I_USER_AGENT); conn.connect(); if (conn.getResponseCode() == HttpURLConnection.HTTP_OK) { diff --git a/app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java index 0ac8ed24..09f49123 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/PostsFetcher.java @@ -10,6 +10,8 @@ import org.json.JSONObject; import java.io.File; import java.net.HttpURLConnection; import java.net.URL; +import java.util.ArrayList; +import java.util.List; import awais.instagrabber.BuildConfig; import awais.instagrabber.interfaces.FetchListener; @@ -28,18 +30,18 @@ import static awais.instagrabber.utils.Constants.FOLDER_PATH; import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; import static awais.instagrabber.utils.Utils.logCollector; -public final class PostsFetcher extends AsyncTask { +public final class PostsFetcher extends AsyncTask> { private static final String TAG = "PostsFetcher"; private final PostItemType type; private final String endCursor; private final String id; - private final FetchListener fetchListener; + private final FetchListener> fetchListener; private String username = null; public PostsFetcher(final String id, final PostItemType type, final String endCursor, - final FetchListener fetchListener) { + final FetchListener> fetchListener) { this.id = id; this.type = type; this.endCursor = endCursor == null ? "" : endCursor; @@ -52,7 +54,7 @@ public final class PostsFetcher extends AsyncTask { } @Override - protected PostModel[] doInBackground(final Void... voids) { + protected List doInBackground(final Void... voids) { // final boolean isHashTag = id.charAt(0) == '#'; // final boolean isSaved = id.charAt(0) == '$'; // final boolean isTagged = id.charAt(0) == '%'; @@ -79,7 +81,7 @@ public final class PostsFetcher extends AsyncTask { default: url = "https://www.instagram.com/graphql/query/?query_id=17880160963012870&id=" + id + "&first=50&after=" + endCursor; } - PostModel[] result = null; + List result = new ArrayList<>(); try { final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); conn.setUseCaches(false); @@ -126,8 +128,7 @@ public final class PostsFetcher extends AsyncTask { } final JSONArray edges = mediaPosts.getJSONArray("edges"); - final PostModel[] models = new PostModel[edges.length()]; - for (int i = 0; i < models.length; ++i) { + for (int i = 0; i < edges.length(); ++i) { final JSONObject mediaNode = edges.getJSONObject(i).getJSONObject("node"); final JSONArray captions = mediaNode.getJSONObject("edge_media_to_caption").getJSONArray("edges"); @@ -139,34 +140,43 @@ public final class PostsFetcher extends AsyncTask { else if (isVideo) itemType = MediaItemType.MEDIA_TYPE_VIDEO; else itemType = MediaItemType.MEDIA_TYPE_IMAGE; - models[i] = new PostModel(itemType, mediaNode.getString(Constants.EXTRAS_ID), - mediaNode.getString("display_url"), mediaNode.getString("thumbnail_src"), - mediaNode.getString(Constants.EXTRAS_SHORTCODE), - captions.length() > 0 ? captions.getJSONObject(0).getJSONObject("node").getString("text") : null, - mediaNode.getLong("taken_at_timestamp"), mediaNode.optBoolean("viewer_has_liked"), - mediaNode.optBoolean("viewer_has_saved"), mediaNode.getJSONObject("edge_liked_by").getLong("count")); - - DownloadUtils.checkExistence(downloadDir, customDir, isSlider, models[i]); + final PostModel model = new PostModel( + itemType, + mediaNode.getString(Constants.EXTRAS_ID), + mediaNode.getString("display_url"), + mediaNode.getString("thumbnail_src"), + mediaNode.getString(Constants.EXTRAS_SHORTCODE), + captions.length() > 0 ? captions.getJSONObject(0) + .getJSONObject("node") + .getString("text") + : null, + mediaNode.getLong("taken_at_timestamp"), + mediaNode.optBoolean("viewer_has_liked"), + mediaNode.optBoolean("viewer_has_saved"), + mediaNode.getJSONObject("edge_liked_by") + .getLong("count") + ); + result.add(model); + DownloadUtils.checkExistence(downloadDir, customDir, isSlider, model); } - if (models.length != 0 && models[models.length - 1] != null) - models[models.length - 1].setPageCursor(hasNextPage, endCursor); - - result = models; + if (!result.isEmpty() && result.get(result.size() - 1) != null) + result.get(result.size() - 1).setPageCursor(hasNextPage, endCursor); } - conn.disconnect(); } catch (Exception e) { - if (logCollector != null) + if (logCollector != null) { logCollector.appendException(e, LogCollector.LogFile.ASYNC_MAIN_POSTS_FETCHER, "doInBackground"); - if (BuildConfig.DEBUG) Log.e(TAG, "", e); + } + if (BuildConfig.DEBUG) { + Log.e(TAG, "Error fetching posts", e); + } } - return result; } @Override - protected void onPostExecute(final PostModel[] postModels) { + protected void onPostExecute(final List postModels) { if (fetchListener != null) fetchListener.onResult(postModels); } } diff --git a/app/src/main/java/awais/instagrabber/asyncs/SuggestionsFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/SuggestionsFetcher.java index 21b77ac8..c40d0406 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/SuggestionsFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/SuggestionsFetcher.java @@ -42,7 +42,6 @@ public final class SuggestionsFetcher extends AsyncTask { +public class CreateThreadAction extends AsyncTask { private static final String TAG = "CommentAction"; private final String cookie; - private final StoryModel storyModel; + private final String userId; private final OnTaskCompleteListener onTaskCompleteListener; - public CommentAction(final String cookie, final StoryModel storyModel, final OnTaskCompleteListener onTaskCompleteListener) { + public CreateThreadAction(final String cookie, final String userId, final OnTaskCompleteListener onTaskCompleteListener) { this.cookie = cookie; - this.storyModel = storyModel; + this.userId = userId; this.onTaskCompleteListener = onTaskCompleteListener; } @@ -41,7 +40,7 @@ public class CommentAction extends AsyncTask { final String urlParameters = Utils.sign("{\"_csrftoken\":\"" + cookie.split("csrftoken=")[1].split(";")[0] + "\",\"_uid\":\"" + CookieUtils.getUserIdFromCookie(cookie) + "\",\"__uuid\":\"" + settingsHelper.getString(Constants.DEVICE_UUID) - + "\",\"recipient_users\":\"[" + storyModel.getUserId() // <- string of array of number (not joking) + + "\",\"recipient_users\":\"[" + userId // <- string of array of number (not joking) + "]\"}"); urlConnection.setRequestProperty("Content-Type", "application/x-www-form-urlencoded"); if (urlParameters != null) { diff --git a/app/src/main/java/awais/instagrabber/asyncs/i/iLikedFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/i/iLikedFetcher.java index c631018d..58aec1ab 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/i/iLikedFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/i/iLikedFetcher.java @@ -10,6 +10,8 @@ import org.json.JSONObject; import java.io.File; import java.net.HttpURLConnection; import java.net.URL; +import java.util.ArrayList; +import java.util.List; import awais.instagrabber.BuildConfig; import awais.instagrabber.interfaces.FetchListener; @@ -28,25 +30,27 @@ import static awais.instagrabber.utils.Constants.FOLDER_PATH; import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; import static awais.instagrabber.utils.Utils.logCollector; -public final class iLikedFetcher extends AsyncTask { - private final String endCursor; - private final FetchListener fetchListener; +public final class iLikedFetcher extends AsyncTask> { + private static final String TAG = "iLikedFetcher"; - public iLikedFetcher(final FetchListener fetchListener) { + private final String endCursor; + private final FetchListener> fetchListener; + + public iLikedFetcher(final FetchListener> fetchListener) { this.endCursor = ""; this.fetchListener = fetchListener; } - public iLikedFetcher(final String endCursor, final FetchListener fetchListener) { + public iLikedFetcher(final String endCursor, final FetchListener> fetchListener) { this.endCursor = endCursor == null ? "" : endCursor; this.fetchListener = fetchListener; } @Override - protected PostModel[] doInBackground(final Void... voids) { - final String url = "https://i.instagram.com/api/v1/feed/liked/?max_id="+endCursor; + protected List doInBackground(final Void... voids) { + final String url = "https://i.instagram.com/api/v1/feed/liked/?max_id=" + endCursor; - PostModel[] result = null; + List result = new ArrayList<>(); try { final HttpURLConnection conn = (HttpURLConnection) new URL(url).openConnection(); conn.setUseCaches(false); @@ -68,8 +72,7 @@ public final class iLikedFetcher extends AsyncTask { } final JSONArray edges = body.getJSONArray("items"); - final PostModel[] models = new PostModel[edges.length()]; - for (int i = 0; i < models.length; ++i) { + for (int i = 0; i < edges.length(); ++i) { final JSONObject mediaNode = edges.getJSONObject(i); final boolean isSlider = mediaNode.has("carousel_media_count"); @@ -80,48 +83,57 @@ public final class iLikedFetcher extends AsyncTask { else if (isVideo) itemType = MediaItemType.MEDIA_TYPE_VIDEO; else itemType = MediaItemType.MEDIA_TYPE_IMAGE; - models[i] = new PostModel(itemType, mediaNode.getString(Constants.EXTRAS_ID), - isSlider - ? ResponseBodyUtils.getHighQualityImage(mediaNode.getJSONArray("carousel_media").getJSONObject(0)) - : ResponseBodyUtils.getHighQualityImage(mediaNode), - isSlider - ? ResponseBodyUtils.getLowQualityImage(mediaNode.getJSONArray("carousel_media").getJSONObject(0)) - : ResponseBodyUtils.getLowQualityImage(mediaNode), + final PostModel model = new PostModel( + itemType, + mediaNode.getString(Constants.EXTRAS_ID), + isSlider ? ResponseBodyUtils.getHighQualityImage(mediaNode.getJSONArray("carousel_media") + .getJSONObject(0)) + : ResponseBodyUtils.getHighQualityImage(mediaNode), + isSlider ? ResponseBodyUtils.getLowQualityImage(mediaNode.getJSONArray("carousel_media") + .getJSONObject(0)) + : ResponseBodyUtils.getLowQualityImage(mediaNode), mediaNode.getString("code"), mediaNode.isNull("caption") ? null : mediaNode.getJSONObject("caption").optString("text"), - mediaNode.getLong("taken_at"), true, - mediaNode.optBoolean("has_viewer_saved"), mediaNode.getLong("like_count")); - + mediaNode.getLong("taken_at"), + true, + mediaNode.optBoolean("has_viewer_saved"), + mediaNode.getLong("like_count")); + result.add(model); String username = mediaNode.getJSONObject("user").getString("username"); final File downloadDir = new File(Environment.getExternalStorageDirectory(), "Download" + - (Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/"+username) : "")); + (Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/" + username) : "")); File customDir = null; if (Utils.settingsHelper.getBoolean(FOLDER_SAVE_TO)) { final String customPath = Utils.settingsHelper.getString(FOLDER_PATH + - (Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) ? ("/"+username) : "")); + (Utils.settingsHelper.getBoolean(DOWNLOAD_USER_FOLDER) + ? ("/" + username) + : "")); if (!TextUtils.isEmpty(customPath)) customDir = new File(customPath); } - DownloadUtils.checkExistence(downloadDir, customDir, isSlider, models[i]); + DownloadUtils.checkExistence(downloadDir, customDir, isSlider, model); } - if (models[models.length - 1] != null) - models[models.length - 1].setPageCursor(hasNextPage, endCursor); - - result = models; + final int length = result.size(); + if (length >= 1 && result.get(length - 1) != null) { + result.get(length - 1).setPageCursor(hasNextPage, endCursor); + } } conn.disconnect(); } catch (Exception e) { - if (logCollector != null) + if (logCollector != null) { logCollector.appendException(e, LogCollector.LogFile.ASYNC_MAIN_POSTS_FETCHER, "doInBackground"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + } + if (BuildConfig.DEBUG) { + Log.e(TAG, "", e); + } } return result; } @Override - protected void onPostExecute(final PostModel[] postModels) { + protected void onPostExecute(final List postModels) { if (fetchListener != null) fetchListener.onResult(postModels); } } diff --git a/app/src/main/java/awais/instagrabber/asyncs/i/iStoryStatusFetcher.java b/app/src/main/java/awais/instagrabber/asyncs/i/iStoryStatusFetcher.java index 2524070e..4ff6bc58 100755 --- a/app/src/main/java/awais/instagrabber/asyncs/i/iStoryStatusFetcher.java +++ b/app/src/main/java/awais/instagrabber/asyncs/i/iStoryStatusFetcher.java @@ -30,7 +30,6 @@ public final class iStoryStatusFetcher extends AsyncTask fetchListener; @@ -38,14 +37,12 @@ public final class iStoryStatusFetcher extends AsyncTask fetchListener) { this.id = id; this.username = username; this.isLoc = isLoc; this.isHashtag = isHashtag; - this.storiesig = storiesig; this.highlight = highlight; this.fetchListener = fetchListener; } @@ -55,13 +52,7 @@ public final class iStoryStatusFetcher extends AsyncTask { + if (isChecked) { + if (TextUtils.isEmpty(binding.etPassword.getText())) { + binding.btnSaveTo.setEnabled(false); + } + binding.passwordField.setVisibility(View.VISIBLE); + binding.etPassword.requestFocus(); + final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm == null) return; + imm.showSoftInput(binding.etPassword, InputMethodManager.SHOW_IMPLICIT); + return; + } + binding.btnSaveTo.setEnabled(true); + binding.passwordField.setVisibility(View.GONE); + final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm == null) return; + imm.hideSoftInputFromWindow(binding.etPassword.getWindowToken(), InputMethodManager.RESULT_UNCHANGED_SHOWN); + }); + binding.btnSaveTo.setOnClickListener(v -> { + if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED) { + showChooser(context); + } else { + requestPermissions(PERMS, STORAGE_PERM_REQUEST_CODE); + } + }); + } + + @Override + public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + final Context context = getContext(); + if (context == null) return; + showChooser(context); + } + } + + private void showChooser(@NonNull final Context context) { + final String folderPath = Utils.settingsHelper.getString(FOLDER_PATH); + final Editable passwordText = binding.etPassword.getText(); + final String password = binding.cbPassword.isChecked() + && passwordText != null + && !TextUtils.isEmpty(passwordText.toString()) + ? passwordText.toString().trim() + : null; + final DirectoryChooser directoryChooser = new DirectoryChooser() + .setInitialDirectory(folderPath) + .setInteractionListener(path -> { + final File file = new File(path, String.format(Locale.ENGLISH, "barinsta_%d.backup", System.currentTimeMillis())); + int flags = 0; + if (binding.cbExportFavorites.isChecked()) { + flags |= ExportImportUtils.FLAG_FAVORITES; + } + if (binding.cbExportSettings.isChecked()) { + flags |= ExportImportUtils.FLAG_SETTINGS; + } + if (binding.cbExportLogins.isChecked()) { + flags |= ExportImportUtils.FLAG_COOKIES; + } + ExportImportUtils.exportData(password, flags, file, result -> { + if (onResultListener != null) { + onResultListener.onResult(result); + } + dismiss(); + }, context); + + }); + directoryChooser.setEnterTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + directoryChooser.setExitTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + directoryChooser.show(getChildFragmentManager(), "directory_chooser"); + } + + public interface OnResultListener { + void onResult(boolean result); + } +} diff --git a/app/src/main/java/awais/instagrabber/dialogs/QuickAccessDialog.java b/app/src/main/java/awais/instagrabber/dialogs/QuickAccessDialog.java deleted file mode 100755 index e5cb4a69..00000000 --- a/app/src/main/java/awais/instagrabber/dialogs/QuickAccessDialog.java +++ /dev/null @@ -1,181 +0,0 @@ -package awais.instagrabber.dialogs; - -import android.app.Activity; -import android.app.Dialog; -import android.content.Context; -import android.content.DialogInterface; -import android.content.pm.PackageManager; -import android.os.Bundle; -import android.view.View; -import android.widget.Toast; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.core.content.ContextCompat; -import androidx.recyclerview.widget.DividerItemDecoration; -import androidx.recyclerview.widget.RecyclerView; - -import com.google.android.material.bottomsheet.BottomSheetDialogFragment; - -import java.util.ArrayList; - -import awais.instagrabber.R; -import awais.instagrabber.adapters.SimpleAdapter; -import awais.instagrabber.utils.Constants; -import awais.instagrabber.utils.CookieUtils; -import awais.instagrabber.utils.DataBox; -import awais.instagrabber.utils.DownloadUtils; -import awais.instagrabber.utils.TextUtils; -import awais.instagrabber.utils.Utils; - -import static awais.instagrabber.utils.Utils.settingsHelper; - -public final class QuickAccessDialog extends BottomSheetDialogFragment implements DialogInterface.OnShowListener, - View.OnClickListener, View.OnLongClickListener { - private boolean cookieChanged, isQuery; - private Activity activity; - private String userQuery, displayName; - private View btnFavorite, btnImportExport; - private SimpleAdapter favoritesAdapter; - private RecyclerView rvFavorites, rvQuickAccess; - - public QuickAccessDialog setQuery(final String userQuery, final String displayName) { - this.userQuery = userQuery; - this.displayName = displayName; - return this; - } - - @NonNull - @Override - public Dialog onCreateDialog(@Nullable Bundle savedInstanceState) { - final Dialog dialog = super.onCreateDialog(savedInstanceState); - - dialog.setOnShowListener(this); - - final Context context = getContext(); - activity = context instanceof Activity ? (Activity) context : getActivity(); - - final View contentView = View.inflate(activity, R.layout.dialog_quick_access, null); - - btnFavorite = contentView.findViewById(R.id.btnFavorite); - btnImportExport = contentView.findViewById(R.id.importExport); - - isQuery = !TextUtils.isEmpty(userQuery); - btnFavorite.setVisibility(isQuery ? View.VISIBLE : View.GONE); - Utils.setTooltipText(btnImportExport, R.string.import_export); - - favoritesAdapter = new SimpleAdapter<>(activity, Utils.dataBox.getAllFavorites(), this, this); - - btnFavorite.setOnClickListener(this); - btnImportExport.setOnClickListener(this); - - rvFavorites = contentView.findViewById(R.id.rvFavorites); - rvQuickAccess = contentView.findViewById(R.id.rvQuickAccess); - - final DividerItemDecoration itemDecoration = new DividerItemDecoration(activity, DividerItemDecoration.VERTICAL); - rvFavorites.addItemDecoration(itemDecoration); - rvFavorites.setAdapter(favoritesAdapter); - - final String cookieStr = settingsHelper.getString(Constants.COOKIE); - if (!TextUtils.isEmpty(cookieStr) - || Utils.dataBox.getCookieCount() > 0 // fallback for export / import - ) { - rvQuickAccess.addItemDecoration(itemDecoration); - final ArrayList allCookies = Utils.dataBox.getAllCookies(); - if (!TextUtils.isEmpty(cookieStr) && allCookies != null) { - for (final DataBox.CookieModel cookie : allCookies) { - if (cookieStr.equals(cookie.getCookie())) { - cookie.setSelected(true); - break; - } - } - } - rvQuickAccess.setAdapter(new SimpleAdapter<>(activity, allCookies, this, this)); - } else { - ((View) rvQuickAccess.getParent()).setVisibility(View.GONE); - } - - dialog.setContentView(contentView); - return dialog; - } - - @Override - public void onClick(@NonNull final View v) { - final Object tag = v.getTag(); - if (v == btnFavorite) { - if (isQuery) { - Utils.dataBox.addFavorite(new DataBox.FavoriteModel(userQuery, System.currentTimeMillis(), displayName)); - favoritesAdapter.setItems(Utils.dataBox.getAllFavorites()); - } - } else if (v == btnImportExport) { - if (ContextCompat.checkSelfPermission(activity, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_DENIED) - requestPermissions(DownloadUtils.PERMS, 6007); - else Utils.showImportExportDialog(v.getContext()); - - } else if (tag instanceof DataBox.FavoriteModel) { - // if (MainActivityBackup.scanHack != null) { - // MainActivityBackup.scanHack.onResult(((DataBox.FavoriteModel) tag).getQuery()); - // dismiss(); - // } - - } else if (tag instanceof DataBox.CookieModel) { - final DataBox.CookieModel cookieModel = (DataBox.CookieModel) tag; - if (!cookieModel.isSelected()) { - settingsHelper.putString(Constants.COOKIE, cookieModel.getCookie()); - CookieUtils.setupCookies(cookieModel.getCookie()); - cookieChanged = true; - } - dismiss(); - } - } - - @Override - public boolean onLongClick(@NonNull final View v) { - final Object tag = v.getTag(); - - if (tag instanceof DataBox.FavoriteModel) { - final DataBox.FavoriteModel favoriteModel = (DataBox.FavoriteModel) tag; - - new AlertDialog.Builder(activity).setPositiveButton(R.string.yes, (d, which) -> { - Utils.dataBox.delFavorite(favoriteModel); - favoritesAdapter.setItems(Utils.dataBox.getAllFavorites()); - }) - .setNegativeButton(R.string.no, null).setMessage(getString(R.string.quick_access_confirm_delete, - favoriteModel.getQuery())).show(); - - } else if (tag instanceof DataBox.CookieModel) { - final DataBox.CookieModel cookieModel = (DataBox.CookieModel) tag; - - if (cookieModel.isSelected()) - Toast.makeText(v.getContext(), R.string.quick_access_cannot_delete_curr, Toast.LENGTH_SHORT).show(); - else - new AlertDialog.Builder(activity) - .setMessage(getString(R.string.quick_access_confirm_delete, cookieModel.getUsername())) - .setPositiveButton(R.string.yes, (d, which) -> { - Utils.dataBox.delUserCookie(cookieModel); - rvQuickAccess.findViewWithTag(cookieModel).setVisibility(View.GONE); - }) - .setNegativeButton(R.string.no, null) - .show(); - } - - return true; - } - - @Override - public void onDismiss(@NonNull final DialogInterface dialog) { - super.onDismiss(dialog); - if (cookieChanged && activity != null) activity.recreate(); - } - - @Override - public void onShow(final DialogInterface dialog) { - if (settingsHelper.getBoolean(Constants.SHOW_QUICK_ACCESS_DIALOG)) - new AlertDialog.Builder(activity) - .setMessage(R.string.quick_access_info_dialog) - .setPositiveButton(R.string.ok, null) - .setNeutralButton(R.string.dont_show_again, (d, which) -> - settingsHelper.putBoolean(Constants.SHOW_QUICK_ACCESS_DIALOG, false)).show(); - } -} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java new file mode 100644 index 00000000..b5005b39 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/dialogs/RestoreBackupDialogFragment.java @@ -0,0 +1,180 @@ +package awais.instagrabber.dialogs; + +import android.app.Dialog; +import android.content.Context; +import android.content.pm.PackageManager; +import android.os.Bundle; +import android.text.Editable; +import android.text.TextWatcher; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.view.Window; +import android.view.inputmethod.InputMethodManager; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; +import androidx.fragment.app.DialogFragment; +import androidx.fragment.app.FragmentTransaction; + +import java.io.File; + +import awais.instagrabber.databinding.DialogRestoreBackupBinding; +import awais.instagrabber.utils.DirectoryChooser; +import awais.instagrabber.utils.ExportImportUtils; +import awais.instagrabber.utils.PasswordUtils.IncorrectPasswordException; +import awais.instagrabber.utils.TextUtils; +import awais.instagrabber.utils.Utils; + +import static awais.instagrabber.utils.Constants.FOLDER_PATH; +import static awais.instagrabber.utils.DownloadUtils.PERMS; + +public class RestoreBackupDialogFragment extends DialogFragment { + private static final int STORAGE_PERM_REQUEST_CODE = 8020; + + private final OnResultListener onResultListener; + + private DialogRestoreBackupBinding binding; + private File file; + private boolean isEncrypted; + + public RestoreBackupDialogFragment(final OnResultListener onResultListener) { + this.onResultListener = onResultListener; + } + + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, + final ViewGroup container, + final Bundle savedInstanceState) { + binding = DialogRestoreBackupBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @NonNull + @Override + public Dialog onCreateDialog(Bundle savedInstanceState) { + Dialog dialog = super.onCreateDialog(savedInstanceState); + dialog.requestWindowFeature(Window.FEATURE_NO_TITLE); + return dialog; + } + + @Override + public void onStart() { + super.onStart(); + final Dialog dialog = getDialog(); + if (dialog == null) return; + final Window window = dialog.getWindow(); + if (window == null) return; + final int height = ViewGroup.LayoutParams.WRAP_CONTENT; + final int width = (int) (Utils.displayMetrics.widthPixels * 0.8); + window.setLayout(width, height); + } + + @Override + public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + init(); + } + + @Override + public void onRequestPermissionsResult(final int requestCode, @NonNull final String[] permissions, @NonNull final int[] grantResults) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults); + if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) { + showChooser(); + } + } + + private void init() { + final Context context = getContext(); + if (context == null) { + return; + } + binding.btnRestore.setEnabled(false); + binding.btnRestore.setOnClickListener(v -> { + int flags = 0; + if (binding.cbFavorites.isChecked()) { + flags |= ExportImportUtils.FLAG_FAVORITES; + } + if (binding.cbSettings.isChecked()) { + flags |= ExportImportUtils.FLAG_SETTINGS; + } + if (binding.cbAccounts.isChecked()) { + flags |= ExportImportUtils.FLAG_COOKIES; + } + final Editable text = binding.etPassword.getText(); + if (isEncrypted && text == null) return; + try { + ExportImportUtils.importData( + context, + flags, + file, + !isEncrypted ? null : text.toString(), + result -> { + if (onResultListener != null) { + onResultListener.onResult(result); + } + dismiss(); + } + ); + } catch (IncorrectPasswordException e) { + binding.passwordField.setError("Incorrect password"); + } + }); + binding.etPassword.addTextChangedListener(new TextWatcher() { + @Override + public void beforeTextChanged(final CharSequence s, final int start, final int count, final int after) {} + + @Override + public void onTextChanged(final CharSequence s, final int start, final int before, final int count) { + binding.btnRestore.setEnabled(!TextUtils.isEmpty(s)); + binding.passwordField.setError(null); + } + + @Override + public void afterTextChanged(final Editable s) {} + }); + if (ContextCompat.checkSelfPermission(context, PERMS[0]) == PackageManager.PERMISSION_GRANTED) { + showChooser(); + return; + } + requestPermissions(PERMS, STORAGE_PERM_REQUEST_CODE); + } + + private void showChooser() { + final String folderPath = Utils.settingsHelper.getString(FOLDER_PATH); + final Context context = getContext(); + if (context == null) return; + final DirectoryChooser directoryChooser = new DirectoryChooser() + .setInitialDirectory(folderPath) + .setShowBackupFiles(true) + .setInteractionListener(file -> { + isEncrypted = ExportImportUtils.isEncrypted(file); + if (isEncrypted) { + binding.passwordGroup.setVisibility(View.VISIBLE); + binding.passwordGroup.post(() -> { + binding.etPassword.requestFocus(); + binding.etPassword.post(() -> { + final InputMethodManager imm = (InputMethodManager) context.getSystemService(Context.INPUT_METHOD_SERVICE); + if (imm == null) return; + imm.showSoftInput(binding.etPassword, InputMethodManager.SHOW_IMPLICIT); + }); + binding.btnRestore.setEnabled(!TextUtils.isEmpty(binding.etPassword.getText())); + }); + } else { + binding.passwordGroup.setVisibility(View.GONE); + binding.btnRestore.setEnabled(true); + } + this.file = file; + binding.filePath.setText(file.getAbsolutePath()); + }); + directoryChooser.setEnterTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + directoryChooser.setExitTransition(FragmentTransaction.TRANSIT_FRAGMENT_FADE); + directoryChooser.setOnCancelListener(this::dismiss); + directoryChooser.show(getChildFragmentManager(), "directory_chooser"); + } + + public interface OnResultListener { + void onResult(boolean result); + } +} diff --git a/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java b/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java new file mode 100644 index 00000000..3d344a59 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.java @@ -0,0 +1,215 @@ +package awais.instagrabber.fragments; + +import android.content.Context; +import android.os.Bundle; +import android.util.Log; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.navigation.NavController; +import androidx.navigation.fragment.NavHostFragment; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; + +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.BrokenBarrierException; +import java.util.concurrent.CyclicBarrier; + +import awais.instagrabber.R; +import awais.instagrabber.adapters.FavoritesAdapter; +import awais.instagrabber.asyncs.LocationFetcher; +import awais.instagrabber.asyncs.ProfileFetcher; +import awais.instagrabber.databinding.FragmentFavoritesBinding; +import awais.instagrabber.utils.DataBox; +import awais.instagrabber.utils.TextUtils; +import awais.instagrabber.utils.Utils; +import awais.instagrabber.viewmodels.FavoritesViewModel; + +public class FavoritesFragment extends Fragment { + private static final String TAG = "FavoritesFragment"; + + private boolean shouldRefresh = true; + private FragmentFavoritesBinding binding; + private RecyclerView root; + private FavoritesViewModel favoritesViewModel; + private FavoritesAdapter adapter; + + @NonNull + @Override + public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { + if (root != null) { + shouldRefresh = false; + return root; + } + binding = FragmentFavoritesBinding.inflate(getLayoutInflater()); + root = binding.getRoot(); + binding.favoriteList.setLayoutManager(new LinearLayoutManager(getContext())); + return root; + } + + @Override + public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { + if (!shouldRefresh) return; + init(); + shouldRefresh = false; + } + + @Override + public void onResume() { + super.onResume(); + if (favoritesViewModel == null || adapter == null) return; + // refresh list every time in onViewStateRestored since it is cheaper than implementing pull down to refresh + favoritesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList); + final List allFavorites = Utils.dataBox.getAllFavorites(); + favoritesViewModel.getList().postValue(allFavorites); + fetchMissingInfo(allFavorites); + } + + private void init() { + favoritesViewModel = new ViewModelProvider(this).get(FavoritesViewModel.class); + adapter = new FavoritesAdapter(model -> { + // navigate + switch (model.getType()) { + case USER: { + final String username = model.getQuery(); + // Log.d(TAG, "username: " + username); + final NavController navController = NavHostFragment.findNavController(this); + final Bundle bundle = new Bundle(); + bundle.putString("username", "@" + username); + navController.navigate(R.id.action_global_profileFragment, bundle); + break; + } + case LOCATION: { + final String locationId = model.getQuery(); + // Log.d(TAG, "locationId: " + locationId); + final NavController navController = NavHostFragment.findNavController(this); + final Bundle bundle = new Bundle(); + bundle.putString("locationId", locationId); + navController.navigate(R.id.action_global_locationFragment, bundle); + break; + } + case HASHTAG: { + final String hashtag = model.getQuery(); + // Log.d(TAG, "hashtag: " + hashtag); + final NavController navController = NavHostFragment.findNavController(this); + final Bundle bundle = new Bundle(); + bundle.putString("hashtag", "#" + hashtag); + navController.navigate(R.id.action_global_hashTagFragment, bundle); + break; + } + default: + // do nothing + } + }, model -> { + // delete + final Context context = getContext(); + if (context == null) return false; + new MaterialAlertDialogBuilder(context) + .setMessage(getString(R.string.quick_access_confirm_delete, model.getQuery())) + .setPositiveButton(R.string.yes, (d, which) -> { + Utils.dataBox.deleteFavorite(model.getQuery(), model.getType()); + d.dismiss(); + favoritesViewModel.getList().postValue(Utils.dataBox.getAllFavorites()); + }) + .setNegativeButton(R.string.no, null) + .show(); + return true; + }); + binding.favoriteList.setAdapter(adapter); + // favoritesViewModel.getList().observe(getViewLifecycleOwner(), adapter::submitList); + + } + + private void fetchMissingInfo(final List allFavorites) { + final Runnable runnable = () -> { + final List updatedList = new ArrayList<>(allFavorites); + // cyclic barrier is to make the async calls synchronous + final CyclicBarrier cyclicBarrier = new CyclicBarrier(2, () -> { + // Log.d(TAG, "fetchMissingInfo: barrier action"); + favoritesViewModel.getList().postValue(new ArrayList<>(updatedList)); + }); + try { + for (final DataBox.FavoriteModel model : allFavorites) { + cyclicBarrier.reset(); + // if the model has missing pic or display name (for user and location), fetch those details + switch (model.getType()) { + case LOCATION: + if (TextUtils.isEmpty(model.getDisplayName()) + || TextUtils.isEmpty(model.getPicUrl())) { + new LocationFetcher(model.getQuery(), result -> { + try { + if (result == null) return; + final int i = updatedList.indexOf(model); + updatedList.remove(i); + final DataBox.FavoriteModel updated = new DataBox.FavoriteModel( + model.getId(), + model.getQuery(), + model.getType(), + result.getName(), + result.getSdProfilePic(), + model.getDateAdded() + ); + Utils.dataBox.addOrUpdateFavorite(updated); + updatedList.add(i, updated); + } finally { + try { + cyclicBarrier.await(); + } catch (BrokenBarrierException | InterruptedException e) { + Log.e(TAG, "fetchMissingInfo: ", e); + } + } + }).execute(); + cyclicBarrier.await(); + } + break; + case USER: + if (TextUtils.isEmpty(model.getDisplayName()) + || TextUtils.isEmpty(model.getPicUrl())) { + new ProfileFetcher(model.getQuery(), result -> { + try { + if (result == null) return; + final int i = updatedList.indexOf(model); + updatedList.remove(i); + final DataBox.FavoriteModel updated = new DataBox.FavoriteModel( + model.getId(), + model.getQuery(), + model.getType(), + result.getName(), + result.getSdProfilePic(), + model.getDateAdded() + ); + Utils.dataBox.addOrUpdateFavorite(updated); + updatedList.add(i, updated); + } finally { + try { + cyclicBarrier.await(); + } catch (BrokenBarrierException | InterruptedException e) { + Log.e(TAG, "fetchMissingInfo: ", e); + } + } + }).execute(); + cyclicBarrier.await(); + } + break; + case HASHTAG: + default: + // hashtags don't require displayName or pic + // updatedList.add(model); + } + } + } catch (Exception e) { + Log.e(TAG, "fetchMissingInfo: ", e); + } + favoritesViewModel.getList().postValue(updatedList); + }; + new Thread(runnable).start(); + } +} diff --git a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java index 522e9e1d..ab5cb671 100644 --- a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java @@ -1,7 +1,6 @@ package awais.instagrabber.fragments; import android.content.Context; -import android.content.res.ColorStateList; import android.graphics.Typeface; import android.os.AsyncTask; import android.os.Bundle; @@ -22,16 +21,18 @@ import androidx.activity.OnBackPressedDispatcher; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; -import androidx.core.content.ContextCompat; -import androidx.core.view.ViewCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import com.google.android.material.snackbar.BaseTransientBottomBar; +import com.google.android.material.snackbar.Snackbar; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.List; import awais.instagrabber.R; @@ -49,21 +50,24 @@ import awais.instagrabber.databinding.FragmentHashtagBinding; import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.models.HashtagModel; import awais.instagrabber.models.PostModel; -import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.enums.DownloadMethod; +import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; +import awais.instagrabber.utils.DataBox; import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; import awais.instagrabber.viewmodels.PostsViewModel; +import awais.instagrabber.webservices.ServiceCallback; +import awais.instagrabber.webservices.TagsService; import awaisomereport.LogCollector; import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.settingsHelper; -public class HashTagFragment extends Fragment { +public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "HashTagFragment"; private MainActivity fragmentActivity; @@ -79,7 +83,8 @@ public class HashTagFragment extends Fragment { private String endCursor; private AsyncTask currentlyExecuting; private boolean isLoggedIn; - private StoryModel[] storyModels; + private TagsService tagsService; + private boolean isPullToRefresh; private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { @Override @@ -116,19 +121,27 @@ public class HashTagFragment extends Fragment { return false; } }); - private final FetchListener postsFetchListener = new FetchListener() { + private final FetchListener> postsFetchListener = new FetchListener>() { @Override - public void onResult(final PostModel[] result) { + public void onResult(final List result) { binding.swipeRefreshLayout.setRefreshing(false); if (result == null) return; binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE)); final List postModels = postsViewModel.getList().getValue(); - final List finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() : new ArrayList<>(postModels); - finalList.addAll(Arrays.asList(result)); + List finalList = postModels == null || postModels.isEmpty() + ? new ArrayList<>() + : new ArrayList<>(postModels); + if (isPullToRefresh) { + finalList = result; + isPullToRefresh = false; + } else { + finalList.addAll(result); + } + finalList.addAll(result); postsViewModel.getList().postValue(finalList); PostModel model = null; - if (result.length != 0) { - model = result[result.length - 1]; + if (!result.isEmpty()) { + model = result.get(result.size() - 1); } if (model == null) return; endCursor = model.getEndCursor(); @@ -141,6 +154,7 @@ public class HashTagFragment extends Fragment { public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); fragmentActivity = (MainActivity) requireActivity(); + tagsService = TagsService.getInstance(); } @Nullable @@ -158,10 +172,18 @@ public class HashTagFragment extends Fragment { @Override public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { if (!shouldRefresh) return; + binding.swipeRefreshLayout.setOnRefreshListener(this); init(); shouldRefresh = false; } + @Override + public void onRefresh() { + isPullToRefresh = true; + endCursor = null; + fetchHashtagModel(); + } + @Override public void onDestroy() { super.onDestroy(); @@ -257,7 +279,6 @@ public class HashTagFragment extends Fragment { private void fetchPosts() { stopCurrentExecutor(); - binding.btnFollowTag.setVisibility(View.VISIBLE); binding.swipeRefreshLayout.setRefreshing(true); if (TextUtils.isEmpty(hashtag)) return; currentlyExecuting = new PostsFetcher(hashtag.substring(1), PostItemType.HASHTAG, endCursor, postsFetchListener) @@ -265,30 +286,110 @@ public class HashTagFragment extends Fragment { final Context context = getContext(); if (context == null) return; if (isLoggedIn) { - new iStoryStatusFetcher(hashtagModel.getName(), null, false, true, false, false, stories -> { - storyModels = stories; + new iStoryStatusFetcher(hashtagModel.getName(), null, false, true, false, stories -> { if (stories != null && stories.length > 0) { binding.mainHashtagImage.setStoriesBorder(); } }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); - + binding.btnFollowTag.setVisibility(View.VISIBLE); binding.btnFollowTag.setText(hashtagModel.getFollowing() ? R.string.unfollow : R.string.follow); - ViewCompat.setBackgroundTintList(binding.btnFollowTag, ColorStateList.valueOf( - ContextCompat.getColor(context, hashtagModel.getFollowing() - ? R.color.btn_purple_background - : R.color.btn_pink_background))); + binding.btnFollowTag.setChipIconResource(hashtagModel.getFollowing() + ? R.drawable.ic_outline_person_add_disabled_24 + : R.drawable.ic_outline_person_add_24); + binding.btnFollowTag.setOnClickListener(v -> { + final String cookie = settingsHelper.getString(Constants.COOKIE); + final String csrfToken = CookieUtils.getCsrfTokenFromCookie(cookie); + binding.btnFollowTag.setClickable(false); + if (!hashtagModel.getFollowing()) { + tagsService.follow(hashtag.substring(1), csrfToken, new ServiceCallback() { + @Override + public void onSuccess(final Boolean result) { + binding.btnFollowTag.setClickable(true); + if (!result) { + Log.e(TAG, "onSuccess: result is false"); + return; + } + onRefresh(); + } + + @Override + public void onFailure(@NonNull final Throwable t) { + binding.btnFollowTag.setClickable(true); + Log.e(TAG, "onFailure: ", t); + final String message = t.getMessage(); + Snackbar.make(root, + message != null ? message + : getString(R.string.downloader_unknown_error), + BaseTransientBottomBar.LENGTH_LONG) + .show(); + } + }); + return; + } + tagsService.unfollow(hashtag.substring(1), csrfToken, new ServiceCallback() { + @Override + public void onSuccess(final Boolean result) { + binding.btnFollowTag.setClickable(true); + if (!result) { + Log.e(TAG, "onSuccess: result is false"); + return; + } + onRefresh(); + } + + @Override + public void onFailure(@NonNull final Throwable t) { + binding.btnFollowTag.setClickable(true); + Log.e(TAG, "onFailure: ", t); + final String message = t.getMessage(); + Snackbar.make(root, + message != null ? message + : getString(R.string.downloader_unknown_error), + BaseTransientBottomBar.LENGTH_LONG) + .show(); + } + }); + }); } else { - binding.btnFollowTag.setText(Utils.dataBox.getFavorite(hashtag) != null - ? R.string.unfavorite_short - : R.string.favorite_short); - ViewCompat.setBackgroundTintList(binding.btnFollowTag, ColorStateList.valueOf( - ContextCompat.getColor(context, Utils.dataBox.getFavorite(hashtag) != null - ? R.color.btn_purple_background - : R.color.btn_pink_background))); + binding.btnFollowTag.setVisibility(View.GONE); } + final DataBox.FavoriteModel favorite = Utils.dataBox.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG); + final boolean isFav = favorite != null; + binding.favChip.setVisibility(View.VISIBLE); + binding.favChip.setChipIconResource(isFav ? R.drawable.ic_star_check_24 + : R.drawable.ic_outline_star_plus_24); + binding.favChip.setText(isFav ? R.string.favorite_short : R.string.add_to_favorites); + binding.favChip.setOnClickListener(v -> { + final DataBox.FavoriteModel fav = Utils.dataBox.getFavorite(hashtag.substring(1), FavoriteType.HASHTAG); + final boolean isFavorite = fav != null; + final String message; + if (isFavorite) { + Utils.dataBox.deleteFavorite(hashtag.substring(1), FavoriteType.HASHTAG); + binding.favChip.setText(R.string.add_to_favorites); + binding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); + message = getString(R.string.removed_from_favs); + } else { + Utils.dataBox.addOrUpdateFavorite(new DataBox.FavoriteModel( + -1, + hashtag.substring(1), + FavoriteType.HASHTAG, + hashtagModel.getName(), + null, + new Date() + )); + binding.favChip.setText(R.string.favorite_short); + binding.favChip.setChipIconResource(R.drawable.ic_star_check_24); + message = getString(R.string.added_to_favs); + } + final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG); + snackbar.setAction(R.string.ok, v1 -> snackbar.dismiss()) + .setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE) + .setAnchorView(fragmentActivity.getBottomNavView()) + .show(); + }); binding.mainHashtagImage.setImageURI(hashtagModel.getSdProfilePic()); final String postCount = String.valueOf(hashtagModel.getPostCount()); - final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count, postCount)); + final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline, postCount)); span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); binding.mainTagPostCount.setText(span); @@ -312,9 +413,7 @@ public class HashTagFragment extends Fragment { if (actionBar != null) { Log.d(TAG, "setting title: " + hashtag); final Handler handler = new Handler(); - handler.postDelayed(() -> { - actionBar.setTitle(hashtag); - }, 200); + handler.postDelayed(() -> actionBar.setTitle(hashtag), 200); } } diff --git a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java index 9a3c79db..f759c624 100644 --- a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java @@ -27,10 +27,14 @@ import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; +import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; + +import com.google.android.material.snackbar.BaseTransientBottomBar; +import com.google.android.material.snackbar.Snackbar; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.List; import awais.instagrabber.R; @@ -48,11 +52,12 @@ import awais.instagrabber.databinding.FragmentLocationBinding; import awais.instagrabber.interfaces.FetchListener; import awais.instagrabber.models.LocationModel; import awais.instagrabber.models.PostModel; -import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.enums.DownloadMethod; +import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; +import awais.instagrabber.utils.DataBox; import awais.instagrabber.utils.DownloadUtils; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; @@ -62,7 +67,7 @@ import awaisomereport.LogCollector; import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.settingsHelper; -public class LocationFragment extends Fragment { +public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "LocationFragment"; private MainActivity fragmentActivity; @@ -78,7 +83,7 @@ public class LocationFragment extends Fragment { private String endCursor; private AsyncTask currentlyExecuting; private boolean isLoggedIn; - private StoryModel[] storyModels; + private boolean isPullToRefresh; private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { @Override @@ -119,20 +124,25 @@ public class LocationFragment extends Fragment { return false; } }); - private final FetchListener postsFetchListener = new FetchListener() { + private final FetchListener> postsFetchListener = new FetchListener>() { @Override - public void onResult(final PostModel[] result) { + public void onResult(final List result) { binding.swipeRefreshLayout.setRefreshing(false); if (result == null) return; binding.mainPosts.post(() -> binding.mainPosts.setVisibility(View.VISIBLE)); final List postModels = postsViewModel.getList().getValue(); - final List finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() - : new ArrayList<>(postModels); - finalList.addAll(Arrays.asList(result)); + List finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() + : new ArrayList<>(postModels); + if (isPullToRefresh) { + finalList = result; + isPullToRefresh = false; + } else { + finalList.addAll(result); + } postsViewModel.getList().postValue(finalList); PostModel model = null; - if (result.length != 0) { - model = result[result.length - 1]; + if (!result.isEmpty()) { + model = result.get(result.size() - 1); } if (model == null) return; endCursor = model.getEndCursor(); @@ -164,10 +174,18 @@ public class LocationFragment extends Fragment { @Override public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) { if (!shouldRefresh) return; + binding.swipeRefreshLayout.setOnRefreshListener(this); init(); shouldRefresh = false; } + @Override + public void onRefresh() { + isPullToRefresh = true; + endCursor = null; + fetchLocationModel(); + } + @Override public void onDestroy() { super.onDestroy(); @@ -182,6 +200,8 @@ public class LocationFragment extends Fragment { isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) != null; final LocationFragmentArgs fragmentArgs = LocationFragmentArgs.fromBundle(getArguments()); locationId = fragmentArgs.getLocationId(); + binding.favChip.setVisibility(View.GONE); + binding.btnMap.setVisibility(View.GONE); setTitle(); setupPosts(); fetchLocationModel(); @@ -273,9 +293,7 @@ public class LocationFragment extends Fragment { true, false, false, - false, stories -> { - storyModels = stories; if (stories != null && stories.length > 0) { binding.mainLocationImage.setStoriesBorder(); } @@ -283,7 +301,7 @@ public class LocationFragment extends Fragment { } binding.mainLocationImage.setImageURI(locationModel.getSdProfilePic()); final String postCount = String.valueOf(locationModel.getPostCount()); - final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count, + final SpannableStringBuilder span = new SpannableStringBuilder(getString(R.string.main_posts_count_inline, postCount)); span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); @@ -329,6 +347,40 @@ public class LocationFragment extends Fragment { binding.locationUrl.setVisibility(View.VISIBLE); binding.locationUrl.setText(TextUtils.getSpannableUrl(url)); } + final DataBox.FavoriteModel favorite = Utils.dataBox.getFavorite(locationId, FavoriteType.LOCATION); + final boolean isFav = favorite != null; + binding.favChip.setVisibility(View.VISIBLE); + binding.favChip.setChipIconResource(isFav ? R.drawable.ic_star_check_24 + : R.drawable.ic_outline_star_plus_24); + binding.favChip.setText(isFav ? R.string.favorite_short : R.string.add_to_favorites); + binding.favChip.setOnClickListener(v -> { + final DataBox.FavoriteModel fav = Utils.dataBox.getFavorite(locationId, FavoriteType.LOCATION); + final boolean isFavorite = fav != null; + final String message; + if (isFavorite) { + Utils.dataBox.deleteFavorite(locationId, FavoriteType.LOCATION); + binding.favChip.setText(R.string.add_to_favorites); + binding.favChip.setChipIconResource(R.drawable.ic_outline_star_plus_24); + message = getString(R.string.removed_from_favs); + } else { + Utils.dataBox.addOrUpdateFavorite(new DataBox.FavoriteModel( + -1, + locationId, + FavoriteType.LOCATION, + locationModel.getName(), + locationModel.getSdProfilePic(), + new Date() + )); + binding.favChip.setText(R.string.favorite_short); + binding.favChip.setChipIconResource(R.drawable.ic_star_check_24); + message = getString(R.string.added_to_favs); + } + final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG); + snackbar.setAction(R.string.ok, v1 -> snackbar.dismiss()) + .setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE) + .setAnchorView(fragmentActivity.getBottomNavView()) + .show(); + }); } private void fetchPosts() { diff --git a/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java index 9858a9eb..efaeb320 100644 --- a/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java @@ -27,7 +27,6 @@ import androidx.navigation.fragment.NavHostFragment; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -58,7 +57,6 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL private static AsyncTask currentlyExecuting; private PostsAdapter postsAdapter; private boolean hasNextPage; - private boolean autoloadPosts; private FragmentSavedBinding binding; private String username; private String endCursor; @@ -107,17 +105,16 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL return false; } }); - private final FetchListener postsFetchListener = new FetchListener() { + private final FetchListener> postsFetchListener = new FetchListener>() { @Override - public void onResult(final PostModel[] result) { + public void onResult(final List result) { final List current = postsViewModel.getList().getValue(); - if (result != null && result.length > 0) { - final List resultList = Arrays.asList(result); + if (result != null && !result.isEmpty()) { if (current == null) { - postsViewModel.getList().postValue(resultList); + postsViewModel.getList().postValue(result); } else { final List currentCopy = new ArrayList<>(current); - currentCopy.addAll(resultList); + currentCopy.addAll(result); postsViewModel.getList().postValue(currentCopy); } binding.mainPosts.post(() -> { @@ -125,11 +122,11 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL binding.mainPosts.setVisibility(View.VISIBLE); }); - final PostModel model = result.length > 0 ? result[result.length - 1] : null; + final PostModel model = !result.isEmpty() ? result.get(result.size() - 1) : null; if (model != null) { endCursor = model.getEndCursor(); hasNextPage = model.hasNextPage(); - if (autoloadPosts && hasNextPage) { + if (hasNextPage) { fetchPosts(); } else { binding.swipeRefreshLayout.setRefreshing(false); @@ -246,7 +243,7 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL binding.swipeRefreshLayout.setRefreshing(true); lazyLoader = new RecyclerLazyLoader(layoutManager, (page, totalItemsCount) -> { - if (!autoloadPosts && hasNextPage) { + if (hasNextPage) { binding.swipeRefreshLayout.setRefreshing(true); fetchPosts(); endCursor = null; @@ -258,7 +255,7 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL private void fetchPosts() { stopCurrentExecutor(); - final AsyncTask asyncTask; + final AsyncTask> asyncTask; switch (type) { case LIKED: asyncTask = new iLikedFetcher(endCursor, postsFetchListener); diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java index 2365bdae..29c50a80 100644 --- a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.java @@ -62,12 +62,12 @@ import java.util.List; import awais.instagrabber.BuildConfig; import awais.instagrabber.R; import awais.instagrabber.adapters.StoriesAdapter; -import awais.instagrabber.asyncs.CommentAction; import awais.instagrabber.asyncs.DownloadAsync; import awais.instagrabber.asyncs.QuizAction; import awais.instagrabber.asyncs.RespondAction; import awais.instagrabber.asyncs.SeenAction; import awais.instagrabber.asyncs.VoteAction; +import awais.instagrabber.asyncs.direct_messages.CreateThreadAction; import awais.instagrabber.asyncs.direct_messages.DirectThreadBroadcaster; import awais.instagrabber.customviews.helpers.SwipeGestureListener; import awais.instagrabber.databinding.FragmentStoryViewerBinding; @@ -196,7 +196,7 @@ public class StoryViewerFragment extends Fragment { new AlertDialog.Builder(context) .setTitle(R.string.reply_story) .setView(input) - .setPositiveButton(R.string.ok, (d, w) -> new CommentAction(cookie, currentStory, threadId -> { + .setPositiveButton(R.string.ok, (d, w) -> new CreateThreadAction(cookie, currentStory.getUserId(), threadId -> { try { final DirectThreadBroadcaster.StoryReplyBroadcastOptions options = new DirectThreadBroadcaster.StoryReplyBroadcastOptions( input.getText().toString(), @@ -544,7 +544,6 @@ public class StoryViewerFragment extends Fragment { }; storiesService.getUserStory(currentStoryMediaId, username, - !isLoggedIn && settingsHelper.getString(Constants.STORY_VIEWER) == StoryViewerChoice.STORIESIG.getValue(), false, false, isHighlight, 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 d295dd0b..a02abbb0 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java @@ -325,6 +325,7 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre @Override public void onResume() { super.onResume(); + binding.feedSwipeRefreshLayout.setRefreshing(false); if (videoAwareRecyclerScroller != null && shouldAutoPlay) { videoAwareRecyclerScroller.startPlaying(); } 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 5b181f8c..40746a8d 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java @@ -2,7 +2,6 @@ package awais.instagrabber.fragments.main; import android.content.Context; import android.content.DialogInterface; -import android.content.res.ColorStateList; import android.graphics.Typeface; import android.os.AsyncTask; import android.os.Bundle; @@ -30,8 +29,6 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AlertDialog; import androidx.coordinatorlayout.widget.CoordinatorLayout; -import androidx.core.content.ContextCompat; -import androidx.core.view.ViewCompat; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; @@ -42,9 +39,12 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; +import com.google.android.material.snackbar.BaseTransientBottomBar; +import com.google.android.material.snackbar.Snackbar; + import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; +import java.util.Date; import java.util.List; import awais.instagrabber.ProfileNavGraphDirections; @@ -56,6 +56,7 @@ import awais.instagrabber.asyncs.HighlightsFetcher; import awais.instagrabber.asyncs.PostsFetcher; import awais.instagrabber.asyncs.ProfileFetcher; import awais.instagrabber.asyncs.UsernameFetcher; +import awais.instagrabber.asyncs.direct_messages.CreateThreadAction; import awais.instagrabber.asyncs.i.iStoryStatusFetcher; import awais.instagrabber.customviews.PrimaryActionModeCallback; import awais.instagrabber.customviews.PrimaryActionModeCallback.CallbacksHelper; @@ -71,6 +72,7 @@ import awais.instagrabber.models.PostModel; import awais.instagrabber.models.ProfileModel; import awais.instagrabber.models.StoryModel; import awais.instagrabber.models.enums.DownloadMethod; +import awais.instagrabber.models.enums.FavoriteType; import awais.instagrabber.models.enums.PostItemType; import awais.instagrabber.models.enums.StoryViewerChoice; import awais.instagrabber.repositories.responses.FriendshipRepoChangeRootResponse; @@ -111,11 +113,12 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe private StoryModel[] storyModels; private boolean hasNextPage; private String endCursor; - private AsyncTask currentlyExecuting; - private MenuItem favMenuItem; + private AsyncTask> currentlyExecuting; private boolean isPullToRefresh; private HighlightsAdapter highlightsAdapter; private HighlightsViewModel highlightsViewModel; + private MenuItem blockMenuItem; + private MenuItem restrictMenuItem; private final Runnable usernameSettingRunnable = () -> { final ActionBar actionBar = fragmentActivity.getSupportActionBar(); @@ -161,11 +164,11 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe return false; } }); - private final FetchListener postsFetchListener = new FetchListener() { + private final FetchListener> postsFetchListener = new FetchListener>() { @Override - public void onResult(final PostModel[] result) { + public void onResult(final List result) { binding.swipeRefreshLayout.setRefreshing(false); - if (result == null || result.length <= 0) { + if (result == null || result.isEmpty()) { binding.privatePage1.setImageResource(R.drawable.ic_cancel); binding.privatePage2.setText(R.string.empty_acc); binding.privatePage.setVisibility(View.VISIBLE); @@ -175,15 +178,14 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe final List postModels = postsViewModel.getList().getValue(); List finalList = postModels == null || postModels.isEmpty() ? new ArrayList<>() : new ArrayList<>(postModels); - final List resultList = Arrays.asList(result); if (isPullToRefresh) { - finalList = resultList; + finalList = result; isPullToRefresh = false; } else { - finalList.addAll(resultList); + finalList.addAll(result); } postsViewModel.getList().postValue(finalList); - final PostModel lastPostModel = result[result.length - 1]; + final PostModel lastPostModel = result.get(result.size() - 1); if (lastPostModel == null) return; endCursor = lastPostModel.getEndCursor(); hasNextPage = lastPostModel.hasNextPage(); @@ -262,7 +264,81 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe @Override public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { inflater.inflate(R.menu.profile_menu, menu); - favMenuItem = menu.findItem(R.id.favourites); + // favMenuItem = menu.findItem(R.id.favourites); + blockMenuItem = menu.findItem(R.id.block); + if (blockMenuItem != null) { + blockMenuItem.setVisible(false); + } + restrictMenuItem = menu.findItem(R.id.restrict); + if (restrictMenuItem != null) { + restrictMenuItem.setVisible(false); + } + } + + @Override + public boolean onOptionsItemSelected(@NonNull final MenuItem item) { + if (item.getItemId() == R.id.restrict) { + if (!isLoggedIn) return false; + final String action = profileModel.getRestricted() ? "Unrestrict" : "Restrict"; + friendshipService.toggleRestrict( + profileModel.getId(), + !profileModel.getRestricted(), + CookieUtils.getCsrfTokenFromCookie(cookie), + new ServiceCallback() { + @Override + public void onSuccess(final FriendshipRepoRestrictRootResponse result) { + Log.d(TAG, action + " success: " + result); + fetchProfileDetails(); + } + + @Override + public void onFailure(final Throwable t) { + Log.e(TAG, "Error while performing " + action, t); + } + }); + return true; + } + if (item.getItemId() == R.id.block) { + final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); + if (!isLoggedIn) return false; + if (profileModel.getBlocked()) { + friendshipService.unblock( + userIdFromCookie, + profileModel.getId(), + CookieUtils.getCsrfTokenFromCookie(cookie), + new ServiceCallback() { + @Override + public void onSuccess(final FriendshipRepoChangeRootResponse result) { + Log.d(TAG, "Unblock success: " + result); + fetchProfileDetails(); + } + + @Override + public void onFailure(final Throwable t) { + Log.e(TAG, "Error unblocking", t); + } + }); + return true; + } + friendshipService.block( + userIdFromCookie, + profileModel.getId(), + CookieUtils.getCsrfTokenFromCookie(cookie), + new ServiceCallback() { + @Override + public void onSuccess(final FriendshipRepoChangeRootResponse result) { + Log.d(TAG, "Block success: " + result); + fetchProfileDetails(); + } + + @Override + public void onFailure(final Throwable t) { + Log.e(TAG, "Error blocking", t); + } + }); + return true; + } + return super.onOptionsItemSelected(item); } @Override @@ -295,6 +371,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe } if (TextUtils.isEmpty(username) && !isLoggedIn) { binding.infoContainer.setVisibility(View.GONE); + binding.swipeRefreshLayout.setEnabled(false); binding.privatePage1.setImageResource(R.drawable.ic_outline_info_24); binding.privatePage2.setText(R.string.no_acc); final NestedCoordinatorLayout.LayoutParams layoutParams = (NestedCoordinatorLayout.LayoutParams) binding.privatePage.getLayoutParams(); @@ -303,6 +380,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe binding.privatePage.setVisibility(View.VISIBLE); return; } + binding.swipeRefreshLayout.setEnabled(true); setupPosts(); setupHighlights(); setupCommonListeners(); @@ -337,17 +415,18 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe } private void fetchProfileDetails() { + if (TextUtils.isEmpty(username)) return; new ProfileFetcher(username.substring(1), profileModel -> { if (getContext() == null) return; this.profileModel = profileModel; - final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); - final boolean isSelf = isLoggedIn - && profileModel != null - && userIdFromCookie != null - && userIdFromCookie.equals(profileModel.getId()); - if (favMenuItem != null) { - favMenuItem.setVisible(isSelf); - } + // final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); + // final boolean isSelf = isLoggedIn + // && profileModel != null + // && userIdFromCookie != null + // && userIdFromCookie.equals(profileModel.getId()); + // if (favMenuItem != null) { + // favMenuItem.setVisible(isSelf); + // } setProfileDetails(); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); @@ -363,12 +442,11 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe } binding.isVerified.setVisibility(profileModel.isVerified() ? View.VISIBLE : View.GONE); final String profileId = profileModel.getId(); - if (settingsHelper.getString(Constants.STORY_VIEWER).equals(StoryViewerChoice.STORIESIG.getValue()) || isLoggedIn) { + if (isLoggedIn) { new iStoryStatusFetcher(profileId, profileModel.getUsername(), false, false, - !isLoggedIn && settingsHelper.getString(Constants.STORY_VIEWER).equals(StoryViewerChoice.STORIESIG.getValue()), false, result -> { storyModels = result; @@ -377,7 +455,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe } }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); new HighlightsFetcher(profileId, - !isLoggedIn && settingsHelper.getString(Constants.STORY_VIEWER).equals(StoryViewerChoice.STORIESIG.getValue()), result -> { if (result != null) { binding.highlightsList.setVisibility(View.VISIBLE); @@ -385,7 +462,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe } else binding.highlightsList.setVisibility(View.GONE); }).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); } else if (settingsHelper.getString(Constants.STORY_VIEWER).equals(StoryViewerChoice.ALOINSTAGRAM.getValue())) { - Log.d("austin_debug", "alo triggered"); + // Log.d(TAG, "alo triggered"); aloService.getUserStory(profileId, profileModel.getUsername(), false, new ServiceCallback>() { @Override public void onSuccess(final List result) { @@ -402,74 +479,66 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe }); } + final String myId = CookieUtils.getUserIdFromCookie(cookie); if (isLoggedIn) { - final String myId = CookieUtils.getUserIdFromCookie(cookie); if (profileId.equals(myId)) { binding.btnTagged.setVisibility(View.VISIBLE); binding.btnSaved.setVisibility(View.VISIBLE); binding.btnLiked.setVisibility(View.VISIBLE); + binding.btnDM.setVisibility(View.GONE); binding.btnSaved.setText(R.string.saved); - ViewCompat.setBackgroundTintList(binding.btnSaved, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_orange_background))); } else { binding.btnTagged.setVisibility(View.GONE); binding.btnSaved.setVisibility(View.GONE); binding.btnLiked.setVisibility(View.GONE); + binding.btnDM.setVisibility(View.VISIBLE); // maybe there is a judgment mechanism? binding.btnFollow.setVisibility(View.VISIBLE); if (profileModel.getFollowing()) { binding.btnFollow.setText(R.string.unfollow); - ViewCompat.setBackgroundTintList(binding.btnFollow, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_purple_background))); + binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24); } else if (profileModel.getRequested()) { binding.btnFollow.setText(R.string.cancel); - ViewCompat.setBackgroundTintList(binding.btnFollow, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_purple_background))); + binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_disabled_24); } else { binding.btnFollow.setText(R.string.follow); - ViewCompat.setBackgroundTintList(binding.btnFollow, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_pink_background))); + binding.btnFollow.setIconResource(R.drawable.ic_outline_person_add_24); } - binding.btnRestrict.setVisibility(View.VISIBLE); - if (profileModel.getRestricted()) { - binding.btnRestrict.setText(R.string.unrestrict); - ViewCompat.setBackgroundTintList(binding.btnRestrict, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_green_background))); - } else { - binding.btnRestrict.setText(R.string.restrict); - ViewCompat.setBackgroundTintList(binding.btnRestrict, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_orange_background))); + if (restrictMenuItem != null) { + restrictMenuItem.setVisible(true); + if (profileModel.getRestricted()) { + restrictMenuItem.setTitle(R.string.unrestrict); + } else { + restrictMenuItem.setTitle(R.string.restrict); + } } - binding.btnBlock.setVisibility(View.VISIBLE); - binding.btnTagged.setVisibility(View.VISIBLE); - if (profileModel.getBlocked()) { - binding.btnBlock.setText(R.string.unblock); - ViewCompat.setBackgroundTintList(binding.btnBlock, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_green_background))); - } else { - binding.btnBlock.setText(R.string.block); - ViewCompat.setBackgroundTintList(binding.btnBlock, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_red_background))); + binding.btnTagged.setVisibility(profileModel.isReallyPrivate() ? View.GONE : View.VISIBLE); + if (blockMenuItem != null) { + blockMenuItem.setVisible(true); + if (profileModel.getBlocked()) { + blockMenuItem.setTitle(R.string.unblock); + } else { + blockMenuItem.setTitle(R.string.block); + } } } } else { - if (Utils.dataBox.getFavorite(username) != null) { - binding.btnFollow.setText(R.string.unfavorite_short); - ViewCompat.setBackgroundTintList(binding.btnFollow, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_purple_background))); - } else { - binding.btnFollow.setText(R.string.favorite_short); - ViewCompat.setBackgroundTintList(binding.btnFollow, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_pink_background))); - } - binding.btnFollow.setVisibility(View.VISIBLE); - if (!profileModel.isReallyPrivate()) { - binding.btnRestrict.setVisibility(View.VISIBLE); - binding.btnRestrict.setText(R.string.tagged); - ViewCompat.setBackgroundTintList(binding.btnRestrict, - ColorStateList.valueOf(ContextCompat.getColor(context, R.color.btn_blue_background))); + if (!profileModel.isReallyPrivate() && restrictMenuItem != null) { + restrictMenuItem.setVisible(true); + if (profileModel.getRestricted()) { + restrictMenuItem.setTitle(R.string.unrestrict); + } else { + restrictMenuItem.setTitle(R.string.restrict); + } } } - + if (!profileId.equals(myId)) { + binding.favCb.setVisibility(View.VISIBLE); + final boolean isFav = Utils.dataBox.getFavorite(username.substring(1), FavoriteType.USER) != null; + binding.favCb.setChecked(isFav); + binding.favCb.setButtonDrawable(isFav ? R.drawable.ic_star_check_24 : R.drawable.ic_outline_star_plus_24); + } else { + binding.favCb.setVisibility(View.GONE); + } binding.mainProfileImage.setImageURI(profileModel.getSdProfilePic()); final long followersCount = profileModel.getFollowersCount(); @@ -563,23 +632,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe final String userIdFromCookie = CookieUtils.getUserIdFromCookie(cookie); // final boolean isSelf = isLoggedIn && profileModel != null && userIdFromCookie != null && userIdFromCookie // .equals(profileModel.getId()); - final String favorite = Utils.dataBox.getFavorite(username); binding.btnFollow.setOnClickListener(v -> { - if (!isLoggedIn) { - if (favorite != null && v == binding.btnFollow) { - Utils.dataBox.delFavorite(new DataBox.FavoriteModel( - username, - Long.parseLong(favorite.split("/")[1]), - username.replaceAll("^@", ""))); - } else if (v == binding.btnFollow) { - Utils.dataBox.addFavorite(new DataBox.FavoriteModel( - username, - System.currentTimeMillis(), - username.replaceAll("^@", ""))); - } - fetchProfileDetails(); - return; - } if (profileModel.getFollowing() || profileModel.getRequested()) { friendshipService.unfollow( userIdFromCookie, @@ -589,7 +642,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe @Override public void onSuccess(final FriendshipRepoChangeRootResponse result) { Log.d(TAG, "Unfollow success: " + result); - fetchProfileDetails(); + onRefresh(); } @Override @@ -606,7 +659,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe @Override public void onSuccess(final FriendshipRepoChangeRootResponse result) { Log.d(TAG, "Follow success: " + result); - fetchProfileDetails(); + onRefresh(); } @Override @@ -616,64 +669,6 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe }); } }); - binding.btnRestrict.setOnClickListener(v -> { - if (!isLoggedIn) return; - final String action = profileModel.getRestricted() ? "Unrestrict" : "Restrict"; - friendshipService.toggleRestrict( - profileModel.getId(), - !profileModel.getRestricted(), - CookieUtils.getCsrfTokenFromCookie(cookie), - new ServiceCallback() { - @Override - public void onSuccess(final FriendshipRepoRestrictRootResponse result) { - Log.d(TAG, action + " success: " + result); - fetchProfileDetails(); - } - - @Override - public void onFailure(final Throwable t) { - Log.e(TAG, "Error while performing " + action, t); - } - }); - }); - binding.btnBlock.setOnClickListener(v -> { - if (!isLoggedIn) return; - if (profileModel.getBlocked()) { - friendshipService.unblock( - userIdFromCookie, - profileModel.getId(), - CookieUtils.getCsrfTokenFromCookie(cookie), - new ServiceCallback() { - @Override - public void onSuccess(final FriendshipRepoChangeRootResponse result) { - Log.d(TAG, "Unblock success: " + result); - fetchProfileDetails(); - } - - @Override - public void onFailure(final Throwable t) { - Log.e(TAG, "Error unblocking", t); - } - }); - return; - } - friendshipService.block( - userIdFromCookie, - profileModel.getId(), - CookieUtils.getCsrfTokenFromCookie(cookie), - new ServiceCallback() { - @Override - public void onSuccess(final FriendshipRepoChangeRootResponse result) { - Log.d(TAG, "Block success: " + result); - fetchProfileDetails(); - } - - @Override - public void onFailure(final Throwable t) { - Log.e(TAG, "Error blocking", t); - } - }); - }); binding.btnSaved.setOnClickListener(v -> { final NavDirections action = ProfileFragmentDirections.actionProfileFragmentToSavedViewerFragment(profileModel.getUsername(), profileModel.getId(), @@ -692,6 +687,12 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe PostItemType.TAGGED); NavHostFragment.findNavController(this).navigate(action); }); + binding.btnDM.setOnClickListener(v -> { + new CreateThreadAction(cookie, profileModel.getId(), threadId -> { + final NavDirections action = ProfileFragmentDirections.actionProfileFragmentToDMThreadFragment(threadId, profileModel.getUsername()); + NavHostFragment.findNavController(this).navigate(action); + }).execute(); + }); binding.mainProfileImage.setOnClickListener(v -> { if (storyModels == null || storyModels.length <= 0) { // show profile pic @@ -721,6 +722,41 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe .setNegativeButton(R.string.cancel, null) .show(); }); + binding.favCb.setOnCheckedChangeListener((buttonView, isChecked) -> { + // do not do anything if state matches the db, as listener is set before profile details are set + final String finalUsername = username.startsWith("@") ? username.substring(1) : username; + final DataBox.FavoriteModel favorite = Utils.dataBox.getFavorite(finalUsername, FavoriteType.USER); + if ((isChecked && favorite != null) || (!isChecked && favorite == null)) { + return; + } + buttonView.setVisibility(View.GONE); + binding.favProgress.setVisibility(View.VISIBLE); + final String message; + if (isChecked) { + final DataBox.FavoriteModel model = new DataBox.FavoriteModel( + -1, + finalUsername, + FavoriteType.USER, + profileModel.getName(), + profileModel.getSdProfilePic(), + new Date() + ); + Utils.dataBox.addOrUpdateFavorite(model); + binding.favCb.setButtonDrawable(R.drawable.ic_star_check_24); + message = getString(R.string.added_to_favs); + } else { + Utils.dataBox.deleteFavorite(finalUsername, FavoriteType.USER); + message = getString(R.string.removed_from_favs); + binding.favCb.setButtonDrawable(R.drawable.ic_outline_star_plus_24); + } + final Snackbar snackbar = Snackbar.make(root, message, BaseTransientBottomBar.LENGTH_LONG); + snackbar.setAction(R.string.ok, v -> snackbar.dismiss()) + .setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE) + .setAnchorView(fragmentActivity.getBottomNavView()) + .show(); + binding.favProgress.setVisibility(View.GONE); + binding.favCb.setVisibility(View.VISIBLE); + }); } private void showProfilePicDialog() { diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/AboutFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/AboutFragment.java index 78699e61..0e72d21f 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/AboutFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/AboutFragment.java @@ -29,12 +29,13 @@ public class AboutFragment extends BasePreferencesFragment { final PreferenceCategory thirdPartyCategory = new PreferenceCategory(context); screen.addPreference(thirdPartyCategory); thirdPartyCategory.setTitle(R.string.about_category_3pt); - thirdPartyCategory.setSummary(R.string.about_category_3pt_summary); + //thirdPartyCategory.setSummary(R.string.about_category_3pt_summary); thirdPartyCategory.setIconSpaceReserved(false); // alphabetical order!!! thirdPartyCategory.addPreference(getExoPlayerPreference()); thirdPartyCategory.addPreference(getFrescoPreference()); thirdPartyCategory.addPreference(getJsoupPreference()); + thirdPartyCategory.addPreference(getMDIPreference()); thirdPartyCategory.addPreference(getRetrofitPreference()); final PreferenceCategory licenseCategory = new PreferenceCategory(context); @@ -157,6 +158,22 @@ public class AboutFragment extends BasePreferencesFragment { return preference; } + private Preference getMDIPreference() { + final Context context = getContext(); + if (context == null) return null; + final Preference preference = new Preference(context); + preference.setTitle("Material Design Icons"); + preference.setSummary("Copyright (C) 2014 Austin Andrews & Google LLC. Apache Version 2.0."); + preference.setIconSpaceReserved(false); + preference.setOnPreferenceClickListener(p -> { + final Intent intent = new Intent(Intent.ACTION_VIEW); + intent.setData(Uri.parse("https://materialdesignicons.com/")); + startActivity(intent); + return true; + }); + return preference; + } + private Preference getLicensePreference() { final Context context = getContext(); if (context == null) return null; diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/BackupPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/BackupPreferencesFragment.java new file mode 100644 index 00000000..62d59258 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/fragments/settings/BackupPreferencesFragment.java @@ -0,0 +1,107 @@ +package awais.instagrabber.fragments.settings; + +import android.content.Context; +import android.view.View; +import android.widget.Toast; + +import androidx.annotation.NonNull; +import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentManager; +import androidx.fragment.app.FragmentTransaction; +import androidx.preference.Preference; +import androidx.preference.PreferenceScreen; + +import com.google.android.material.snackbar.BaseTransientBottomBar; +import com.google.android.material.snackbar.Snackbar; + +import awais.instagrabber.R; +import awais.instagrabber.dialogs.CreateBackupDialogFragment; +import awais.instagrabber.dialogs.RestoreBackupDialogFragment; + +public class BackupPreferencesFragment extends BasePreferencesFragment { + + @Override + void setupPreferenceScreen(final PreferenceScreen screen) { + final Context context = getContext(); + if (context == null) { + return; + } + screen.addPreference(getCreatePreference(context)); + screen.addPreference(getRestorePreference(context)); + } + + private Preference getCreatePreference(@NonNull final Context context) { + final Preference preference = new Preference(context); + preference.setTitle(R.string.create_backup); + preference.setIconSpaceReserved(false); + preference.setOnPreferenceClickListener(preference1 -> { + final FragmentManager fragmentManager = getParentFragmentManager(); + final CreateBackupDialogFragment fragment = new CreateBackupDialogFragment(result -> { + final View view = getView(); + if (view != null) { + Snackbar.make(view, + result ? R.string.dialog_export_success + : R.string.dialog_export_failed, + BaseTransientBottomBar.LENGTH_LONG) + .setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE) + .setAction(R.string.ok, v -> {}) + .show(); + return; + } + Toast.makeText(context, + result ? R.string.dialog_export_success + : R.string.dialog_export_failed, + Toast.LENGTH_LONG) + .show(); + }); + final FragmentTransaction ft = fragmentManager.beginTransaction(); + ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) + .add(fragment, "createBackup") + .commit(); + return true; + }); + return preference; + } + + private Preference getRestorePreference(@NonNull final Context context) { + final Preference preference = new Preference(context); + preference.setTitle(R.string.restore_backup); + preference.setIconSpaceReserved(false); + preference.setOnPreferenceClickListener(preference1 -> { + final FragmentManager fragmentManager = getParentFragmentManager(); + final RestoreBackupDialogFragment fragment = new RestoreBackupDialogFragment(result -> { + final View view = getView(); + if (view != null) { + Snackbar.make(view, + result ? R.string.dialog_import_success + : R.string.dialog_import_failed, + BaseTransientBottomBar.LENGTH_LONG) + .setAnimationMode(BaseTransientBottomBar.ANIMATION_MODE_SLIDE) + .setAction(R.string.ok, v -> {}) + .addCallback(new BaseTransientBottomBar.BaseCallback() { + @Override + public void onDismissed(final Snackbar transientBottomBar, final int event) { + recreateActivity(result); + } + }) + .show(); + return; + } + recreateActivity(result); + }); + final FragmentTransaction ft = fragmentManager.beginTransaction(); + ft.setTransition(FragmentTransaction.TRANSIT_FRAGMENT_OPEN) + .add(fragment, "restoreBackup") + .commit(); + return true; + }); + return preference; + } + + private void recreateActivity(final boolean result) { + if (!result) return; + final FragmentActivity activity = getActivity(); + if (activity == null) return; + activity.recreate(); + } +} diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/BasePreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/BasePreferencesFragment.java index 973ab464..caade8b7 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/BasePreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/BasePreferencesFragment.java @@ -21,7 +21,7 @@ public abstract class BasePreferencesFragment extends PreferenceFragmentCompat i @Override public void onCreatePreferences(final Bundle savedInstanceState, final String rootKey) { final PreferenceManager preferenceManager = getPreferenceManager(); - preferenceManager.setSharedPreferencesName("settings"); + preferenceManager.setSharedPreferencesName(Constants.SHARED_PREFERENCES_NAME); preferenceManager.getSharedPreferences().registerOnSharedPreferenceChangeListener(this); final Context context = getContext(); if (context == null) return; 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 ed3b1915..8b12bb38 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java @@ -20,7 +20,7 @@ import androidx.preference.PreferenceCategory; import androidx.preference.PreferenceScreen; import androidx.preference.PreferenceViewHolder; -import java.util.ArrayList; +import java.util.List; import awais.instagrabber.BuildConfig; import awais.instagrabber.R; @@ -55,11 +55,11 @@ public class MorePreferencesFragment extends BasePreferencesFragment { accountCategory.setTitle(R.string.account); accountCategory.setIconSpaceReserved(false); screen.addPreference(accountCategory); - final ArrayList allCookies = Utils.dataBox.getAllCookies(); + final List allCookies = Utils.dataBox.getAllCookies(); if (isLoggedIn) { accountCategory.setSummary(R.string.account_hint); accountCategory.addPreference(getAccountSwitcherPreference(cookie)); - accountCategory.addPreference(getPreference(R.string.logout, R.string.logout_summary, R.drawable.ic_logout, preference -> { + accountCategory.addPreference(getPreference(R.string.logout, R.string.logout_summary, R.drawable.ic_logout_24, preference -> { if (getContext() == null) return false; CookieUtils.setupCookies("LOGOUT"); shouldRecreate(); @@ -79,7 +79,7 @@ public class MorePreferencesFragment extends BasePreferencesFragment { } if (allCookies != null && allCookies.size() > 0) { - accountCategory.addPreference(getPreference(R.string.remove_all_acc, null, R.drawable.ic_delete, preference -> { + accountCategory.addPreference(getPreference(R.string.remove_all_acc, null, R.drawable.ic_account_multiple_remove_24, preference -> { if (getContext() == null) return false; new AlertDialog.Builder(getContext()) .setTitle(R.string.logout) @@ -96,37 +96,49 @@ public class MorePreferencesFragment extends BasePreferencesFragment { })); } - final PreferenceCategory generalCategory = new PreferenceCategory(context); - generalCategory.setTitle(R.string.pref_category_general); - generalCategory.setIconSpaceReserved(false); - screen.addPreference(generalCategory); + // final PreferenceCategory generalCategory = new PreferenceCategory(context); + // generalCategory.setTitle(R.string.pref_category_general); + // generalCategory.setIconSpaceReserved(false); + // screen.addPreference(generalCategory); + screen.addPreference(getDivider(context)); if (isLoggedIn) { - generalCategory.addPreference(getPreference(R.string.action_notif, R.drawable.ic_not_liked, preference -> { + screen.addPreference(getPreference(R.string.action_notif, R.drawable.ic_not_liked, preference -> { final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToNotificationsViewer(); NavHostFragment.findNavController(this).navigate(navDirections); return true; })); } - generalCategory.addPreference(getPreference(R.string.action_settings, R.drawable.ic_outline_settings_24, preference -> { + screen.addPreference(getPreference(R.string.title_favorites, R.drawable.ic_star_24, preference -> { + final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToFavoritesFragment(); + NavHostFragment.findNavController(this).navigate(navDirections); + return true; + })); + + screen.addPreference(getDivider(context)); + screen.addPreference(getPreference(R.string.action_settings, R.drawable.ic_outline_settings_24, preference -> { final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToSettingsPreferencesFragment(); NavHostFragment.findNavController(this).navigate(navDirections); return true; })); - final Preference aboutPreference = getPreference(R.string.action_about, R.drawable.ic_outline_info_24, preference -> { + screen.addPreference(getPreference(R.string.backup_and_restore, R.drawable.ic_settings_backup_restore_24, preference -> { + final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToBackupPreferencesFragment(); + NavHostFragment.findNavController(this).navigate(navDirections); + return true; + })); + screen.addPreference(getPreference(R.string.action_about, R.drawable.ic_outline_info_24, preference1 -> { final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToAboutFragment(); NavHostFragment.findNavController(this).navigate(navDirections); return true; - }); - generalCategory.addPreference(aboutPreference); + })); screen.addPreference(getDivider(context)); - - final Preference versionPreference = getPreference(R.string.version, - BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")", -1, preference -> { - FlavorTown.updateCheck((AppCompatActivity) requireActivity(), true); - return true; - }); - screen.addPreference(versionPreference); + screen.addPreference(getPreference(R.string.version, + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")", + -1, + preference -> { + FlavorTown.updateCheck((AppCompatActivity) requireActivity(), true); + return true; + })); screen.addPreference(getDivider(context)); final Preference reminderPreference = getPreference(R.string.reminder, R.string.reminder_summary, R.drawable.ic_warning, null); diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/SettingsPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/SettingsPreferencesFragment.java index ba3abc9c..90b36445 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/SettingsPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/SettingsPreferencesFragment.java @@ -180,9 +180,9 @@ public class SettingsPreferencesFragment extends BasePreferencesFragment { if (context == null) return null; return new SaveToCustomFolderPreference(context, (resultCallback) -> new DirectoryChooser() .setInitialDirectory(settingsHelper.getString(FOLDER_PATH)) - .setInteractionListener(path -> { - settingsHelper.putString(FOLDER_PATH, path); - resultCallback.onResult(path); + .setInteractionListener(file -> { + settingsHelper.putString(FOLDER_PATH, file.getAbsolutePath()); + resultCallback.onResult(file.getAbsolutePath()); }) .show(getParentFragmentManager(), null)); } diff --git a/app/src/main/java/awais/instagrabber/models/enums/FavoriteType.java b/app/src/main/java/awais/instagrabber/models/enums/FavoriteType.java new file mode 100644 index 00000000..cdf926a9 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/models/enums/FavoriteType.java @@ -0,0 +1,7 @@ +package awais.instagrabber.models.enums; + +public enum FavoriteType { + USER, + HASHTAG, + LOCATION +} diff --git a/app/src/main/java/awais/instagrabber/models/enums/StoryViewerChoice.java b/app/src/main/java/awais/instagrabber/models/enums/StoryViewerChoice.java index 38b91bc3..dd38d7ac 100755 --- a/app/src/main/java/awais/instagrabber/models/enums/StoryViewerChoice.java +++ b/app/src/main/java/awais/instagrabber/models/enums/StoryViewerChoice.java @@ -4,9 +4,8 @@ import java.io.Serializable; public enum StoryViewerChoice implements Serializable { NONE(0), - STORIESIG(1), - ALOINSTAGRAM(2), - INSTADP(3); + ALOINSTAGRAM(1), + INSTADP(2); private int value; diff --git a/app/src/main/java/awais/instagrabber/repositories/TagsRepository.java b/app/src/main/java/awais/instagrabber/repositories/TagsRepository.java new file mode 100644 index 00000000..13cb4857 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/TagsRepository.java @@ -0,0 +1,19 @@ +package awais.instagrabber.repositories; + +import retrofit2.Call; +import retrofit2.http.Header; +import retrofit2.http.POST; +import retrofit2.http.Path; + +public interface TagsRepository { + + @POST("/web/tags/follow/{tag}/") + Call follow(@Header("User-Agent") String userAgent, + @Header("x-csrftoken") String csrfToken, + @Path("tag") String tag); + + @POST("/web/tags/unfollow/{tag}/") + Call unfollow(@Header("User-Agent") String userAgent, + @Header("x-csrftoken") String csrfToken, + @Path("tag") String tag); +} diff --git a/app/src/main/java/awais/instagrabber/services/ActivityCheckerService.java b/app/src/main/java/awais/instagrabber/services/ActivityCheckerService.java index fb01201c..255d051c 100644 --- a/app/src/main/java/awais/instagrabber/services/ActivityCheckerService.java +++ b/app/src/main/java/awais/instagrabber/services/ActivityCheckerService.java @@ -9,6 +9,7 @@ import android.os.Handler; import android.os.IBinder; import android.text.TextUtils; +import androidx.annotation.NonNull; import androidx.core.app.NotificationCompat; import androidx.core.app.NotificationManagerCompat; @@ -115,18 +116,24 @@ public class ActivityCheckerService extends Service { } private void showNotification(final String notificationString) { - final Intent intent = new Intent(getApplicationContext(), MainActivity.class) - .setAction(Constants.ACTION_SHOW_ACTIVITY) - .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); final Notification notification = new NotificationCompat.Builder(this, Constants.ACTIVITY_CHANNEL_ID) .setCategory(NotificationCompat.CATEGORY_STATUS) .setSmallIcon(R.drawable.ic_notif) .setAutoCancel(true) - .setPriority(NotificationCompat.PRIORITY_MIN) + .setOnlyAlertOnce(true) + .setPriority(NotificationCompat.PRIORITY_DEFAULT) .setContentTitle(getString(R.string.action_notif)) .setContentText(notificationString) - .setContentIntent(PendingIntent.getActivity(getApplicationContext(), 1738, intent, PendingIntent.FLAG_UPDATE_CURRENT)) + .setContentIntent(getPendingIntent()) .build(); notificationManager.notify(Constants.ACTIVITY_NOTIFICATION_ID, notification); } + + @NonNull + private PendingIntent getPendingIntent() { + final Intent intent = new Intent(getApplicationContext(), MainActivity.class) + .setAction(Constants.ACTION_SHOW_ACTIVITY) + .setFlags(Intent.FLAG_ACTIVITY_NEW_TASK | Intent.FLAG_ACTIVITY_SINGLE_TOP); + return PendingIntent.getActivity(getApplicationContext(), 1738, intent, PendingIntent.FLAG_UPDATE_CURRENT); + } } diff --git a/app/src/main/java/awais/instagrabber/utils/Constants.java b/app/src/main/java/awais/instagrabber/utils/Constants.java index 6155fd06..4a599261 100644 --- a/app/src/main/java/awais/instagrabber/utils/Constants.java +++ b/app/src/main/java/awais/instagrabber/utils/Constants.java @@ -82,4 +82,6 @@ public final class Constants { public static final String ACTION_SHOW_ACTIVITY = "show_activity"; public static final String PREF_DARK_THEME = "dark_theme"; public static final String PREF_LIGHT_THEME = "light_theme"; + public static final String DEFAULT_HASH_TAG_PIC = "https://www.instagram.com/static/images/hashtag/search-hashtag-default-avatar.png/1d8417c9a4f5.png"; + public static final String SHARED_PREFERENCES_NAME = "settings"; } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/DataBox.java b/app/src/main/java/awais/instagrabber/utils/DataBox.java index d6bad63f..73e71b04 100755 --- a/app/src/main/java/awais/instagrabber/utils/DataBox.java +++ b/app/src/main/java/awais/instagrabber/utils/DataBox.java @@ -6,15 +6,18 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteOpenHelper; import android.util.Log; -import android.widget.Toast; +import android.util.Pair; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.core.util.ObjectsCompat; import java.util.ArrayList; +import java.util.Date; +import java.util.List; import awais.instagrabber.BuildConfig; +import awais.instagrabber.models.enums.FavoriteType; import awaisomereport.LogCollector; import static awais.instagrabber.utils.Utils.logCollector; @@ -24,12 +27,9 @@ public final class DataBox extends SQLiteOpenHelper { private static DataBox sInstance; - private final static int VERSION = 2; + private final static int VERSION = 3; private final static String TABLE_COOKIES = "cookies"; private final static String TABLE_FAVORITES = "favorites"; - private final static String KEY_DATE_ADDED = "date_added"; - private final static String KEY_QUERY_TEXT = "query_text"; - private final static String KEY_QUERY_DISPLAY = "query_display"; private final static String KEY_ID = "id"; private final static String KEY_USERNAME = Constants.EXTRAS_USERNAME; @@ -38,7 +38,12 @@ public final class DataBox extends SQLiteOpenHelper { private final static String KEY_FULL_NAME = "full_name"; private final static String KEY_PROFILE_PIC = "profile_pic"; - private final Context c; + private final static String FAV_COL_ID = "id"; + private final static String FAV_COL_QUERY = "query_text"; + private final static String FAV_COL_TYPE = "type"; + private final static String FAV_COL_DISPLAY_NAME = "display_name"; + private final static String FAV_COL_PIC_URL = "pic_url"; + private final static String FAV_COL_DATE_ADDED = "date_added"; public static synchronized DataBox getInstance(final Context context) { if (sInstance == null) sInstance = new DataBox(context.getApplicationContext()); @@ -47,7 +52,6 @@ public final class DataBox extends SQLiteOpenHelper { private DataBox(@Nullable final Context context) { super(context, "cookiebox.db", null, VERSION); - c = context; } @Override @@ -60,42 +64,120 @@ public final class DataBox extends SQLiteOpenHelper { + KEY_COOKIE + " TEXT," + KEY_FULL_NAME + " TEXT," + KEY_PROFILE_PIC + " TEXT)"); - db.execSQL("CREATE TABLE favorites (id INTEGER PRIMARY KEY, query_text TEXT, date_added INTEGER, query_display TEXT)"); + // db.execSQL("CREATE TABLE favorites (id INTEGER PRIMARY KEY, query_text TEXT, date_added INTEGER, query_display TEXT)"); + db.execSQL("CREATE TABLE " + TABLE_FAVORITES + " (" + + FAV_COL_ID + " INTEGER PRIMARY KEY," + + FAV_COL_QUERY + " TEXT," + + FAV_COL_TYPE + " TEXT," + + FAV_COL_DISPLAY_NAME + " TEXT," + + FAV_COL_PIC_URL + " TEXT," + + FAV_COL_DATE_ADDED + " INTEGER)"); Log.i(TAG, "Tables created!"); } @Override public void onUpgrade(final SQLiteDatabase db, final int oldVersion, final int newVersion) { Log.i(TAG, String.format("Updating DB from v%d to v%d", oldVersion, newVersion)); - if (oldVersion == 1) { - db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_FULL_NAME + " TEXT"); - db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_PROFILE_PIC + " TEXT"); + // switch without break, so that all migrations from a previous version to new are run + switch (oldVersion) { + case 1: + db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_FULL_NAME + " TEXT"); + db.execSQL("ALTER TABLE " + TABLE_COOKIES + " ADD " + KEY_PROFILE_PIC + " TEXT"); + case 2: + final List oldFavorites = backupOldFavorites(db); + // recreate with new columns (as there will be no doubt about the `query_display` column being present or not in the future versions) + db.execSQL("DROP TABLE " + TABLE_FAVORITES); + db.execSQL("CREATE TABLE " + TABLE_FAVORITES + " (" + + FAV_COL_ID + " INTEGER PRIMARY KEY," + + FAV_COL_QUERY + " TEXT," + + FAV_COL_TYPE + " TEXT," + + FAV_COL_DISPLAY_NAME + " TEXT," + + FAV_COL_PIC_URL + " TEXT," + + FAV_COL_DATE_ADDED + " INTEGER)"); + // add the old favorites back + for (final FavoriteModel oldFavorite : oldFavorites) { + addOrUpdateFavorite(db, oldFavorite); + } } Log.i(TAG, String.format("DB update from v%d to v%d completed!", oldVersion, newVersion)); } - public final void addFavorite(@NonNull final FavoriteModel favoriteModel) { - final String query = favoriteModel.getQuery(); - final String display = favoriteModel.getDisplayName(); + @NonNull + private List backupOldFavorites(@NonNull final SQLiteDatabase db) { + // check if old favorites table had the column query_display + final boolean queryDisplayExists = checkColumnExists(db, TABLE_FAVORITES, "query_display"); + Log.d(TAG, "backupOldFavorites: queryDisplayExists: " + queryDisplayExists); + final List oldModels = new ArrayList<>(); + final String sql = "SELECT " + + "query_text," + + "date_added" + + (queryDisplayExists ? ",query_display" : "") + + " FROM " + TABLE_FAVORITES; + try (final Cursor cursor = db.rawQuery(sql, null)) { + if (cursor != null && cursor.moveToFirst()) { + do { + try { + final String queryText = cursor.getString(cursor.getColumnIndex("query_text")); + final Pair favoriteTypeQueryPair = Utils.migrateOldFavQuery(queryText); + if (favoriteTypeQueryPair == null) continue; + final FavoriteType type = favoriteTypeQueryPair.first; + final String query = favoriteTypeQueryPair.second; + oldModels.add(new FavoriteModel( + -1, + query, + type, + queryDisplayExists ? cursor.getString(cursor.getColumnIndex("query_display")) + : null, + null, + new Date(cursor.getLong(cursor.getColumnIndex("date_added"))) + )); + } catch (Exception e) { + Log.e(TAG, "onUpgrade", e); + } + } while (cursor.moveToNext()); + } + } catch (Exception e) { + Log.e(TAG, "onUpgrade", e); + } + Log.d(TAG, "backupOldFavorites: oldModels:" + oldModels); + return oldModels; + } + + public boolean checkColumnExists(@NonNull final SQLiteDatabase db, + @NonNull final String tableName, + @NonNull final String columnName) { + boolean exists = false; + try (Cursor cursor = db.rawQuery("PRAGMA table_info(" + tableName + ")", null)) { + if (cursor.moveToFirst()) { + do { + final String currentColumn = cursor.getString(cursor.getColumnIndex("name")); + if (currentColumn.equals(columnName)) { + exists = true; + } + } while (cursor.moveToNext()); + + } + } catch (Exception ex) { + Log.e(TAG, "checkColumnExists", ex); + } + return exists; + } + + public final void addOrUpdateFavorite(@NonNull final FavoriteModel model) { + final String query = model.getQuery(); if (!TextUtils.isEmpty(query)) { try (final SQLiteDatabase db = getWritableDatabase()) { db.beginTransaction(); try { - final ContentValues values = new ContentValues(); - values.put(KEY_DATE_ADDED, favoriteModel.getDate()); - values.put(KEY_QUERY_TEXT, query); - values.put(KEY_QUERY_DISPLAY, display); - - final int rows = db.update(TABLE_FAVORITES, values, KEY_QUERY_TEXT + "=?", new String[]{query}); - - if (rows != 1) - db.insertOrThrow(TABLE_FAVORITES, null, values); - + addOrUpdateFavorite(db, model); db.setTransactionSuccessful(); } catch (final Exception e) { - if (logCollector != null) - logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "addFavorite"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + if (logCollector != null) { + logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "addOrUpdateFavorite"); + } + if (BuildConfig.DEBUG) { + Log.e(TAG, "Error adding/updating favorite", e); + } } finally { db.endTransaction(); } @@ -103,23 +185,46 @@ public final class DataBox extends SQLiteOpenHelper { } } - public final synchronized void delFavorite(@NonNull final FavoriteModel favoriteModel) { - final String query = favoriteModel.getQuery(); + private void addOrUpdateFavorite(@NonNull final SQLiteDatabase db, @NonNull final FavoriteModel model) { + final ContentValues values = new ContentValues(); + values.put(FAV_COL_QUERY, model.getQuery()); + values.put(FAV_COL_TYPE, model.getType().toString()); + values.put(FAV_COL_DISPLAY_NAME, model.getDisplayName()); + values.put(FAV_COL_PIC_URL, model.getPicUrl()); + values.put(FAV_COL_DATE_ADDED, model.getDateAdded().getTime()); + int rows; + if (model.getId() >= 1) { + rows = db.update(TABLE_FAVORITES, values, FAV_COL_ID + "=?", new String[]{String.valueOf(model.getId())}); + } else { + rows = db.update(TABLE_FAVORITES, + values, + FAV_COL_QUERY + "=?" + + " AND " + FAV_COL_TYPE + "=?", + new String[]{model.getQuery(), model.getType().toString()}); + } + if (rows != 1) { + db.insertOrThrow(TABLE_FAVORITES, null, values); + } + } + + public final synchronized void deleteFavorite(@NonNull final String query, @NonNull final FavoriteType type) { if (!TextUtils.isEmpty(query)) { try (final SQLiteDatabase db = getWritableDatabase()) { db.beginTransaction(); try { - final int rowsDeleted = db.delete(TABLE_FAVORITES, "query_text=? AND date_added=?", - new String[]{query, Long.toString(favoriteModel.getDate())}); + final int rowsDeleted = db.delete(TABLE_FAVORITES, + FAV_COL_QUERY + "=?" + + " AND " + FAV_COL_TYPE + "=?", + new String[]{query, type.toString()}); - final int rowsDeletedTwo = db.delete(TABLE_FAVORITES, "query_text=? AND date_added=?", - new String[]{query.replaceAll("@", ""), Long.toString(favoriteModel.getDate())}); - - if (rowsDeleted > 0 || rowsDeletedTwo > 0) db.setTransactionSuccessful(); + if (rowsDeleted > 0) db.setTransactionSuccessful(); } catch (final Exception e) { - if (logCollector != null) - logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "delFavorite"); - if (BuildConfig.DEBUG) Log.e(TAG, "Error", e); + if (logCollector != null) { + logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "deleteFavorite"); + } + if (BuildConfig.DEBUG) { + Log.e(TAG, "Error", e); + } } finally { db.endTransaction(); } @@ -127,76 +232,87 @@ public final class DataBox extends SQLiteOpenHelper { } } - @Nullable - public final ArrayList getAllFavorites() { - ArrayList favorites = null; - FavoriteModel tempFav; + @NonNull + public final List getAllFavorites() { + final List favorites = new ArrayList<>(); final SQLiteDatabase db = getWritableDatabase(); - - try (final Cursor cursor = db.rawQuery("SELECT query_text, date_added, query_display FROM favorites ORDER BY date_added DESC", null)) { + try (final Cursor cursor = db.rawQuery("SELECT " + + FAV_COL_ID + "," + + FAV_COL_QUERY + "," + + FAV_COL_TYPE + "," + + FAV_COL_DISPLAY_NAME + "," + + FAV_COL_PIC_URL + "," + + FAV_COL_DATE_ADDED + + " FROM " + TABLE_FAVORITES, + null)) { if (cursor != null && cursor.moveToFirst()) { db.beginTransaction(); - favorites = new ArrayList<>(); + FavoriteModel tempFav; do { + FavoriteType type = null; + try { + type = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE))); + } catch (IllegalArgumentException ignored) {} tempFav = new FavoriteModel( - (cursor.getString(0).charAt(0) == '@' || cursor.getString(0).charAt(0) == '#' || cursor.getString(0).contains("/")) - ? cursor.getString(0) - : "@" + cursor.getString(0), // query text - cursor.getLong(1), // date added - cursor.getString(2) == null ? (cursor.getString(0).charAt(0) == '@' || cursor.getString(0).charAt(0) == '#' || cursor - .getString(0).contains("/")) - ? cursor.getString(0) - : "@" + cursor.getString(0) : cursor.getString(2) // display + cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)), + cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)), + type, + cursor.getString(cursor.getColumnIndex(FAV_COL_DISPLAY_NAME)), + cursor.getString(cursor.getColumnIndex(FAV_COL_PIC_URL)), + new Date(cursor.getLong(cursor.getColumnIndex(FAV_COL_DATE_ADDED))) ); - if (cursor.getString(2) == null) { - try { - final ContentValues values = new ContentValues(); - values.put(KEY_DATE_ADDED, tempFav.getDate()); - values.put(KEY_QUERY_TEXT, tempFav.getQuery()); - values.put(KEY_QUERY_DISPLAY, tempFav.getDisplayName()); - - final int rows = db.update(TABLE_FAVORITES, values, KEY_QUERY_TEXT + "=?", new String[]{tempFav.getQuery()}); - - if (rows != 1) - db.insertOrThrow(TABLE_FAVORITES, null, values); - } catch (final Exception e) { - if (logCollector != null) - logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "delFavorite"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); - } - } favorites.add(tempFav); } while (cursor.moveToNext()); db.endTransaction(); } - } catch (final Exception x) { - Log.e("austin_debug", "", x); - try { - db.execSQL("ALTER TABLE favorites ADD query_display TEXT"); - Toast.makeText(c, "DB has migrated, launch quick access again.", Toast.LENGTH_SHORT).show(); - } catch (final Exception e) { - if (logCollector != null) - logCollector.appendException(e, LogCollector.LogFile.DATA_BOX_FAVORITES, "migrate"); - Toast.makeText(c, "DB migration failed, contact maintainer.", Toast.LENGTH_SHORT).show(); - if (BuildConfig.DEBUG) Log.e(TAG, "", e); - } + } catch (final Exception e) { + Log.e(TAG, "", e); } - return favorites; } - public final String getFavorite(@NonNull final String query) { + @Nullable + public final FavoriteModel getFavorite(@NonNull final String query, @NonNull final FavoriteType type) { try (final SQLiteDatabase db = getReadableDatabase(); - final Cursor cursor = db.rawQuery("SELECT query_text, date_added FROM favorites WHERE " - + KEY_QUERY_TEXT + "='" + query + "' ORDER BY date_added DESC", null)) { + final Cursor cursor = db.rawQuery("SELECT " + + FAV_COL_ID + "," + + FAV_COL_QUERY + "," + + FAV_COL_TYPE + "," + + FAV_COL_DISPLAY_NAME + "," + + FAV_COL_PIC_URL + "," + + FAV_COL_DATE_ADDED + + " FROM " + TABLE_FAVORITES + + " WHERE " + FAV_COL_QUERY + "='" + query + "'" + + " AND " + FAV_COL_TYPE + "='" + type.toString() + "'", + null)) { if (cursor != null && cursor.moveToFirst()) { - return cursor.getString(0) + "/" + String.valueOf(cursor.getLong(1)); + FavoriteType favoriteType = null; + try { + favoriteType = FavoriteType.valueOf(cursor.getString(cursor.getColumnIndex(FAV_COL_TYPE))); + } catch (IllegalArgumentException ignored) {} + return new FavoriteModel( + cursor.getInt(cursor.getColumnIndex(FAV_COL_ID)), + cursor.getString(cursor.getColumnIndex(FAV_COL_QUERY)), + favoriteType, + cursor.getString(cursor.getColumnIndex(FAV_COL_DISPLAY_NAME)), + cursor.getString(cursor.getColumnIndex(FAV_COL_PIC_URL)), + new Date(cursor.getLong(cursor.getColumnIndex(FAV_COL_DATE_ADDED))) + ); } } - return null; } + public final void addOrUpdateUser(@NonNull final DataBox.CookieModel cookieModel) { + addOrUpdateUser( + cookieModel.getUid(), + cookieModel.getUsername(), + cookieModel.getCookie(), + cookieModel.getFullName(), + cookieModel.getProfilePic() + ); + } + public final void addOrUpdateUser(final String uid, final String username, final String cookie, @@ -261,15 +377,6 @@ public final class DataBox extends SQLiteOpenHelper { } } - public final int getCookieCount() { - int cookieCount = 0; - try (final SQLiteDatabase db = getReadableDatabase(); - final Cursor cursor = db.rawQuery("SELECT * FROM cookies", null)) { - if (cursor != null) cookieCount = cursor.getCount(); - } - return cookieCount; - } - @Nullable public final CookieModel getCookie(final String uid) { CookieModel cookie = null; @@ -297,10 +404,9 @@ public final class DataBox extends SQLiteOpenHelper { return cookie; } - @Nullable - public final ArrayList getAllCookies() { - ArrayList cookies = null; - + @NonNull + public final List getAllCookies() { + final List cookies = new ArrayList<>(); try (final SQLiteDatabase db = getReadableDatabase(); final Cursor cursor = db.rawQuery( "SELECT " @@ -312,7 +418,6 @@ public final class DataBox extends SQLiteOpenHelper { + " FROM " + TABLE_COOKIES, null) ) { if (cursor != null && cursor.moveToFirst()) { - cookies = new ArrayList<>(); do { cookies.add(new CookieModel( cursor.getString(cursor.getColumnIndex(KEY_UID)), @@ -324,7 +429,6 @@ public final class DataBox extends SQLiteOpenHelper { } while (cursor.moveToNext()); } } - return cookies; } @@ -376,6 +480,12 @@ public final class DataBox extends SQLiteOpenHelper { this.selected = selected; } + public boolean isValid() { + return !TextUtils.isEmpty(uid) + && !TextUtils.isEmpty(username) + && !TextUtils.isEmpty(cookie); + } + @Override public boolean equals(final Object o) { if (this == o) return true; @@ -394,36 +504,92 @@ public final class DataBox extends SQLiteOpenHelper { @NonNull @Override public String toString() { - return username; + return "CookieModel{" + + "uid='" + uid + '\'' + + ", username='" + username + '\'' + + ", cookie='" + cookie + '\'' + + ", fullName='" + fullName + '\'' + + ", profilePic='" + profilePic + '\'' + + ", selected=" + selected + + '}'; } } public static class FavoriteModel { - private final String query, displayName; - private final long date; + private final int id; + private final String query; + private final FavoriteType type; + private final String displayName; + private final String picUrl; + private final Date dateAdded; - public FavoriteModel(final String query, final long date, final String displayName) { + public FavoriteModel(final int id, + final String query, + final FavoriteType type, + final String displayName, + final String picUrl, + final Date dateAdded) { + this.id = id; this.query = query; - this.date = date; + this.type = type; this.displayName = displayName; + this.picUrl = picUrl; + this.dateAdded = dateAdded; + } + + public int getId() { + return id; } public String getQuery() { return query; } + public FavoriteType getType() { + return type; + } + public String getDisplayName() { return displayName; } - public long getDate() { - return date; + public String getPicUrl() { + return picUrl; + } + + public Date getDateAdded() { + return dateAdded; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final FavoriteModel that = (FavoriteModel) o; + return id == that.id && + ObjectsCompat.equals(query, that.query) && + type == that.type && + ObjectsCompat.equals(displayName, that.displayName) && + ObjectsCompat.equals(picUrl, that.picUrl) && + ObjectsCompat.equals(dateAdded, that.dateAdded); + } + + @Override + public int hashCode() { + return ObjectsCompat.hash(id, query, type, displayName, picUrl, dateAdded); } @NonNull @Override public String toString() { - return query; + return "FavoriteModel{" + + "id=" + id + + ", query='" + query + '\'' + + ", type=" + type + + ", displayName='" + displayName + '\'' + + ", picUrl='" + picUrl + '\'' + + ", dateAdded=" + dateAdded + + '}'; } } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/DirectoryChooser.java b/app/src/main/java/awais/instagrabber/utils/DirectoryChooser.java index 969029a1..d349e490 100755 --- a/app/src/main/java/awais/instagrabber/utils/DirectoryChooser.java +++ b/app/src/main/java/awais/instagrabber/utils/DirectoryChooser.java @@ -3,20 +3,26 @@ package awais.instagrabber.utils; import android.app.Activity; import android.app.Dialog; import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.drawable.Drawable; import android.os.Bundle; import android.os.Environment; import android.os.FileObserver; import android.view.LayoutInflater; import android.view.View; import android.view.ViewGroup; -import android.widget.TextView; +import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; import androidx.recyclerview.widget.LinearLayoutManager; -import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.snackbar.BaseTransientBottomBar; +import com.google.android.material.snackbar.Snackbar; import java.io.File; import java.util.ArrayList; @@ -24,22 +30,27 @@ import java.util.Collections; import java.util.List; import awais.instagrabber.R; -import awais.instagrabber.adapters.SimpleAdapter; +import awais.instagrabber.adapters.DirectoryFilesAdapter; +import awais.instagrabber.databinding.LayoutDirectoryChooserBinding; +import awais.instagrabber.viewmodels.FileListViewModel; public final class DirectoryChooser extends DialogFragment { + private static final String TAG = "DirectoryChooser"; + public static final String KEY_CURRENT_DIRECTORY = "CURRENT_DIRECTORY"; private static final File sdcardPathFile = Environment.getExternalStorageDirectory(); private static final String sdcardPath = sdcardPathFile.getPath(); - private final List fileNames = new ArrayList<>(); + private Context context; - private View btnConfirm, btnNavUp, btnCancel; + private LayoutDirectoryChooserBinding binding; + private FileObserver fileObserver; private File selectedDir; private String initialDirectory; - private TextView tvSelectedFolder; - private FileObserver fileObserver; - private SimpleAdapter listDirectoriesAdapter; private OnFragmentInteractionListener interactionListener; - private boolean showZaAiConfigFiles = false; + private boolean showBackupFiles = false; + private View.OnClickListener navigationOnClickListener; + private FileListViewModel fileListViewModel; + private OnCancelListener onCancelListener; public DirectoryChooser() { super(); @@ -51,8 +62,8 @@ public final class DirectoryChooser extends DialogFragment { return this; } - public DirectoryChooser setShowZaAiConfigFiles(final boolean showZaAiConfigFiles) { - this.showZaAiConfigFiles = showZaAiConfigFiles; + public DirectoryChooser setShowBackupFiles(final boolean showBackupFiles) { + this.showBackupFiles = showBackupFiles; return this; } @@ -74,60 +85,71 @@ public final class DirectoryChooser extends DialogFragment { @NonNull @Override public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { + binding = LayoutDirectoryChooserBinding.inflate(inflater, container, false); + init(container); + return binding.getRoot(); + } + + private void init(final ViewGroup container) { Context context = this.context; if (context == null) context = getContext(); if (context == null) context = getActivity(); - if (context == null) context = inflater.getContext(); - - final View view = inflater.inflate(R.layout.layout_directory_chooser, container, false); - - btnNavUp = view.findViewById(R.id.btnNavUp); - btnCancel = view.findViewById(R.id.btnCancel); - btnConfirm = view.findViewById(R.id.btnConfirm); - tvSelectedFolder = view.findViewById(R.id.txtvSelectedFolder); - + if (context == null) return; + if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) != PackageManager.PERMISSION_GRANTED) { + final String text = "Storage permissions denied!"; + if (container == null) { + Toast.makeText(context, text, Toast.LENGTH_LONG).show(); + } else { + Snackbar.make(container, text, BaseTransientBottomBar.LENGTH_LONG).show(); + } + dismiss(); + } final View.OnClickListener clickListener = v -> { - final Object tag; - if (v instanceof TextView && (tag = v.getTag()) instanceof CharSequence) { - final File file = new File(selectedDir, tag.toString()); - if (file.isDirectory()) - changeDirectory(file); - else if (showZaAiConfigFiles && file.isFile()) { - if (interactionListener != null && file.canRead()) - interactionListener.onSelectDirectory(file.getAbsolutePath()); - dismiss(); - } - - } else if (v == btnNavUp) { - final File parent; - if (selectedDir != null && (parent = selectedDir.getParentFile()) != null) - changeDirectory(parent); - - } else if (v == btnConfirm) { + if (v == binding.btnConfirm) { if (interactionListener != null && isValidFile(selectedDir)) - interactionListener.onSelectDirectory(selectedDir.getAbsolutePath()); + interactionListener.onSelectDirectory(selectedDir); dismiss(); - } else if (v == btnCancel) { + } else if (v == binding.btnCancel) { + if (onCancelListener != null) { + onCancelListener.onCancel(); + } dismiss(); } }; - btnNavUp.setOnClickListener(clickListener); - btnCancel.setOnClickListener(clickListener); - btnConfirm.setOnClickListener(clickListener); - - listDirectoriesAdapter = new SimpleAdapter<>(context, fileNames, clickListener); - - final RecyclerView directoriesList = view.findViewById(R.id.directoryList); - directoriesList.setLayoutManager(new LinearLayoutManager(context)); - directoriesList.setAdapter(listDirectoriesAdapter); - + navigationOnClickListener = v -> { + final File parent; + if (selectedDir != null && (parent = selectedDir.getParentFile()) != null) { + changeDirectory(parent); + } + }; + binding.toolbar.setNavigationOnClickListener(navigationOnClickListener); + binding.toolbar.setSubtitle(showBackupFiles ? R.string.select_backup_file : R.string.select_folder); + binding.btnCancel.setOnClickListener(clickListener); + // no need to show confirm for file picker + binding.btnConfirm.setVisibility(showBackupFiles ? View.GONE : View.VISIBLE); + if (!showBackupFiles) { + binding.btnConfirm.setOnClickListener(clickListener); + } + fileListViewModel = new ViewModelProvider(this).get(FileListViewModel.class); + final DirectoryFilesAdapter listDirectoriesAdapter = new DirectoryFilesAdapter(file -> { + if (file.isDirectory()) { + changeDirectory(file); + return; + } + if (showBackupFiles && file.isFile()) { + if (interactionListener != null && file.canRead()) { + interactionListener.onSelectDirectory(file); + } + dismiss(); + } + }); + fileListViewModel.getList().observe(this, listDirectoriesAdapter::submitList); + binding.directoryList.setLayoutManager(new LinearLayoutManager(context)); + binding.directoryList.setAdapter(listDirectoriesAdapter); final File initDir = new File(initialDirectory); final File initialDir = !TextUtils.isEmpty(initialDirectory) && isValidFile(initDir) ? initDir : Environment.getExternalStorageDirectory(); - changeDirectory(initialDir); - - return view; } @Override @@ -153,10 +175,14 @@ public final class DirectoryChooser extends DialogFragment { public void onBackPressed() { if (selectedDir != null) { final String absolutePath = selectedDir.getAbsolutePath(); - if (absolutePath.equals(sdcardPath) || absolutePath.equals(sdcardPathFile.getAbsolutePath())) + if (absolutePath.equals(sdcardPath) || absolutePath.equals(sdcardPathFile.getAbsolutePath())) { + if (onCancelListener != null) { + onCancelListener.onCancel(); + } dismiss(); - else + } else { changeDirectory(selectedDir.getParentFile()); + } } } }; @@ -189,21 +215,28 @@ public final class DirectoryChooser extends DialogFragment { private void changeDirectory(final File dir) { if (dir != null && dir.isDirectory()) { final String path = dir.getAbsolutePath(); - + binding.toolbar.setTitle(path); final File[] contents = dir.listFiles(); if (contents != null) { - fileNames.clear(); - + final List fileNames = new ArrayList<>(); for (final File f : contents) { final String name = f.getName(); - if (f.isDirectory() || showZaAiConfigFiles && f.isFile() && name.toLowerCase().endsWith(".zaai")) - fileNames.add(name); + final String nameLowerCase = name.toLowerCase(); + final boolean isBackupFile = nameLowerCase.endsWith(".zaai") || nameLowerCase.endsWith(".backup"); + if (f.isDirectory() || (showBackupFiles && f.isFile() && isBackupFile)) + fileNames.add(f); } - - Collections.sort(fileNames); + Collections.sort(fileNames, (o1, o2) -> { + if ((o1.isDirectory() && o2.isDirectory()) + || (o1.isFile() && o2.isFile())) { + return o1.getName().compareToIgnoreCase(o2.getName()); + } + if (o1.isDirectory()) return -1; + if (o2.isDirectory()) return 1; + return 0; + }); + fileListViewModel.getList().postValue(fileNames); selectedDir = dir; - tvSelectedFolder.setText(path); - listDirectoriesAdapter.notifyDataSetChanged(); fileObserver = new FileObserver(path, FileObserver.CREATE | FileObserver.DELETE | FileObserver.MOVED_FROM | FileObserver.MOVED_TO) { private final Runnable currentDirRefresher = () -> changeDirectory(selectedDir); @@ -222,15 +255,15 @@ public final class DirectoryChooser extends DialogFragment { if (selectedDir != null) { final String path = selectedDir.getAbsolutePath(); toggleUpButton(!path.equals(sdcardPathFile.getAbsolutePath()) && selectedDir != sdcardPathFile); - btnConfirm.setEnabled(isValidFile(selectedDir)); + binding.btnConfirm.setEnabled(isValidFile(selectedDir)); } } private void toggleUpButton(final boolean enable) { - if (btnNavUp != null) { - btnNavUp.setEnabled(enable); - btnNavUp.setAlpha(enable ? 1f : 0.617f); - } + binding.toolbar.setNavigationOnClickListener(enable ? navigationOnClickListener : null); + final Drawable navigationIcon = binding.toolbar.getNavigationIcon(); + if (navigationIcon == null) return; + navigationIcon.setAlpha(enable ? 255 : (int) (255 * 0.617)); } private boolean isValidFile(final File file) { @@ -242,7 +275,17 @@ public final class DirectoryChooser extends DialogFragment { return this; } + public void setOnCancelListener(final OnCancelListener onCancelListener) { + if (onCancelListener != null) { + this.onCancelListener = onCancelListener; + } + } + + public interface OnCancelListener { + void onCancel(); + } + public interface OnFragmentInteractionListener { - void onSelectDirectory(final String path); + void onSelectDirectory(final File file); } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/DirectoryUtils.java b/app/src/main/java/awais/instagrabber/utils/DirectoryUtils.java new file mode 100644 index 00000000..710510a3 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/utils/DirectoryUtils.java @@ -0,0 +1,76 @@ +package awais.instagrabber.utils; + +import android.os.Build; +import android.os.Environment; + +import java.io.File; +import java.util.Collections; +import java.util.HashSet; +import java.util.Set; +import java.util.regex.Pattern; + +public class DirectoryUtils { + private static final Pattern DIR_SEPORATOR = Pattern.compile("/"); + + /** + * From: https://stackoverflow.com/a/18871043/1436766 + * + * Returns all available SD-Cards in the system (include emulated) + *

+ * Warning: Hack! Based on Android source code of version 4.3 (API 18) + * Because there is no standard way to get it. + * TODO: Test on future Android versions 4.4+ + * + * @return paths to all available SD-Cards in the system (include emulated) + */ + public static Set getStorageDirectories() { + // Final set of paths + final Set rv = new HashSet<>(); + // Primary physical SD-CARD (not emulated) + final String rawExternalStorage = System.getenv("EXTERNAL_STORAGE"); + // All Secondary SD-CARDs (all exclude primary) separated by ":" + final String rawSecondaryStoragesStr = System.getenv("SECONDARY_STORAGE"); + // Primary emulated SD-CARD + final String rawEmulatedStorageTarget = System.getenv("EMULATED_STORAGE_TARGET"); + if (TextUtils.isEmpty(rawEmulatedStorageTarget)) { + // Device has physical external storage; use plain paths. + if (TextUtils.isEmpty(rawExternalStorage)) { + // EXTERNAL_STORAGE undefined; falling back to default. + rv.add("/storage/sdcard0"); + } else { + rv.add(rawExternalStorage); + } + } else { + // Device has emulated storage; external storage paths should have + // userId burned into them. + final String rawUserId; + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN_MR1) { + rawUserId = ""; + } else { + final String path = Environment.getExternalStorageDirectory().getAbsolutePath(); + final String[] folders = DIR_SEPORATOR.split(path); + final String lastFolder = folders[folders.length - 1]; + boolean isDigit = false; + try { + Integer.valueOf(lastFolder); + isDigit = true; + } catch (NumberFormatException ignored) { + } + rawUserId = isDigit ? lastFolder : ""; + } + // /storage/emulated/0[1,2,...] + if (TextUtils.isEmpty(rawUserId)) { + rv.add(rawEmulatedStorageTarget); + } else { + rv.add(rawEmulatedStorageTarget + File.separator + rawUserId); + } + } + // Add all secondary storages + if (!TextUtils.isEmpty(rawSecondaryStoragesStr)) { + // All Secondary SD-CARDs splited into array + final String[] rawSecondaryStorages = rawSecondaryStoragesStr.split(File.pathSeparator); + Collections.addAll(rv, rawSecondaryStorages); + } + return rv; + } +} diff --git a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java index 4b867f87..48576a98 100644 --- a/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DownloadUtils.java @@ -37,7 +37,9 @@ import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; public final class DownloadUtils { public static final String[] PERMS = new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}; - public static void batchDownload(@NonNull final Context context, @Nullable String username, final DownloadMethod method, + public static void batchDownload(@NonNull final Context context, + @Nullable String username, + final DownloadMethod method, final List itemsToDownload) { if (Utils.settingsHelper == null) Utils.settingsHelper = new SettingsHelper(context); diff --git a/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java b/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java index 750e3b80..3770bd65 100755 --- a/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java @@ -1,40 +1,40 @@ package awais.instagrabber.utils; import android.content.Context; -import android.text.InputFilter; -import android.text.InputType; +import android.content.SharedPreferences; import android.util.Base64; import android.util.Log; +import android.util.Pair; import android.widget.Toast; import androidx.annotation.IntDef; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.appcompat.app.AlertDialog; -import androidx.appcompat.widget.AppCompatEditText; import org.json.JSONArray; +import org.json.JSONException; import org.json.JSONObject; import java.io.File; import java.io.FileInputStream; import java.io.FileOutputStream; -import java.util.ArrayList; +import java.util.Date; import java.util.Iterator; - -import javax.crypto.Cipher; -import javax.crypto.spec.IvParameterSpec; -import javax.crypto.spec.SecretKeySpec; +import java.util.List; +import java.util.Map; import awais.instagrabber.BuildConfig; -import awais.instagrabber.R; import awais.instagrabber.interfaces.FetchListener; +import awais.instagrabber.models.enums.FavoriteType; +import awais.instagrabber.utils.PasswordUtils.IncorrectPasswordException; import awaisomereport.LogCollector.LogFile; import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.settingsHelper; public final class ExportImportUtils { + private static final String TAG = "ExportImportUtils"; + public static final int FLAG_COOKIES = 1; public static final int FLAG_FAVORITES = 1 << 1; public static final int FLAG_SETTINGS = 1 << 2; @@ -42,292 +42,289 @@ public final class ExportImportUtils { @IntDef(value = {FLAG_COOKIES, FLAG_FAVORITES, FLAG_SETTINGS}, flag = true) @interface ExportImportFlags {} - public static void Export(@Nullable final String password, @ExportImportFlags final int flags, @NonNull final File filePath, - final FetchListener fetchListener) { - final String exportString = ExportImportUtils.getExportString(flags); - if (!TextUtils.isEmpty(exportString)) { - final boolean isPass = !TextUtils.isEmpty(password); - byte[] exportBytes = null; - - if (isPass) { - final byte[] passwordBytes = password.getBytes(); - final byte[] bytes = new byte[32]; - System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32)); - - try { - exportBytes = PasswordUtils.enc(exportString, bytes); - } catch (final Exception e) { - if (fetchListener != null) fetchListener.onResult(false); - if (logCollector != null) - logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::isPass"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); - } - } else { - exportBytes = Base64.encode(exportString.getBytes(), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING); + public static void exportData(@Nullable final String password, + @ExportImportFlags final int flags, + @NonNull final File filePath, + final FetchListener fetchListener, + @NonNull final Context context) { + final String exportString = getExportString(flags, context); + if (TextUtils.isEmpty(exportString)) return; + final boolean isPass = !TextUtils.isEmpty(password); + byte[] exportBytes = null; + if (isPass) { + final byte[] passwordBytes = password.getBytes(); + final byte[] bytes = new byte[32]; + System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32)); + try { + exportBytes = PasswordUtils.enc(exportString, bytes); + } catch (final Exception e) { + if (fetchListener != null) fetchListener.onResult(false); + if (logCollector != null) + logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::isPass"); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); } - - if (exportBytes != null && exportBytes.length > 1) { - try (final FileOutputStream fos = new FileOutputStream(filePath)) { - fos.write(isPass ? 'A' : 'Z'); - fos.write(exportBytes); - if (fetchListener != null) fetchListener.onResult(true); - } catch (final Exception e) { - if (fetchListener != null) fetchListener.onResult(false); - if (logCollector != null) - logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::notPass"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); - } - } else if (fetchListener != null) fetchListener.onResult(false); + } else { + exportBytes = Base64.encode(exportString.getBytes(), Base64.DEFAULT | Base64.NO_WRAP | Base64.NO_PADDING); } + if (exportBytes != null && exportBytes.length > 1) { + try (final FileOutputStream fos = new FileOutputStream(filePath)) { + fos.write(isPass ? 'A' : 'Z'); + fos.write(exportBytes); + if (fetchListener != null) fetchListener.onResult(true); + } catch (final Exception e) { + if (fetchListener != null) fetchListener.onResult(false); + if (logCollector != null) + logCollector.appendException(e, LogFile.UTILS_EXPORT, "Export::notPass"); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); + } + } else if (fetchListener != null) fetchListener.onResult(false); } - public static void Import(@NonNull final Context context, @ExportImportFlags final int flags, @NonNull final File filePath, - final FetchListener fetchListener) { - try (final FileInputStream fis = new FileInputStream(filePath)) { + public static void importData(@NonNull final Context context, + @ExportImportFlags final int flags, + @NonNull final File file, + final String password, + final FetchListener fetchListener) throws IncorrectPasswordException { + try (final FileInputStream fis = new FileInputStream(file)) { final int configType = fis.read(); - final StringBuilder builder = new StringBuilder(); int c; while ((c = fis.read()) != -1) { builder.append((char) c); } - if (configType == 'A') { // password - final AppCompatEditText editText = new AppCompatEditText(context); - editText.setFilters(new InputFilter[]{new InputFilter.LengthFilter(32)}); - editText.setInputType(InputType.TYPE_TEXT_VARIATION_PASSWORD); - new AlertDialog.Builder(context).setView(editText).setTitle(R.string.password) - .setPositiveButton(R.string.confirm, (dialog, which) -> { - final CharSequence text = editText.getText(); - if (!TextUtils.isEmpty(text)) { - try { - final byte[] passwordBytes = text.toString().getBytes(); - final byte[] bytes = new byte[32]; - System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32)); - saveToSettings(new String(PasswordUtils.dec(builder.toString(), bytes)), flags, - fetchListener); - } catch (final Exception e) { - if (fetchListener != null) fetchListener.onResult(false); - if (logCollector != null) - logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import::pass"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); - } - - } else - Toast.makeText(context, R.string.dialog_export_err_password_empty, Toast.LENGTH_SHORT).show(); - }).show(); - + if (TextUtils.isEmpty(password)) return; + try { + final byte[] passwordBytes = password.getBytes(); + final byte[] bytes = new byte[32]; + System.arraycopy(passwordBytes, 0, bytes, 0, Math.min(passwordBytes.length, 32)); + importJson(new String(PasswordUtils.dec(builder.toString(), bytes)), + flags, + fetchListener); + } catch (final IncorrectPasswordException e) { + throw e; + } catch (final Exception e) { + if (fetchListener != null) fetchListener.onResult(false); + if (logCollector != null) + logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import::pass"); + if (BuildConfig.DEBUG) Log.e(TAG, "Error importing backup", e); + } } else if (configType == 'Z') { - saveToSettings(new String(Base64.decode(builder.toString(), Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP)), - flags, fetchListener); + importJson(new String(Base64.decode(builder.toString(), Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP)), + flags, + fetchListener); } else { - Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); + Toast.makeText(context, "File is corrupted!", Toast.LENGTH_LONG).show(); if (fetchListener != null) fetchListener.onResult(false); } + } catch (IncorrectPasswordException e) { + // separately handle incorrect password + throw e; } catch (final Exception e) { if (fetchListener != null) fetchListener.onResult(false); if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "Import"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); } } - private static void saveToSettings(final String json, @ExportImportFlags final int flags, final FetchListener fetchListener) { + private static void importJson(@NonNull final String json, + @ExportImportFlags final int flags, + final FetchListener fetchListener) { try { final JSONObject jsonObject = new JSONObject(json); - if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS && jsonObject.has("settings")) { - final JSONObject objSettings = jsonObject.getJSONObject("settings"); - final Iterator keys = objSettings.keys(); - while (keys.hasNext()) { - final String key = keys.next(); - final Object val = objSettings.opt(key); - if (val instanceof String) { - settingsHelper.putString(key, (String) val); - } else if (val instanceof Integer) { - settingsHelper.putInteger(key, (int) val); - } else if (val instanceof Boolean) { - settingsHelper.putBoolean(key, (boolean) val); - } - } + importSettings(jsonObject); } - if ((flags & FLAG_COOKIES) == FLAG_COOKIES && jsonObject.has("cookies")) { - final JSONArray cookies = jsonObject.getJSONArray("cookies"); - final int cookiesLen = cookies.length(); - for (int i = 0; i < cookiesLen; ++i) { - final JSONObject cookieObject = cookies.getJSONObject(i); - // final DataBox.CookieModel cookieModel = new DataBox.CookieModel(cookieObject.getString("i"), - // cookieObject.getString("u"), - // cookieObject.getString("c"), - // fullName, - // profilePic); - // Utils.dataBox.addOrUpdateUser(cookieModel.getUid(), cookieModel.getUserInfo(), cookieModel.getCookie()); - } + importAccounts(jsonObject); } - if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES && jsonObject.has("favs")) { - final JSONArray favs = jsonObject.getJSONArray("favs"); - final int favsLen = favs.length(); - for (int i = 0; i < favsLen; ++i) { - final JSONObject favsObject = favs.getJSONObject(i); - Utils.dataBox.addFavorite(new DataBox.FavoriteModel(favsObject.getString("q"), - favsObject.getLong("d"), - favsObject.has("s") ? favsObject.getString("s") : favsObject.getString("q"))); - } + importFavorites(jsonObject); } - if (fetchListener != null) fetchListener.onResult(true); - } catch (final Exception e) { if (fetchListener != null) fetchListener.onResult(false); - if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "saveToSettings"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_IMPORT, "importJson"); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); } } + private static void importFavorites(final JSONObject jsonObject) throws JSONException { + final JSONArray favs = jsonObject.getJSONArray("favs"); + for (int i = 0; i < favs.length(); i++) { + final JSONObject favsObject = favs.getJSONObject(i); + final String queryText = favsObject.optString("q"); + if (TextUtils.isEmpty(queryText)) continue; + final Pair favoriteTypeQueryPair; + String query = null; + FavoriteType favoriteType = null; + if (queryText.contains("@") + || queryText.contains("#") + || queryText.contains("/")) { + favoriteTypeQueryPair = Utils.migrateOldFavQuery(queryText); + if (favoriteTypeQueryPair != null) { + query = favoriteTypeQueryPair.second; + favoriteType = favoriteTypeQueryPair.first; + } + } else { + query = queryText; + favoriteType = FavoriteType.valueOf(favsObject.optString("type")); + } + if (query == null || favoriteType == null) { + continue; + } + final DataBox.FavoriteModel favoriteModel = new DataBox.FavoriteModel( + -1, + query, + favoriteType, + favsObject.optString("s"), + favoriteType == FavoriteType.HASHTAG ? null + : favsObject.optString("pic_url"), + new Date(favsObject.getLong("d"))); + // Log.d(TAG, "importJson: favoriteModel: " + favoriteModel); + Utils.dataBox.addOrUpdateFavorite(favoriteModel); + } + } + + private static void importAccounts(final JSONObject jsonObject) throws JSONException { + final JSONArray cookies = jsonObject.getJSONArray("cookies"); + for (int i = 0; i < cookies.length(); i++) { + final JSONObject cookieObject = cookies.getJSONObject(i); + final DataBox.CookieModel cookieModel = new DataBox.CookieModel( + cookieObject.optString("i"), + cookieObject.optString("u"), + cookieObject.optString("c"), + cookieObject.optString("full_name"), + cookieObject.optString("profile_pic") + ); + if (!cookieModel.isValid()) continue; + // Log.d(TAG, "importJson: cookieModel: " + cookieModel); + Utils.dataBox.addOrUpdateUser(cookieModel); + } + } + + private static void importSettings(final JSONObject jsonObject) throws JSONException { + final JSONObject objSettings = jsonObject.getJSONObject("settings"); + final Iterator keys = objSettings.keys(); + while (keys.hasNext()) { + final String key = keys.next(); + final Object val = objSettings.opt(key); + // Log.d(TAG, "importJson: key: " + key + ", val: " + val); + if (val instanceof String) { + settingsHelper.putString(key, (String) val); + } else if (val instanceof Integer) { + settingsHelper.putInteger(key, (int) val); + } else if (val instanceof Boolean) { + settingsHelper.putBoolean(key, (boolean) val); + } + } + } + + public static boolean isEncrypted(final File file) { + try (final FileInputStream fis = new FileInputStream(file)) { + final int configType = fis.read(); + if (configType == 'A') { + return true; + } + } catch (final Exception e) { + Log.e(TAG, "isEncrypted", e); + } + return false; + } + @Nullable - private static String getExportString(@ExportImportFlags final int flags) { + private static String getExportString(@ExportImportFlags final int flags, + @NonNull final Context context) { String result = null; try { final JSONObject jsonObject = new JSONObject(); - - String str; if ((flags & FLAG_SETTINGS) == FLAG_SETTINGS) { - str = getSettings(); - if (str != null) jsonObject.put("settings", new JSONObject(str)); + jsonObject.put("settings", getSettings(context)); } - if ((flags & FLAG_COOKIES) == FLAG_COOKIES) { - str = getCookies(); - if (str != null) jsonObject.put("cookies", new JSONArray(str)); + jsonObject.put("cookies", getCookies()); } - if ((flags & FLAG_FAVORITES) == FLAG_FAVORITES) { - str = getFavorites(); - if (str != null) jsonObject.put("favs", new JSONArray(str)); + jsonObject.put("favs", getFavorites()); } result = jsonObject.toString(); } catch (final Exception e) { if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getExportString"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + if (BuildConfig.DEBUG) Log.e(TAG, "", e); } return result; } - @Nullable - private static String getSettings() { - String result = null; + @NonNull + private static JSONObject getSettings(@NonNull final Context context) { + final SharedPreferences sharedPreferences = context.getSharedPreferences(Constants.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); + final Map allPrefs = sharedPreferences.getAll(); + if (allPrefs == null) { + return new JSONObject(); + } + try { + final JSONObject jsonObject = new JSONObject(allPrefs); + jsonObject.remove(Constants.COOKIE); + jsonObject.remove(Constants.DEVICE_UUID); + jsonObject.remove(Constants.PREV_INSTALL_VERSION); + return jsonObject; + } catch (Exception e) { + Log.e(TAG, "Error exporting settings", e); + } + return new JSONObject(); + } - if (settingsHelper != null) { - try { - final JSONObject json = new JSONObject(); - json.put(Constants.APP_THEME, settingsHelper.getString(Constants.APP_THEME)); - json.put(Constants.APP_LANGUAGE, settingsHelper.getString(Constants.APP_LANGUAGE)); - - String str = settingsHelper.getString(Constants.FOLDER_PATH); - if (!TextUtils.isEmpty(str)) json.put(Constants.FOLDER_PATH, str); - - str = settingsHelper.getString(Constants.DATE_TIME_FORMAT); - if (!TextUtils.isEmpty(str)) json.put(Constants.DATE_TIME_FORMAT, str); - - str = settingsHelper.getString(Constants.DATE_TIME_SELECTION); - if (!TextUtils.isEmpty(str)) json.put(Constants.DATE_TIME_SELECTION, str); - - str = settingsHelper.getString(Constants.CUSTOM_DATE_TIME_FORMAT); - if (!TextUtils.isEmpty(str)) json.put(Constants.CUSTOM_DATE_TIME_FORMAT, str); - - json.put(Constants.DOWNLOAD_USER_FOLDER, settingsHelper.getBoolean(Constants.DOWNLOAD_USER_FOLDER)); - json.put(Constants.MUTED_VIDEOS, settingsHelper.getBoolean(Constants.MUTED_VIDEOS)); - json.put(Constants.AUTOPLAY_VIDEOS, settingsHelper.getBoolean(Constants.AUTOPLAY_VIDEOS)); - json.put(Constants.AUTOLOAD_POSTS, settingsHelper.getBoolean(Constants.AUTOLOAD_POSTS)); - json.put(Constants.FOLDER_SAVE_TO, settingsHelper.getBoolean(Constants.FOLDER_SAVE_TO)); - - result = json.toString(); - } catch (final Exception e) { - result = null; - if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getSettings"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + @NonNull + private static JSONArray getFavorites() { + if (Utils.dataBox == null) return new JSONArray(); + try { + final List allFavorites = Utils.dataBox.getAllFavorites(); + final JSONArray jsonArray = new JSONArray(); + for (final DataBox.FavoriteModel favorite : allFavorites) { + final JSONObject jsonObject = new JSONObject(); + jsonObject.put("q", favorite.getQuery()); + jsonObject.put("type", favorite.getType().toString()); + jsonObject.put("s", favorite.getDisplayName()); + jsonObject.put("pic_url", favorite.getPicUrl()); + jsonObject.put("d", favorite.getDateAdded().getTime()); + jsonArray.put(jsonObject); + } + return jsonArray; + } catch (final Exception e) { + if (logCollector != null) { + logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites"); + } + if (BuildConfig.DEBUG) { + Log.e(TAG, "Error exporting favorites", e); } } - - return result; + return new JSONArray(); } - @Nullable - private static String getFavorites() { - String result = null; - if (Utils.dataBox != null) { - try { - final ArrayList allFavorites = Utils.dataBox.getAllFavorites(); - final int allFavoritesSize; - if (allFavorites != null && (allFavoritesSize = allFavorites.size()) > 0) { - final JSONArray jsonArray = new JSONArray(); - for (int i = 0; i < allFavoritesSize; i++) { - final DataBox.FavoriteModel favorite = allFavorites.get(i); - final JSONObject jsonObject = new JSONObject(); - jsonObject.put("q", favorite.getQuery()); - jsonObject.put("d", favorite.getDate()); - jsonObject.put("s", favorite.getDisplayName()); - jsonArray.put(jsonObject); - } - result = jsonArray.toString(); - } - } catch (final Exception e) { - result = null; - if (logCollector != null) logCollector.appendException(e, LogFile.UTILS_EXPORT, "getFavorites"); - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); + @NonNull + private static JSONArray getCookies() { + if (Utils.dataBox == null) return new JSONArray(); + try { + final List allCookies = Utils.dataBox.getAllCookies(); + final JSONArray jsonArray = new JSONArray(); + for (final DataBox.CookieModel cookie : allCookies) { + final JSONObject jsonObject = new JSONObject(); + jsonObject.put("i", cookie.getUid()); + jsonObject.put("u", cookie.getUsername()); + jsonObject.put("c", cookie.getCookie()); + jsonObject.put("full_name", cookie.getFullName()); + jsonObject.put("profile_pic", cookie.getProfilePic()); + jsonArray.put(jsonObject); + } + return jsonArray; + } catch (final Exception e) { + if (BuildConfig.DEBUG) { + Log.e(TAG, "Error exporting accounts", e); } } - return result; - } - - @Nullable - private static String getCookies() { - String result = null; - if (Utils.dataBox != null) { - try { - final ArrayList allCookies = Utils.dataBox.getAllCookies(); - final int allCookiesSize; - if (allCookies != null && (allCookiesSize = allCookies.size()) > 0) { - final JSONArray jsonArray = new JSONArray(); - for (int i = 0; i < allCookiesSize; i++) { - final DataBox.CookieModel cookieModel = allCookies.get(i); - final JSONObject jsonObject = new JSONObject(); - jsonObject.put("i", cookieModel.getUid()); - jsonObject.put("u", cookieModel.getUsername()); - jsonObject.put("c", cookieModel.getCookie()); - jsonArray.put(jsonObject); - } - result = jsonArray.toString(); - } - } catch (final Exception e) { - result = null; - if (BuildConfig.DEBUG) Log.e("AWAISKING_APP", "", e); - } - } - return result; - } - - private final static class PasswordUtils { - private static final String cipherAlgo = "AES"; - private static final String cipherTran = "AES/CBC/PKCS5Padding"; - - private static byte[] dec(final String encrypted, final byte[] keyValue) throws Exception { - final Cipher cipher = Cipher.getInstance(cipherTran); - final SecretKeySpec secretKey = new SecretKeySpec(keyValue, cipherAlgo); - cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(new byte[16])); - return cipher.doFinal(Base64.decode(encrypted, Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP)); - } - - private static byte[] enc(@NonNull final String str, final byte[] keyValue) throws Exception { - final Cipher cipher = Cipher.getInstance(cipherTran); - final SecretKeySpec secretKey = new SecretKeySpec(keyValue, cipherAlgo); - cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(new byte[16])); - final byte[] bytes = cipher.doFinal(str.getBytes()); - return Base64.encode(bytes, Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP); - } + return new JSONArray(); } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java b/app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java index 87c225a1..9af64d87 100644 --- a/app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java +++ b/app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java @@ -34,7 +34,7 @@ public class NavigationExtensions { int firstFragmentGraphId = 0; for (int i = 0; i < navGraphIds.size(); i++) { final int navGraphId = navGraphIds.get(i); - final String fragmentTag = getFragmentTag(i); + final String fragmentTag = getFragmentTag(navGraphId); final NavHostFragment navHostFragment = obtainNavHostFragment(fragmentManager, fragmentTag, navGraphId, containerId); final NavController navController = navHostFragment.getNavController(); final int graphId = navController.getGraph().getId(); @@ -57,7 +57,8 @@ public class NavigationExtensions { return false; } String newlySelectedItemTag = graphIdToTagMap.get(item.getItemId()); - if (!selectedItemTag[0].equals(newlySelectedItemTag)) { + String tag = selectedItemTag[0]; + if (tag != null && !tag.equals(newlySelectedItemTag)) { fragmentManager.popBackStack(firstFragmentTag, FragmentManager.POP_BACK_STACK_INCLUSIVE); Fragment fragment = fragmentManager.findFragmentByTag(newlySelectedItemTag); if (fragment == null) { @@ -176,7 +177,7 @@ public class NavigationExtensions { final Intent intent) { for (int i = 0; i < navGraphIds.size(); i++) { final int navGraphId = navGraphIds.get(i); - final String fragmentTag = getFragmentTag(i); + final String fragmentTag = getFragmentTag(navGraphId); final NavHostFragment navHostFragment = obtainNavHostFragment(fragmentManager, fragmentTag, navGraphId, containerId); if (navHostFragment.getNavController().handleDeepLink(intent)) { final int selectedItemId = bottomNavigationView.getSelectedItemId(); diff --git a/app/src/main/java/awais/instagrabber/utils/PasswordUtils.java b/app/src/main/java/awais/instagrabber/utils/PasswordUtils.java new file mode 100644 index 00000000..2cc380bc --- /dev/null +++ b/app/src/main/java/awais/instagrabber/utils/PasswordUtils.java @@ -0,0 +1,47 @@ +package awais.instagrabber.utils; + +import android.util.Base64; + +import androidx.annotation.NonNull; + +import java.security.GeneralSecurityException; +import java.security.InvalidAlgorithmParameterException; +import java.security.InvalidKeyException; +import java.security.NoSuchAlgorithmException; + +import javax.crypto.BadPaddingException; +import javax.crypto.Cipher; +import javax.crypto.IllegalBlockSizeException; +import javax.crypto.NoSuchPaddingException; +import javax.crypto.spec.IvParameterSpec; +import javax.crypto.spec.SecretKeySpec; + +public final class PasswordUtils { + private static final String cipherAlgo = "AES"; + private static final String cipherTran = "AES/CBC/PKCS5Padding"; + + public static byte[] dec(final String encrypted, final byte[] keyValue) throws Exception { + try { + final Cipher cipher = Cipher.getInstance(cipherTran); + final SecretKeySpec secretKey = new SecretKeySpec(keyValue, cipherAlgo); + cipher.init(Cipher.DECRYPT_MODE, secretKey, new IvParameterSpec(new byte[16])); + return cipher.doFinal(Base64.decode(encrypted, Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP)); + } catch (NoSuchAlgorithmException | NoSuchPaddingException | InvalidAlgorithmParameterException | InvalidKeyException | BadPaddingException | IllegalBlockSizeException e) { + throw new IncorrectPasswordException(e); + } + } + + public static byte[] enc(@NonNull final String str, final byte[] keyValue) throws Exception { + final Cipher cipher = Cipher.getInstance(cipherTran); + final SecretKeySpec secretKey = new SecretKeySpec(keyValue, cipherAlgo); + cipher.init(Cipher.ENCRYPT_MODE, secretKey, new IvParameterSpec(new byte[16])); + final byte[] bytes = cipher.doFinal(str.getBytes()); + return Base64.encode(bytes, Base64.DEFAULT | Base64.NO_PADDING | Base64.NO_WRAP); + } + + public static class IncorrectPasswordException extends Exception { + public IncorrectPasswordException(final GeneralSecurityException e) { + super(e); + } + } +} diff --git a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java index a5610b22..658177d3 100644 --- a/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/ResponseBodyUtils.java @@ -232,8 +232,8 @@ public final class ResponseBodyUtils { final String threadV2Id = data.getString("thread_v2_id"); final String threadTitle = data.getString("thread_title"); - final String threadNewestCursor = data.getString("newest_cursor"); - final String threadOldestCursor = data.getString("oldest_cursor"); + final String threadNewestCursor = data.optString("newest_cursor"); + final String threadOldestCursor = data.optString("oldest_cursor"); final String threadNextCursor = data.has("next_cursor") ? data.getString("next_cursor") : null; final String threadPrevCursor = data.has("prev_cursor") ? data.getString("prev_cursor") : null; diff --git a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java index aa39823e..34ad3102 100755 --- a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java +++ b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java @@ -40,7 +40,7 @@ public final class SettingsHelper { private final SharedPreferences sharedPreferences; public SettingsHelper(@NonNull final Context context) { - this.sharedPreferences = context.getSharedPreferences("settings", Context.MODE_PRIVATE); + this.sharedPreferences = context.getSharedPreferences(Constants.SHARED_PREFERENCES_NAME, Context.MODE_PRIVATE); } @NonNull diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index 1fed5097..6e3b9ca6 100755 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/app/src/main/java/awais/instagrabber/utils/Utils.java @@ -1,28 +1,20 @@ package awais.instagrabber.utils; import android.app.Activity; -import android.app.AlertDialog; import android.content.ClipData; import android.content.ClipboardManager; import android.content.ContentResolver; import android.content.Context; -import android.content.res.Configuration; import android.content.res.Resources; import android.net.Uri; -import android.os.Build; -import android.text.Editable; import android.util.DisplayMetrics; import android.util.Log; -import android.view.LayoutInflater; -import android.view.View; +import android.util.Pair; import android.webkit.MimeTypeMap; import android.widget.Toast; import androidx.annotation.NonNull; -import androidx.annotation.StringRes; -import androidx.appcompat.app.AppCompatActivity; -import androidx.appcompat.app.AppCompatDelegate; -import androidx.fragment.app.FragmentManager; +import androidx.annotation.Nullable; import com.google.android.exoplayer2.database.ExoDatabaseProvider; import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor; @@ -39,11 +31,9 @@ import javax.crypto.Mac; import javax.crypto.spec.SecretKeySpec; import awais.instagrabber.R; -import awais.instagrabber.databinding.DialogImportExportBinding; +import awais.instagrabber.models.enums.FavoriteType; import awaisomereport.LogCollector; -import static awais.instagrabber.utils.Constants.FOLDER_PATH; - public final class Utils { private static final String TAG = "Utils"; private static final int VIDEO_CACHE_MAX_BYTES = 10 * 1024 * 1024; @@ -64,19 +54,6 @@ public final class Utils { return Math.round((dp * displayMetrics.densityDpi) / 160.0f); } - public static void setTooltipText(final View view, @StringRes final int tooltipTextRes) { - if (view != null && tooltipTextRes != 0 && tooltipTextRes != -1) { - final Context context = view.getContext(); - final String tooltipText = context.getResources().getString(tooltipTextRes); - - if (Build.VERSION.SDK_INT >= 26) view.setTooltipText(tooltipText); - else view.setOnLongClickListener(v -> { - Toast.makeText(context, tooltipText, Toast.LENGTH_SHORT).show(); - return true; - }); - } - } - public static void copyText(@NonNull final Context context, final CharSequence string) { if (clipboardManager == null) clipboardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE); @@ -89,100 +66,6 @@ public final class Utils { Toast.makeText(context, toastMessage, Toast.LENGTH_SHORT).show(); } - public static void showImportExportDialog(final Context context) { - final DialogImportExportBinding importExportBinding = DialogImportExportBinding.inflate(LayoutInflater.from(context)); - - final View passwordParent = (View) importExportBinding.cbPassword.getParent(); - final View exportLoginsParent = (View) importExportBinding.cbExportLogins.getParent(); - final View exportFavoritesParent = (View) importExportBinding.cbExportFavorites.getParent(); - final View exportSettingsParent = (View) importExportBinding.cbExportSettings.getParent(); - final View importLoginsParent = (View) importExportBinding.cbImportLogins.getParent(); - final View importFavoritesParent = (View) importExportBinding.cbImportFavorites.getParent(); - final View importSettingsParent = (View) importExportBinding.cbImportSettings.getParent(); - - importExportBinding.cbPassword.setOnCheckedChangeListener((buttonView, isChecked) -> - importExportBinding.etPassword.etPassword.setEnabled(isChecked)); - - final AlertDialog[] dialog = new AlertDialog[1]; - final View.OnClickListener onClickListener = v -> { - if (v == passwordParent) importExportBinding.cbPassword.performClick(); - - else if (v == exportLoginsParent) importExportBinding.cbExportLogins.performClick(); - else if (v == exportFavoritesParent) - importExportBinding.cbExportFavorites.performClick(); - - else if (v == importLoginsParent) importExportBinding.cbImportLogins.performClick(); - else if (v == importFavoritesParent) - importExportBinding.cbImportFavorites.performClick(); - - else if (v == exportSettingsParent) importExportBinding.cbExportSettings.performClick(); - else if (v == importSettingsParent) importExportBinding.cbImportSettings.performClick(); - - else if (context instanceof AppCompatActivity) { - final FragmentManager fragmentManager = ((AppCompatActivity) context).getSupportFragmentManager(); - final String folderPath = settingsHelper.getString(FOLDER_PATH); - - if (v == importExportBinding.btnSaveTo) { - final Editable text = importExportBinding.etPassword.etPassword.getText(); - final boolean passwordChecked = importExportBinding.cbPassword.isChecked(); - if (passwordChecked && TextUtils.isEmpty(text)) - Toast.makeText(context, R.string.dialog_export_err_password_empty, Toast.LENGTH_SHORT).show(); - else { - new DirectoryChooser().setInitialDirectory(folderPath).setInteractionListener(path -> { - final File file = new File(path, "InstaGrabber_Settings_" + System.currentTimeMillis() + ".zaai"); - final String password = passwordChecked ? text.toString() : null; - int flags = 0; - if (importExportBinding.cbExportFavorites.isChecked()) - flags |= ExportImportUtils.FLAG_FAVORITES; - if (importExportBinding.cbExportSettings.isChecked()) - flags |= ExportImportUtils.FLAG_SETTINGS; - if (importExportBinding.cbExportLogins.isChecked()) - flags |= ExportImportUtils.FLAG_COOKIES; - - ExportImportUtils.Export(password, flags, file, result -> { - Toast.makeText(context, result ? R.string.dialog_export_success : R.string.dialog_export_failed, Toast.LENGTH_SHORT) - .show(); - if (dialog[0] != null && dialog[0].isShowing()) dialog[0].dismiss(); - }); - - }).show(fragmentManager, null); - } - - } else if (v == importExportBinding.btnImport) { - new DirectoryChooser().setInitialDirectory(folderPath).setShowZaAiConfigFiles(true).setInteractionListener(path -> { - int flags = 0; - if (importExportBinding.cbImportFavorites.isChecked()) - flags |= ExportImportUtils.FLAG_FAVORITES; - if (importExportBinding.cbImportSettings.isChecked()) - flags |= ExportImportUtils.FLAG_SETTINGS; - if (importExportBinding.cbImportLogins.isChecked()) - flags |= ExportImportUtils.FLAG_COOKIES; - - ExportImportUtils.Import(context, flags, new File(path), result -> { - ((AppCompatActivity) context).recreate(); - Toast.makeText(context, result ? R.string.dialog_import_success : R.string.dialog_import_failed, Toast.LENGTH_SHORT) - .show(); - if (dialog[0] != null && dialog[0].isShowing()) dialog[0].dismiss(); - }); - - }).show(fragmentManager, null); - } - } - }; - - passwordParent.setOnClickListener(onClickListener); - exportLoginsParent.setOnClickListener(onClickListener); - exportSettingsParent.setOnClickListener(onClickListener); - exportFavoritesParent.setOnClickListener(onClickListener); - importLoginsParent.setOnClickListener(onClickListener); - importSettingsParent.setOnClickListener(onClickListener); - importFavoritesParent.setOnClickListener(onClickListener); - importExportBinding.btnSaveTo.setOnClickListener(onClickListener); - importExportBinding.btnImport.setOnClickListener(onClickListener); - - dialog[0] = new AlertDialog.Builder(context).setView(importExportBinding.getRoot()).show(); - } - public static Map sign(final Map form) { final String signed = sign(new JSONObject(form).toString()); if (signed == null) { @@ -251,4 +134,16 @@ public final class Utils { } return simpleCache; } + + @Nullable + public static Pair migrateOldFavQuery(final String queryText) { + if (queryText.startsWith("@")) { + return new Pair<>(FavoriteType.USER, queryText.substring(1)); + } else if (queryText.contains("/")) { + return new Pair<>(FavoriteType.LOCATION, queryText.substring(0, queryText.indexOf("/"))); + } else if (queryText.startsWith("#")) { + return new Pair<>(FavoriteType.HASHTAG, queryText.substring(1)); + } + return null; + } } diff --git a/app/src/main/java/awais/instagrabber/viewmodels/FavoritesViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/FavoritesViewModel.java new file mode 100644 index 00000000..e30d4116 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/viewmodels/FavoritesViewModel.java @@ -0,0 +1,19 @@ +package awais.instagrabber.viewmodels; + +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import java.util.List; + +import awais.instagrabber.utils.DataBox; + +public class FavoritesViewModel extends ViewModel { + private MutableLiveData> list; + + public MutableLiveData> getList() { + if (list == null) { + list = new MutableLiveData<>(); + } + return list; + } +} diff --git a/app/src/main/java/awais/instagrabber/viewmodels/FileListViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/FileListViewModel.java new file mode 100644 index 00000000..c8ebcd5c --- /dev/null +++ b/app/src/main/java/awais/instagrabber/viewmodels/FileListViewModel.java @@ -0,0 +1,18 @@ +package awais.instagrabber.viewmodels; + +import androidx.lifecycle.MutableLiveData; +import androidx.lifecycle.ViewModel; + +import java.io.File; +import java.util.List; + +public class FileListViewModel extends ViewModel { + private MutableLiveData> list; + + public MutableLiveData> getList() { + if (list == null) { + list = new MutableLiveData<>(); + } + return list; + } +} diff --git a/app/src/main/java/awais/instagrabber/webservices/StoriesService.java b/app/src/main/java/awais/instagrabber/webservices/StoriesService.java index 14e3fc7e..fb5bba2c 100644 --- a/app/src/main/java/awais/instagrabber/webservices/StoriesService.java +++ b/app/src/main/java/awais/instagrabber/webservices/StoriesService.java @@ -98,14 +98,12 @@ public class StoriesService extends BaseService { public void getUserStory(final String id, final String username, - final boolean storiesig, final boolean isLoc, final boolean isHashtag, final boolean highlight, final ServiceCallback> callback) { - final String url = buildUrl(id, storiesig, isLoc, isHashtag, highlight); - final String userAgent = storiesig ? Constants.A_USER_AGENT : Constants.I_USER_AGENT; - final Call userStoryCall = repository.getUserStory(userAgent, url); + final String url = buildUrl(id, isLoc, isHashtag, highlight); + final Call userStoryCall = repository.getUserStory(Constants.I_USER_AGENT, url); userStoryCall.enqueue(new Callback() { @Override public void onResponse(@NonNull final Call call, @NonNull final Response response) { @@ -119,7 +117,7 @@ public class StoriesService extends BaseService { } data = new JSONObject(body); - if (!storiesig && !highlight) + if (!highlight) data = data.optJSONObject((isLoc || isHashtag) ? "story" : "reel"); else if (highlight) data = data.getJSONObject("reels").optJSONObject(id); @@ -243,16 +241,10 @@ public class StoriesService extends BaseService { }); } - private String buildUrl(final String id, final boolean storiesig, final boolean isLoc, final boolean isHashtag, final boolean highlight) { + private String buildUrl(final String id, final boolean isLoc, final boolean isHashtag, final boolean highlight) { final String userId = id.replace(":", "%3A"); final StringBuilder builder = new StringBuilder(); - builder.append("https://"); - if (storiesig) { - builder.append("storiesig"); - } else { - builder.append("i.instagram"); - } - builder.append(".com/api/v1/"); + builder.append("https://i.instagram.com/api/v1/"); if (isLoc) { builder.append("locations/"); } @@ -266,11 +258,7 @@ public class StoriesService extends BaseService { } builder.append(userId); if (!highlight) { - if (storiesig) { - builder.append("/reel_media/"); - } else { - builder.append("/story/"); - } + builder.append("/story/"); } return builder.toString(); } diff --git a/app/src/main/java/awais/instagrabber/webservices/TagsService.java b/app/src/main/java/awais/instagrabber/webservices/TagsService.java new file mode 100644 index 00000000..2834a570 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/webservices/TagsService.java @@ -0,0 +1,101 @@ +package awais.instagrabber.webservices; + +import android.util.Log; + +import androidx.annotation.NonNull; + +import org.json.JSONException; +import org.json.JSONObject; + +import awais.instagrabber.repositories.TagsRepository; +import awais.instagrabber.utils.Constants; +import retrofit2.Call; +import retrofit2.Callback; +import retrofit2.Response; +import retrofit2.Retrofit; + +public class TagsService extends BaseService { + + private static final String TAG = "TagsService"; + + // web for www.instagram.com + private final TagsRepository webRepository; + + private static TagsService instance; + + private TagsService() { + final Retrofit webRetrofit = getRetrofitBuilder() + .baseUrl("https://www.instagram.com/") + .build(); + webRepository = webRetrofit.create(TagsRepository.class); + } + + public static TagsService getInstance() { + if (instance == null) { + instance = new TagsService(); + } + return instance; + } + + public void follow(@NonNull final String tag, + @NonNull final String csrfToken, + final ServiceCallback callback) { + final Call request = webRepository.follow(Constants.USER_AGENT, + csrfToken, + tag); + request.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + final String body = response.body(); + if (body == null) { + callback.onFailure(new RuntimeException("body is null")); + return; + } + try { + final JSONObject jsonObject = new JSONObject(body); + final String status = jsonObject.optString("status"); + callback.onSuccess(status.equals("ok")); + } catch (JSONException e) { + Log.e(TAG, "onResponse: ", e); + } + } + + @Override + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + // Log.e(TAG, "onFailure: ", t); + callback.onFailure(t); + } + }); + } + + public void unfollow(@NonNull final String tag, + @NonNull final String csrfToken, + final ServiceCallback callback) { + final Call request = webRepository.unfollow(Constants.USER_AGENT, + csrfToken, + tag); + request.enqueue(new Callback() { + @Override + public void onResponse(@NonNull final Call call, @NonNull final Response response) { + final String body = response.body(); + if (body == null) { + callback.onFailure(new RuntimeException("body is null")); + return; + } + try { + final JSONObject jsonObject = new JSONObject(body); + final String status = jsonObject.optString("status"); + callback.onSuccess(status.equals("ok")); + } catch (JSONException e) { + Log.e(TAG, "onResponse: ", e); + } + } + + @Override + public void onFailure(@NonNull final Call call, @NonNull final Throwable t) { + // Log.e(TAG, "onFailure: ", t); + callback.onFailure(t); + } + }); + } +} diff --git a/app/src/main/res/drawable-night/expired.png b/app/src/main/res/drawable-night/expired.png deleted file mode 100644 index 31da07c8..00000000 Binary files a/app/src/main/res/drawable-night/expired.png and /dev/null differ diff --git a/app/src/main/res/drawable/expired.png b/app/src/main/res/drawable/expired.png deleted file mode 100644 index 83f232d4..00000000 Binary files a/app/src/main/res/drawable/expired.png and /dev/null differ diff --git a/app/src/main/res/drawable/ic_account_multiple_remove_24.xml b/app/src/main/res/drawable/ic_account_multiple_remove_24.xml new file mode 100644 index 00000000..eb035e53 --- /dev/null +++ b/app/src/main/res/drawable/ic_account_multiple_remove_24.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_block_24.xml b/app/src/main/res/drawable/ic_block_24.xml new file mode 100644 index 00000000..9fefeec6 --- /dev/null +++ b/app/src/main/res/drawable/ic_block_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_clock_alert_outline_24.xml b/app/src/main/res/drawable/ic_clock_alert_outline_24.xml new file mode 100644 index 00000000..731319fe --- /dev/null +++ b/app/src/main/res/drawable/ic_clock_alert_outline_24.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_file_24.xml b/app/src/main/res/drawable/ic_file_24.xml new file mode 100644 index 00000000..f404fbf7 --- /dev/null +++ b/app/src/main/res/drawable/ic_file_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_logout.xml b/app/src/main/res/drawable/ic_folder_24.xml similarity index 68% rename from app/src/main/res/drawable/ic_logout.xml rename to app/src/main/res/drawable/ic_folder_24.xml index bab545a7..dc6b0802 100644 --- a/app/src/main/res/drawable/ic_logout.xml +++ b/app/src/main/res/drawable/ic_folder_24.xml @@ -6,5 +6,5 @@ android:tint="?attr/colorControlNormal"> + android:pathData="M10,4H4c-1.1,0 -1.99,0.9 -1.99,2L2,18c0,1.1 0.9,2 2,2h16c1.1,0 2,-0.9 2,-2V8c0,-1.1 -0.9,-2 -2,-2h-8l-2,-2z"/> diff --git a/app/src/main/res/drawable/ic_highlight_off_24.xml b/app/src/main/res/drawable/ic_highlight_off_24.xml new file mode 100644 index 00000000..6a21d0d7 --- /dev/null +++ b/app/src/main/res/drawable/ic_highlight_off_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_logout_24.xml b/app/src/main/res/drawable/ic_logout_24.xml new file mode 100644 index 00000000..376bc7ce --- /dev/null +++ b/app/src/main/res/drawable/ic_logout_24.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_outline_class_24.xml b/app/src/main/res/drawable/ic_outline_class_24.xml new file mode 100644 index 00000000..bace1783 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_class_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_map_24.xml b/app/src/main/res/drawable/ic_outline_map_24.xml new file mode 100644 index 00000000..d0769b3e --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_map_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_person_add_24.xml b/app/src/main/res/drawable/ic_outline_person_add_24.xml new file mode 100644 index 00000000..a2a0572a --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_person_add_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_person_add_disabled_24.xml b/app/src/main/res/drawable/ic_outline_person_add_disabled_24.xml new file mode 100644 index 00000000..e230319e --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_person_add_disabled_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_person_pin_24.xml b/app/src/main/res/drawable/ic_outline_person_pin_24.xml new file mode 100644 index 00000000..13963ebf --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_person_pin_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_star_24.xml b/app/src/main/res/drawable/ic_outline_star_24.xml new file mode 100644 index 00000000..b6d93cac --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_star_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_outline_star_plus_24.xml b/app/src/main/res/drawable/ic_outline_star_plus_24.xml new file mode 100644 index 00000000..2977b0e5 --- /dev/null +++ b/app/src/main/res/drawable/ic_outline_star_plus_24.xml @@ -0,0 +1,10 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_settings_backup_restore_24.xml b/app/src/main/res/drawable/ic_settings_backup_restore_24.xml new file mode 100644 index 00000000..1772ed50 --- /dev/null +++ b/app/src/main/res/drawable/ic_settings_backup_restore_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_star_check_24.xml b/app/src/main/res/drawable/ic_star_check_24.xml new file mode 100644 index 00000000..b413c895 --- /dev/null +++ b/app/src/main/res/drawable/ic_star_check_24.xml @@ -0,0 +1,8 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/rounder_corner_semi_black_bg.xml b/app/src/main/res/drawable/rounder_corner_semi_black_bg.xml index a0706c60..67e019e3 100644 --- a/app/src/main/res/drawable/rounder_corner_semi_black_bg.xml +++ b/app/src/main/res/drawable/rounder_corner_semi_black_bg.xml @@ -1,6 +1,6 @@ - + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 47477544..ad84f7a4 100755 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -28,7 +28,6 @@ android:layout_width="match_parent" android:layout_height="?attr/actionBarSize" app:layout_collapseMode="none" - app:popupTheme="@style/Widget.AppTheme.Toolbar.PrimarySurface" app:title="@string/app_name" tools:menu="@menu/main_menu" /> diff --git a/app/src/main/res/layout/dialog_create_backup.xml b/app/src/main/res/layout/dialog_create_backup.xml new file mode 100644 index 00000000..78df89d5 --- /dev/null +++ b/app/src/main/res/layout/dialog_create_backup.xml @@ -0,0 +1,73 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_import_export.xml b/app/src/main/res/layout/dialog_import_export.xml deleted file mode 100755 index 0d6910f1..00000000 --- a/app/src/main/res/layout/dialog_import_export.xml +++ /dev/null @@ -1,230 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_profilepic.xml b/app/src/main/res/layout/dialog_profilepic.xml index 0b5a4000..38db7201 100644 --- a/app/src/main/res/layout/dialog_profilepic.xml +++ b/app/src/main/res/layout/dialog_profilepic.xml @@ -4,7 +4,7 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="match_parent" - android:background="@color/semi_transparent_black"> + android:background="@color/black_a50"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_favorites.xml b/app/src/main/res/layout/fragment_favorites.xml new file mode 100644 index 00000000..05a808d6 --- /dev/null +++ b/app/src/main/res/layout/fragment_favorites.xml @@ -0,0 +1,7 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/fragment_hashtag.xml b/app/src/main/res/layout/fragment_hashtag.xml index 311ec733..262a2f22 100644 --- a/app/src/main/res/layout/fragment_hashtag.xml +++ b/app/src/main/res/layout/fragment_hashtag.xml @@ -17,47 +17,120 @@ android:layout_height="wrap_content" app:layout_scrollFlags="scroll"> - + android:padding="@dimen/profile_info_container_bottom_space"> + android:background="?selectableItemBackgroundBorderless" + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toStartOf="@id/mainTagPostCount" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent" + tools:background="@mipmap/ic_launcher" /> + app:layout_constraintBottom_toTopOf="@id/btnFollowTag" + app:layout_constraintStart_toEndOf="@id/mainHashtagImage" + app:layout_constraintTop_toTopOf="@id/mainHashtagImage" + tools:text="35 Posts" /> - - + app:chipBackgroundColor="@null" + app:chipIcon="@drawable/ic_outline_person_add_24" + app:chipIconTint="@color/deep_purple_800" + app:layout_constraintBottom_toBottomOf="@id/mainHashtagImage" + app:layout_constraintStart_toEndOf="@id/mainHashtagImage" + app:layout_constraintTop_toBottomOf="@id/mainTagPostCount" + app:rippleColor="@color/purple_200" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_location.xml b/app/src/main/res/layout/fragment_location.xml index 9ea5a610..8ae9ef80 100644 --- a/app/src/main/res/layout/fragment_location.xml +++ b/app/src/main/res/layout/fragment_location.xml @@ -17,101 +17,128 @@ android:layout_height="wrap_content" app:layout_scrollFlags="scroll|exitUntilCollapsed"> - + android:padding="8dp"> - + + + + + android:layout_marginStart="8dp" + android:layout_marginLeft="8dp" + android:text="@string/map" + app:chipBackgroundColor="@null" + app:chipIcon="@drawable/ic_outline_map_24" + app:chipIconTint="@color/green_500" + app:layout_constraintBottom_toTopOf="@id/locationFullName" + app:layout_constraintStart_toEndOf="@id/mainLocationImage" + app:layout_constraintTop_toBottomOf="@id/mainLocPostCount" + app:rippleColor="@color/grey_500" + tools:visibility="visible" /> - - - - - - + + app:layout_constraintBottom_toTopOf="@id/locationUrl" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/locationFullName" + tools:text="IN THE MIDDLE OF OUR STREET" + tools:visibility="visible" /> - + app:layout_constraintBottom_toBottomOf="parent" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/locationBiography" + tools:text="https://austinhuang.me/" + tools:visibility="visible" /> + diff --git a/app/src/main/res/layout/fragment_profile.xml b/app/src/main/res/layout/fragment_profile.xml index 2552fd7f..aedcbaaa 100644 --- a/app/src/main/res/layout/fragment_profile.xml +++ b/app/src/main/res/layout/fragment_profile.xml @@ -79,8 +79,10 @@ android:ellipsize="marquee" android:paddingStart="8dp" android:paddingLeft="8dp" + android:paddingTop="8dp" android:paddingEnd="4dp" android:paddingRight="4dp" + android:paddingBottom="8dp" android:singleLine="true" android:textAppearance="@style/TextAppearance.AppCompat.Body1" android:textStyle="bold" @@ -102,15 +104,34 @@ app:srcCompat="@drawable/verified" tools:visibility="visible" /> + + + + + tools:visibility="gone" /> - - - - - - - - - - + + - \ No newline at end of file + android:background="?attr/selectableItemBackground" + android:minHeight="56dp"> + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_fav_section_header.xml b/app/src/main/res/layout/item_fav_section_header.xml new file mode 100644 index 00000000..19f6fd40 --- /dev/null +++ b/app/src/main/res/layout/item_fav_section_header.xml @@ -0,0 +1,13 @@ + + \ No newline at end of file diff --git a/app/src/main/res/layout/item_feed_slider.xml b/app/src/main/res/layout/item_feed_slider.xml index a6b31978..2267d089 100755 --- a/app/src/main/res/layout/item_feed_slider.xml +++ b/app/src/main/res/layout/item_feed_slider.xml @@ -18,7 +18,7 @@ android:id="@+id/media_list" android:layout_width="match_parent" android:layout_height="wrap_content" - tools:background="@color/semi_transparent_black" /> + tools:background="@color/black_a50" /> @@ -36,7 +36,6 @@ android:layout_height="wrap_content" android:gravity="center_vertical" android:textAppearance="@style/TextAppearance.AppCompat.Medium" - android:textColor="@color/feed_text_primary_color" tools:text="username" /> diff --git a/app/src/main/res/layout/item_notification.xml b/app/src/main/res/layout/item_notification.xml index f2c344f1..9b2e4b11 100644 --- a/app/src/main/res/layout/item_notification.xml +++ b/app/src/main/res/layout/item_notification.xml @@ -20,37 +20,6 @@ app:roundAsCircle="true" tools:placeholderImage="@mipmap/ic_launcher" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + tools:text="sub-comment long long long long long long long long long long" /> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + \ No newline at end of file diff --git a/app/src/main/res/layout/item_suggestion.xml b/app/src/main/res/layout/item_suggestion.xml index 55425bb1..df114c34 100755 --- a/app/src/main/res/layout/item_suggestion.xml +++ b/app/src/main/res/layout/item_suggestion.xml @@ -7,7 +7,10 @@ android:background="?selectableItemBackground" android:clickable="true" android:focusable="true" - android:padding="8dp"> + android:paddingLeft="16dp" + android:paddingTop="8dp" + android:paddingRight="16dp" + android:paddingBottom="8dp"> - - - - - - - - - - - - - + app:layout_constraintBottom_toTopOf="@id/directoryList" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toTopOf="parent"> - - - - - - - - + app:navigationIcon="@drawable/ic_arrow_upward_24" + tools:title="/this/that/thy" /> + - \ No newline at end of file + app:layout_constraintBottom_toTopOf="@id/bottom_horizontal_divider" + app:layout_constraintEnd_toEndOf="parent" + app:layout_constraintStart_toStartOf="parent" + app:layout_constraintTop_toBottomOf="@id/appBarLayout" /> + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/layout_dm_raven_media.xml b/app/src/main/res/layout/layout_dm_raven_media.xml index 6eda3942..b5aa10a3 100644 --- a/app/src/main/res/layout/layout_dm_raven_media.xml +++ b/app/src/main/res/layout/layout_dm_raven_media.xml @@ -10,7 +10,7 @@ android:layout_width="match_parent" android:layout_height="?actionBarSize" android:padding="8dp" - android:src="@drawable/expired" /> + app:srcCompat="@drawable/ic_clock_alert_outline_24" /> + android:visibility="gone" + tools:visibility="visible">

- + + + + + + + + + + \ 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 242d25a7..3f2ee769 100644 --- a/app/src/main/res/navigation/more_nav_graph.xml +++ b/app/src/main/res/navigation/more_nav_graph.xml @@ -34,6 +34,24 @@ 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 1af3b2bd..082a186d 100644 --- a/app/src/main/res/navigation/profile_nav_graph.xml +++ b/app/src/main/res/navigation/profile_nav_graph.xml @@ -36,7 +36,7 @@ + app:destination="@id/profile_nav_graph"> + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values-de/arrays.xml b/app/src/main/res/values-de/arrays.xml index 088c4a46..ce2a1fbc 100644 --- a/app/src/main/res/values-de/arrays.xml +++ b/app/src/main/res/values-de/arrays.xml @@ -42,7 +42,6 @@ Deaktivieren - storiesig Aloinstagram Instadp diff --git a/app/src/main/res/values-es/arrays.xml b/app/src/main/res/values-es/arrays.xml index 774dcb7b..135783ea 100755 --- a/app/src/main/res/values-es/arrays.xml +++ b/app/src/main/res/values-es/arrays.xml @@ -42,7 +42,6 @@ Disable - storiesig Aloinstagram Instadp diff --git a/app/src/main/res/values-fa/arrays.xml b/app/src/main/res/values-fa/arrays.xml index 76e43ab5..7a46c80a 100644 --- a/app/src/main/res/values-fa/arrays.xml +++ b/app/src/main/res/values-fa/arrays.xml @@ -42,7 +42,6 @@ ΨΊΫŒΨ±ΩΨΉΨ§Ω„ - Ψ§Ψ³Ψͺوری Ω‡Ψ§ΫŒ ig Aloinstagram Instadp diff --git a/app/src/main/res/values-fr/arrays.xml b/app/src/main/res/values-fr/arrays.xml index 34fb31e8..d3873b53 100755 --- a/app/src/main/res/values-fr/arrays.xml +++ b/app/src/main/res/values-fr/arrays.xml @@ -42,7 +42,6 @@ Disable - storiesig Aloinstagram Instadp diff --git a/app/src/main/res/values-in/arrays.xml b/app/src/main/res/values-in/arrays.xml index 9a8969fa..54736873 100644 --- a/app/src/main/res/values-in/arrays.xml +++ b/app/src/main/res/values-in/arrays.xml @@ -42,7 +42,6 @@ Nonaktifkan - storiesig Aloinstagram Instadp diff --git a/app/src/main/res/values-it/arrays.xml b/app/src/main/res/values-it/arrays.xml index 0332b8d4..7c0d00d8 100755 --- a/app/src/main/res/values-it/arrays.xml +++ b/app/src/main/res/values-it/arrays.xml @@ -42,7 +42,6 @@ Disattiva - storiesig Aloinstagram Instadp diff --git a/app/src/main/res/values-mk/arrays.xml b/app/src/main/res/values-mk/arrays.xml index e25cc849..3d671204 100644 --- a/app/src/main/res/values-mk/arrays.xml +++ b/app/src/main/res/values-mk/arrays.xml @@ -42,7 +42,6 @@ Disable - storiesig Aloinstagram Instadp diff --git a/app/src/main/res/values-night/color.xml b/app/src/main/res/values-night/color.xml index 92e7892a..ce9cab02 100755 --- a/app/src/main/res/values-night/color.xml +++ b/app/src/main/res/values-night/color.xml @@ -1,6 +1,4 @@ - @color/text_color_dark - #353535 \ No newline at end of file diff --git a/app/src/main/res/values-pl/arrays.xml b/app/src/main/res/values-pl/arrays.xml index cb45f646..71a651d0 100644 --- a/app/src/main/res/values-pl/arrays.xml +++ b/app/src/main/res/values-pl/arrays.xml @@ -42,7 +42,6 @@ Disable - storiesig Aloinstagram Instadp diff --git a/app/src/main/res/values-pt/arrays.xml b/app/src/main/res/values-pt/arrays.xml index 86a68b2e..c469e24c 100644 --- a/app/src/main/res/values-pt/arrays.xml +++ b/app/src/main/res/values-pt/arrays.xml @@ -42,7 +42,6 @@ Disable - storiesig Aloinstagram Instadp diff --git a/app/src/main/res/values-ru/arrays.xml b/app/src/main/res/values-ru/arrays.xml index 995f1dbc..d9a71cac 100644 --- a/app/src/main/res/values-ru/arrays.xml +++ b/app/src/main/res/values-ru/arrays.xml @@ -42,7 +42,6 @@ Disable - storiesig Aloinstagram Instadp diff --git a/app/src/main/res/values-tr/arrays.xml b/app/src/main/res/values-tr/arrays.xml index a801b947..576645a2 100644 --- a/app/src/main/res/values-tr/arrays.xml +++ b/app/src/main/res/values-tr/arrays.xml @@ -42,7 +42,6 @@ Devredışı BΔ±rak - storiesig Aloinstagram Instadp diff --git a/app/src/main/res/values-zh/arrays.xml b/app/src/main/res/values-zh/arrays.xml index e2561ddb..e89bd427 100755 --- a/app/src/main/res/values-zh/arrays.xml +++ b/app/src/main/res/values-zh/arrays.xml @@ -42,7 +42,6 @@ 禁用 - storiesig Aloinstagram Instadp diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 3a65c322..5e8ca0b2 100755 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -42,7 +42,6 @@ Disable - storiesig Aloinstagram Instadp diff --git a/app/src/main/res/values/color.xml b/app/src/main/res/values/color.xml index 90ff3501..5d46cf57 100755 --- a/app/src/main/res/values/color.xml +++ b/app/src/main/res/values/color.xml @@ -28,15 +28,13 @@ #FFBB00 #FF000000 - @color/text_color_light - #efefef - #80000000 #FFFFFF #000000 #121212 + #80000000 #FAFAFA #F5F5F5 @@ -83,7 +81,67 @@ #bb86fc #4b01d0 - #cf6679 + #EDE7F6 + #D1C4E9 + #B39DDB + #9575CD + #7E57C2 + #673AB7 + #5E35B1 + #512DA8 + #4527A0 + #311B92 + #B388FF + #7C4DFF + #651FFF + #6200EA + + #FFEBEE + #FFCDD2 + #EF9A9A + #E57373 + #EF5350 + #F44336 + #E53935 + #D32F2F + #C62828 + #B71C1C + #FF8A80 + #FF5252 + #FF1744 + #D50000 + + #FBE9E7 + #FFCCBC + #FFAB91 + #FF8A65 + #FF7043 + #FF5722 + #F4511E + #E64A19 + #D84315 + #BF360C + #FF9E80 + #FF6E40 + #FF3D00 + #DD2C00 + + #FFFDE7 + #FFF9C4 + #FFF59D + #FFF176 + #FFEE58 + #FFEB3B + #FDD835 + #FBC02D + #F9A825 + #F57F17 + #FFFF8D + #FFFF00 + #FFEA00 + #FFD600 + + #5CE362 #a86735 diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c98f724a..fe5c3d78 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -18,6 +18,8 @@ Copied to clipboard! Report Password (Max 32 chars) + Set a password (max 32 chars) + Password OK Yes Cancel @@ -25,7 +27,7 @@ Confirm Up Don\'t Show Again - Selected folder: + Current directory Favorites Discover Comments @@ -58,6 +60,7 @@ Language What to do? %s\nPosts + %s Posts %s\nFollowers %s\nFollowing Video post @@ -109,6 +112,7 @@ Liked Saved Tagged + Message Like (%s) Unlike (%s) Bookmark @@ -125,16 +129,18 @@ Export Import Export Logins - Export Settings - Export Favorites - Import Settings + Accounts + Settings + Favorites + Import settings Import Logins - Import Favorites + Import accounts + Import favorites Successfully imported! Failed to import! Successfully exported! Failed to export! - Password is empty! Password cannot be empt, dumbass! + Password is empty! Refresh Get cookies Desktop Mode @@ -146,7 +152,7 @@ Swap Time and Date positions Favorites panel is for adding your favorite hashtags and/or usernames.\n\nAnd the Quick Access panel is for quickly switching between accounts.\n\nNote 1: Make sure to Login into each account [Settings > Login] to add account to the list!\n\nNote 2: Log out of the current account and then log into the other account. Cannot delete currently in use account - Are you sure you want to delete %s? + Are you sure you want to delete \'%s\'? Width: %d\nHeight: %d \nColor depth: Select profile picture endpoint\n(Does not affect hashtags) @@ -284,4 +290,17 @@ Barista Bibliogram Material Dark + Added to Favorites + Add to favorites + Accounts + Hashtags + Locations + Unknown + Removed from Favourites + Backup & Restore + Create + Restore + File: + Enter password + Select a backup file (.zaai/.backup) \ No newline at end of file diff --git a/app/src/main/res/values/styles.xml b/app/src/main/res/values/styles.xml index 459a3d33..54b6c291 100755 --- a/app/src/main/res/values/styles.xml +++ b/app/src/main/res/values/styles.xml @@ -57,7 +57,11 @@ + + + + + + + + + + + + diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml index d4358cfa..e1c4087a 100644 --- a/app/src/main/res/values/themes.xml +++ b/app/src/main/res/values/themes.xml @@ -19,6 +19,7 @@ false @style/ThemeOverlay.MaterialComponents.ActionBar + @style/ThemeOverlay.MaterialComponents.MaterialAlertDialog.Light