diff --git a/app/build.gradle b/app/build.gradle index f48a0439..8b9632e6 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -108,7 +108,7 @@ dependencies { def nav_version = '2.3.4' def exoplayer_version = '2.13.2' - implementation 'com.google.android.material:material:1.4.0-alpha01' + implementation 'com.google.android.material:material:1.4.0-alpha02' implementation "com.google.android.exoplayer:exoplayer-core:$exoplayer_version" implementation "com.google.android.exoplayer:exoplayer-dash:$exoplayer_version" diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.java b/app/src/main/java/awais/instagrabber/activities/MainActivity.java index 5ea65279..b45a3200 100644 --- a/app/src/main/java/awais/instagrabber/activities/MainActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.java @@ -8,7 +8,6 @@ import android.content.ComponentName; import android.content.Context; import android.content.Intent; import android.content.ServiceConnection; -import android.content.res.TypedArray; import android.database.MatrixCursor; import android.net.Uri; import android.os.Build; @@ -24,6 +23,7 @@ import android.view.WindowManager; import android.widget.AutoCompleteTextView; import android.widget.Toast; +import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; @@ -47,15 +47,16 @@ import androidx.navigation.ui.NavigationUI; import com.google.android.material.appbar.AppBarLayout; import com.google.android.material.appbar.CollapsingToolbarLayout; +import com.google.android.material.badge.BadgeDrawable; import com.google.android.material.behavior.HideBottomViewOnScrollBehavior; import com.google.android.material.bottomnavigation.BottomNavigationView; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Iterators; import java.util.ArrayList; -import java.util.Arrays; +import java.util.Collections; import java.util.Deque; -import java.util.HashMap; import java.util.List; -import java.util.Map; import java.util.stream.Collectors; import awais.instagrabber.BuildConfig; @@ -69,6 +70,7 @@ import awais.instagrabber.fragments.directmessages.DirectMessageInboxFragmentDir import awais.instagrabber.fragments.main.FeedFragment; import awais.instagrabber.fragments.settings.PreferenceKeys; import awais.instagrabber.models.IntentModel; +import awais.instagrabber.models.Tab; import awais.instagrabber.models.enums.SuggestionType; import awais.instagrabber.repositories.responses.search.SearchItem; import awais.instagrabber.repositories.responses.search.SearchResponse; @@ -83,6 +85,7 @@ import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.emoji.EmojiParser; import awais.instagrabber.viewmodels.AppStateViewModel; +import awais.instagrabber.viewmodels.DirectInboxViewModel; import awais.instagrabber.webservices.SearchService; import retrofit2.Call; import retrofit2.Callback; @@ -93,15 +96,8 @@ import static awais.instagrabber.utils.Utils.settingsHelper; public class MainActivity extends BaseLanguageActivity implements FragmentManager.OnBackStackChangedListener { private static final String TAG = "MainActivity"; - - private static final List SHOW_BOTTOM_VIEW_DESTINATIONS = Arrays.asList( - R.id.directMessagesInboxFragment, - R.id.feedFragment, - R.id.profileFragment, - R.id.discoverFragment, - R.id.morePreferencesFragment); - private static final Map NAV_TO_MENU_ID_MAP = new HashMap<>(); private static final String FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex"; + private static final String LAST_SELECT_NAV_MENU_ID = "lastSelectedNavMenuId"; private ActivityMainBinding binding; private LiveData currentNavControllerLiveData; @@ -113,10 +109,13 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage private boolean showSearch = true; private Handler suggestionsFetchHandler; private int firstFragmentGraphIndex; + private int lastSelectedNavMenuId; private boolean isActivityCheckerServiceBound = false; private boolean isBackStackEmpty = false; private boolean isLoggedIn; private HideBottomViewOnScrollBehavior behavior; + private List currentTabs; + private List showBottomViewDestinations = Collections.emptyList(); private final ServiceConnection serviceConnection = new ServiceConnection() { @Override @@ -132,14 +131,6 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage } }; - static { - NAV_TO_MENU_ID_MAP.put(R.navigation.direct_messages_nav_graph, R.id.direct_messages_nav_graph); - NAV_TO_MENU_ID_MAP.put(R.navigation.feed_nav_graph, R.id.feed_nav_graph); - NAV_TO_MENU_ID_MAP.put(R.navigation.profile_nav_graph, R.id.profile_nav_graph); - NAV_TO_MENU_ID_MAP.put(R.navigation.discover_nav_graph, R.id.discover_nav_graph); - NAV_TO_MENU_ID_MAP.put(R.navigation.more_nav_graph, R.id.more_nav_graph); - } - @Override protected void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -184,6 +175,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage initEmojiCompat(); searchService = SearchService.getInstance(); // initDmService(); + initDmUnreadCount(); } private void initDmService() { @@ -193,6 +185,16 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage DMSyncAlarmReceiver.setAlarm(this); } + private void initDmUnreadCount() { + if (!isLoggedIn) return; + final DirectInboxViewModel directInboxViewModel = new ViewModelProvider(this).get(DirectInboxViewModel.class); + directInboxViewModel.getUnseenCount().observe(this, unseenCountResource -> { + if (unseenCountResource == null) return; + final Integer unseenCount = unseenCountResource.data; + setNavBarDMUnreadCountBadge(unseenCount == null ? 0 : unseenCount); + }); + } + @Override public boolean onCreateOptionsMenu(final Menu menu) { getMenuInflater().inflate(R.menu.main_menu, menu); @@ -217,6 +219,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage @Override protected void onSaveInstanceState(@NonNull final Bundle outState) { outState.putString(FIRST_FRAGMENT_GRAPH_INDEX_KEY, String.valueOf(firstFragmentGraphIndex)); + outState.putString(LAST_SELECT_NAV_MENU_ID, String.valueOf(binding.bottomNavView.getSelectedItemId())); super.onSaveInstanceState(outState); } @@ -229,6 +232,12 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage firstFragmentGraphIndex = Integer.parseInt(key); } catch (NumberFormatException ignored) { } } + final String lastSelected = (String) savedInstanceState.get(LAST_SELECT_NAV_MENU_ID); + if (lastSelected != null) { + try { + lastSelectedNavMenuId = Integer.parseInt(lastSelected); + } catch (NumberFormatException ignored) { } + } setupBottomNavigationBar(false); } @@ -263,9 +272,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage final NavController navController = currentNavControllerLiveData.getValue(); if (navController != null) { @SuppressLint("RestrictedApi") final Deque backStack = navController.getBackStack(); - if (backStack != null) { - currentNavControllerBackStack = backStack.size(); - } + currentNavControllerBackStack = backStack.size(); } } if (isTaskRoot() && isBackStackEmpty && currentNavControllerBackStack == 2) { @@ -318,7 +325,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage final Bundle bundle = new Bundle(); switch (type) { case TYPE_LOCATION: - bundle.putLong("locationId", Long.valueOf(query)); + bundle.putLong("locationId", Long.parseLong(query)); navController.navigate(R.id.action_global_locationFragment, bundle); break; case TYPE_HASHTAG: @@ -368,14 +375,16 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage cursor = null; return; } - final List result = new ArrayList(); + final List result = new ArrayList<>(); if (isLoggedIn) { - if (body.getList() != null) result.addAll(searchHash ? body.getList() - .stream() - .filter(i -> i.getUser() == null) - .collect(Collectors.toList()) : body.getList()); - } - else { + if (body.getList() != null) { + result.addAll(searchHash ? body.getList() + .stream() + .filter(i -> i.getUser() == null) + .collect(Collectors.toList()) + : body.getList()); + } + } else { if (body.getUsers() != null && !searchHash) result.addAll(body.getUsers()); if (body.getHashtags() != null) result.addAll(body.getHashtags()); if (body.getPlaces() != null) result.addAll(body.getPlaces()); @@ -420,9 +429,10 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage @Override public void onFailure(@NonNull final Call call, - Throwable t) { - if (!call.isCanceled() && t != null) + @NonNull Throwable t) { + if (!call.isCanceled()) { Log.e(TAG, "Exception on search:", t); + } } }; @@ -444,7 +454,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage } prevSuggestionAsync = searchService.search(isLoggedIn, searchUser || searchHash ? currentSearchQuery.substring(1) - : currentSearchQuery, + : currentSearchQuery, searchUser ? "user" : (searchHash ? "hashtag" : "blended")); suggestionAdapter.changeCursor(null); prevSuggestionAsync.enqueue(cb); @@ -474,36 +484,18 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage return true; } - private void setupBottomNavigationBar(final boolean setDefaultFromSettings) { - int main_nav_ids = R.array.main_nav_ids; - if (!isLoggedIn) { - main_nav_ids = R.array.logged_out_main_nav_ids; - final int selectedItemId = binding.bottomNavView.getSelectedItemId(); - binding.bottomNavView.getMenu().clear(); - binding.bottomNavView.inflateMenu(R.menu.logged_out_bottom_navigation_menu); - if (selectedItemId == R.id.profile_nav_graph - || selectedItemId == R.id.more_nav_graph) { - binding.bottomNavView.setSelectedItemId(selectedItemId); - } else { - setBottomNavSelectedItem(R.navigation.profile_nav_graph); - } - } - final List mainNavList = getMainNavList(main_nav_ids); - if (setDefaultFromSettings) { - final String defaultTabResNameString = settingsHelper.getString(Constants.DEFAULT_TAB); - try { - int navId = 0; - if (!TextUtils.isEmpty(defaultTabResNameString)) { - navId = getResources().getIdentifier(defaultTabResNameString, "navigation", getPackageName()); - } - final int defaultNavId = navId <= 0 ? R.navigation.profile_nav_graph - : navId; - final int index = mainNavList.indexOf(defaultNavId); - if (index >= 0) firstFragmentGraphIndex = index; - setBottomNavSelectedItem(defaultNavId); - } catch (NumberFormatException e) { - Log.e(TAG, "Error parsing id", e); - } + private void setupBottomNavigationBar(final boolean setDefaultTabFromSettings) { + currentTabs = !isLoggedIn ? setupAnonBottomNav() : setupMainBottomNav(); + final List mainNavList = currentTabs.stream() + .map(Tab::getNavigationResId) + .collect(Collectors.toList()); + showBottomViewDestinations = currentTabs.stream() + .map(Tab::getStartDestinationFragmentId) + .collect(Collectors.toList()); + if (setDefaultTabFromSettings) { + setSelectedTab(currentTabs); + } else { + binding.bottomNavView.setSelectedItemId(lastSelectedNavMenuId); } final LiveData navControllerLiveData = setupWithNavController( binding.bottomNavView, @@ -526,27 +518,86 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage }); } - private void setBottomNavSelectedItem(final int navId) { - final Integer menuId = NAV_TO_MENU_ID_MAP.get(navId); - if (menuId != null) { - binding.bottomNavView.setSelectedItemId(menuId); + private void setSelectedTab(final List tabs) { + final String defaultTabResNameString = settingsHelper.getString(Constants.DEFAULT_TAB); + try { + int navId = 0; + if (!TextUtils.isEmpty(defaultTabResNameString)) { + navId = getResources().getIdentifier(defaultTabResNameString, "navigation", getPackageName()); + } + final int navGraph = isLoggedIn ? R.navigation.feed_nav_graph + : R.navigation.profile_nav_graph; + final int defaultNavId = navId <= 0 ? navGraph : navId; + int index = Iterators.indexOf(tabs.iterator(), tab -> { + if (tab == null) return false; + return tab.getNavigationResId() == defaultNavId; + }); + if (index < 0 || index >= tabs.size()) index = 0; + firstFragmentGraphIndex = index; + setBottomNavSelectedTab(tabs.get(index)); + } catch (Exception e) { + Log.e(TAG, "Error parsing id", e); } } - @NonNull - private List getMainNavList(final int main_nav_ids) { - final TypedArray navIds = getResources().obtainTypedArray(main_nav_ids); - final List mainNavList = new ArrayList<>(navIds.length()); - final int length = navIds.length(); - for (int i = 0; i < length; i++) { - final int resourceId = navIds.getResourceId(i, -1); - if (resourceId < 0) continue; - mainNavList.add(resourceId); + private List setupAnonBottomNav() { + final int selectedItemId = binding.bottomNavView.getSelectedItemId(); + final Tab profileTab = new Tab(R.drawable.ic_person_24, + getString(R.string.profile), + false, + "profile_nav_graph", + R.navigation.profile_nav_graph, + R.id.profile_nav_graph, + R.id.profileFragment); + final Tab moreTab = new Tab(R.drawable.ic_more_horiz_24, + getString(R.string.more), + false, + "more_nav_graph", + R.navigation.more_nav_graph, + R.id.more_nav_graph, + R.id.morePreferencesFragment); + final Menu menu = binding.bottomNavView.getMenu(); + menu.clear(); + menu.add(0, profileTab.getNavigationRootId(), 0, profileTab.getTitle()).setIcon(profileTab.getIconResId()); + menu.add(0, moreTab.getNavigationRootId(), 0, moreTab.getTitle()).setIcon(moreTab.getIconResId()); + if (selectedItemId != R.id.profile_nav_graph && selectedItemId != R.id.more_nav_graph) { + setBottomNavSelectedTab(profileTab); } - navIds.recycle(); - return mainNavList; + return ImmutableList.of(profileTab, moreTab); } + private List setupMainBottomNav() { + final Menu menu = binding.bottomNavView.getMenu(); + menu.clear(); + final List navTabList = Utils.getNavTabList(this).first; + for (final Tab tab : navTabList) { + menu.add(0, tab.getNavigationRootId(), 0, tab.getTitle()).setIcon(tab.getIconResId()); + } + return navTabList; + } + + private void setBottomNavSelectedTab(@NonNull final Tab tab) { + binding.bottomNavView.setSelectedItemId(tab.getNavigationRootId()); + } + + private void setBottomNavSelectedTab(@SuppressWarnings("SameParameterValue") @IdRes final int navGraphRootId) { + binding.bottomNavView.setSelectedItemId(navGraphRootId); + } + + // @NonNull + // private List getMainNavList(final int main_nav_ids) { + // final TypedArray navIds = getResources().obtainTypedArray(main_nav_ids); + // final List mainNavList = new ArrayList<>(navIds.length()); + // final int length = navIds.length(); + // for (int i = 0; i < length; i++) { + // final int resourceId = navIds.getResourceId(i, -1); + // if (resourceId < 0) continue; + // mainNavList.add(resourceId); + // } + // navIds.recycle(); + // return mainNavList; + // } + private void setupNavigation(final Toolbar toolbar, final NavController navController) { if (navController == null) return; NavigationUI.setupWithNavController(toolbar, navController); @@ -564,7 +615,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage final int destinationId = destination.getId(); @SuppressLint("RestrictedApi") final Deque backStack = navController.getBackStack(); setupMenu(backStack.size(), destinationId); - final boolean contains = SHOW_BOTTOM_VIEW_DESTINATIONS.contains(destinationId); + final boolean contains = showBottomViewDestinations.contains(destinationId); binding.bottomNavView.setVisibility(contains ? View.VISIBLE : View.GONE); if (contains && behavior != null) { behavior.slideUp(binding.bottomNavView); @@ -674,7 +725,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage }); final int selectedItemId = binding.bottomNavView.getSelectedItemId(); if (selectedItemId != R.navigation.direct_messages_nav_graph) { - setBottomNavSelectedItem(R.navigation.direct_messages_nav_graph); + setBottomNavSelectedTab(R.id.direct_messages_nav_graph); } } @@ -746,7 +797,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage final NavController navController = currentNavControllerLiveData.getValue(); if (navController == null) return; final Bundle bundle = new Bundle(); - bundle.putLong("locationId", Long.valueOf(locationId)); + bundle.putLong("locationId", Long.parseLong(locationId)); navController.navigate(R.id.action_global_locationFragment, bundle); } @@ -853,4 +904,27 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage public Toolbar getToolbar() { return binding.toolbar; } + + public List getCurrentTabs() { + return currentTabs; + } + +// public boolean isNavRootInCurrentTabs(@IdRes final int navRootId) { +// return showBottomViewDestinations.stream().anyMatch(id -> id == navRootId); +// } + + private void setNavBarDMUnreadCountBadge(final int unseenCount) { + final BadgeDrawable badge = binding.bottomNavView.getOrCreateBadge(R.id.direct_messages_nav_graph); + if (badge == null) return; + if (unseenCount == 0) { + badge.setVisible(false); + badge.clearNumber(); + return; + } + if (badge.getVerticalOffset() != 10) { + badge.setVerticalOffset(10); + } + badge.setNumber(unseenCount); + badge.setVisible(true); + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java index 9fa309f2..2b1f410e 100644 --- a/app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/DirectItemsAdapter.java @@ -17,6 +17,7 @@ import java.util.ArrayList; import java.util.Date; import java.util.List; import java.util.Objects; +import java.util.function.Function; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemActionLogViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemAnimatedMediaViewHolder; @@ -34,6 +35,7 @@ import awais.instagrabber.adapters.viewholder.directmessages.DirectItemTextViewH import awais.instagrabber.adapters.viewholder.directmessages.DirectItemVideoCallEventViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemViewHolder; import awais.instagrabber.adapters.viewholder.directmessages.DirectItemVoiceMediaViewHolder; +import awais.instagrabber.adapters.viewholder.directmessages.DirectItemXmaViewHolder; import awais.instagrabber.customviews.emoji.Emoji; import awais.instagrabber.databinding.LayoutDmActionLogBinding; import awais.instagrabber.databinding.LayoutDmAnimatedMediaBinding; @@ -207,6 +209,11 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter callback); } public interface DirectItemInternalLongClickListener { diff --git a/app/src/main/java/awais/instagrabber/adapters/SuggestionsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/SuggestionsAdapter.java index 6c4fa4c7..6c51f1f7 100755 --- a/app/src/main/java/awais/instagrabber/adapters/SuggestionsAdapter.java +++ b/app/src/main/java/awais/instagrabber/adapters/SuggestionsAdapter.java @@ -10,7 +10,6 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.cursoradapter.widget.CursorAdapter; -import awais.instagrabber.R; import awais.instagrabber.databinding.ItemSuggestionBinding; import awais.instagrabber.models.enums.SuggestionType; diff --git a/app/src/main/java/awais/instagrabber/adapters/TabsAdapter.java b/app/src/main/java/awais/instagrabber/adapters/TabsAdapter.java new file mode 100644 index 00000000..474e4190 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/adapters/TabsAdapter.java @@ -0,0 +1,156 @@ +package awais.instagrabber.adapters; + +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.StringRes; +import androidx.recyclerview.widget.DiffUtil; +import androidx.recyclerview.widget.ListAdapter; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.common.collect.ImmutableList; + +import java.util.ArrayList; +import java.util.List; +import java.util.Objects; +import java.util.stream.Collectors; + +import awais.instagrabber.R; +import awais.instagrabber.adapters.viewholder.TabViewHolder; +import awais.instagrabber.databinding.ItemFavSectionHeaderBinding; +import awais.instagrabber.databinding.ItemTabOrderPrefBinding; +import awais.instagrabber.models.Tab; +import awais.instagrabber.utils.Utils; + +public class TabsAdapter extends ListAdapter { + private static final DiffUtil.ItemCallback DIFF_CALLBACK = new DiffUtil.ItemCallback() { + @Override + public boolean areItemsTheSame(@NonNull final TabOrHeader oldItem, @NonNull final TabOrHeader newItem) { + if (oldItem.isHeader() && newItem.isHeader()) { + return oldItem.header == newItem.header; + } + if (!oldItem.isHeader() && !newItem.isHeader()) { + final Tab oldTab = oldItem.tab; + final Tab newTab = newItem.tab; + return oldTab.getIconResId() == newTab.getIconResId() + && Objects.equals(oldTab.getTitle(), newTab.getTitle()); + } + return false; + } + + @Override + public boolean areContentsTheSame(@NonNull final TabOrHeader oldItem, @NonNull final TabOrHeader newItem) { + if (oldItem.isHeader() && newItem.isHeader()) { + return oldItem.header == newItem.header; + } + if (!oldItem.isHeader() && !newItem.isHeader()) { + final Tab oldTab = oldItem.tab; + final Tab newTab = newItem.tab; + return oldTab.getIconResId() == newTab.getIconResId() + && Objects.equals(oldTab.getTitle(), newTab.getTitle()); + } + return false; + } + }; + + private final TabAdapterCallback tabAdapterCallback; + + private List current = new ArrayList<>(); + private List others = new ArrayList<>(); + + public TabsAdapter(@NonNull final TabAdapterCallback tabAdapterCallback) { + super(DIFF_CALLBACK); + this.tabAdapterCallback = tabAdapterCallback; + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull final ViewGroup parent, final int viewType) { + final LayoutInflater layoutInflater = LayoutInflater.from(parent.getContext()); + if (viewType == 1) { + final ItemTabOrderPrefBinding binding = ItemTabOrderPrefBinding.inflate(layoutInflater, parent, false); + return new TabViewHolder(binding, tabAdapterCallback); + } + final ItemFavSectionHeaderBinding headerBinding = ItemFavSectionHeaderBinding.inflate(layoutInflater, parent, false); + return new DirectUsersAdapter.HeaderViewHolder(headerBinding); + } + + @Override + public void onBindViewHolder(@NonNull final RecyclerView.ViewHolder holder, final int position) { + if (holder instanceof DirectUsersAdapter.HeaderViewHolder) { + ((DirectUsersAdapter.HeaderViewHolder) holder).bind(R.string.other_tabs); + return; + } + if (holder instanceof TabViewHolder) { + final Tab tab = getItem(position).tab; + ((TabViewHolder) holder).bind(tab, others.contains(tab), current.size() == 5); + } + } + + @Override + public int getItemViewType(final int position) { + return getItem(position).isHeader() ? 0 : 1; + } + + public void submitList(final List current, final List others, final Runnable commitCallback) { + final ImmutableList.Builder builder = ImmutableList.builder(); + if (current != null) { + builder.addAll(current.stream() + .map(TabOrHeader::new) + .collect(Collectors.toList())); + } + builder.add(new TabOrHeader(R.string.other_tabs)); + if (others != null) { + builder.addAll(others.stream() + .map(TabOrHeader::new) + .collect(Collectors.toList())); + } + // Mutable non-null copies + this.current = current != null ? new ArrayList<>(current) : new ArrayList<>(); + this.others = others != null ? new ArrayList<>(others) : new ArrayList<>(); + submitList(builder.build(), commitCallback); + } + + public void submitList(final List current, final List others) { + submitList(current, others, null); + } + + public void moveItem(final int from, final int to) { + final List currentCopy = new ArrayList<>(current); + Utils.moveItem(from, to, currentCopy); + submitList(currentCopy, others); + tabAdapterCallback.onOrderChange(currentCopy); + } + + public int getCurrentCount() { + return current.size(); + } + + public static class TabOrHeader { + Tab tab; + int header; + + public TabOrHeader(final Tab tab) { + this.tab = tab; + } + + public TabOrHeader(@StringRes final int header) { + this.header = header; + } + + boolean isHeader() { + return header != 0; + } + } + + public interface TabAdapterCallback { + void onStartDrag(TabViewHolder viewHolder); + + void onOrderChange(List newOrderTabs); + + void onAdd(Tab tab); + + void onRemove(Tab tab); + } +} diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/TabViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/TabViewHolder.java new file mode 100644 index 00000000..d4be8726 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/TabViewHolder.java @@ -0,0 +1,88 @@ +package awais.instagrabber.adapters.viewholder; + +import android.annotation.SuppressLint; +import android.content.res.ColorStateList; +import android.graphics.drawable.Drawable; +import android.view.MotionEvent; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.core.content.ContextCompat; +import androidx.core.widget.ImageViewCompat; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.color.MaterialColors; + +import awais.instagrabber.R; +import awais.instagrabber.adapters.TabsAdapter; +import awais.instagrabber.databinding.ItemTabOrderPrefBinding; +import awais.instagrabber.models.Tab; + +public class TabViewHolder extends RecyclerView.ViewHolder { + private final ItemTabOrderPrefBinding binding; + private final TabsAdapter.TabAdapterCallback tabAdapterCallback; + private final int highlightColor; + private final Drawable originalBgColor; + + private boolean draggable = true; + + @SuppressLint("ClickableViewAccessibility") + public TabViewHolder(@NonNull final ItemTabOrderPrefBinding binding, + @NonNull final TabsAdapter.TabAdapterCallback tabAdapterCallback) { + super(binding.getRoot()); + this.binding = binding; + this.tabAdapterCallback = tabAdapterCallback; + highlightColor = MaterialColors.getColor(itemView.getContext(), R.attr.colorControlHighlight, 0); + originalBgColor = itemView.getBackground(); + binding.handle.setOnTouchListener((v, event) -> { + if (event.getActionMasked() == MotionEvent.ACTION_DOWN) { + tabAdapterCallback.onStartDrag(this); + } + return true; + }); + } + + public void bind(@NonNull final Tab tab, + final boolean isInOthers, + final boolean isCurrentFull) { + draggable = !isInOthers; + binding.icon.setImageResource(tab.getIconResId()); + binding.title.setText(tab.getTitle()); + binding.handle.setVisibility(isInOthers ? View.GONE : View.VISIBLE); + binding.addRemove.setImageResource(isInOthers ? R.drawable.ic_round_add_circle_24 + : R.drawable.ic_round_remove_circle_24); + final ColorStateList tintList = ColorStateList.valueOf(ContextCompat.getColor( + itemView.getContext(), + isInOthers ? R.color.green_500 + : R.color.red_500)); + ImageViewCompat.setImageTintList(binding.addRemove, tintList); + binding.addRemove.setOnClickListener(v -> { + if (isInOthers) { + tabAdapterCallback.onAdd(tab); + return; + } + tabAdapterCallback.onRemove(tab); + }); + final boolean enabled = tab.isRemovable() + && !(isInOthers && isCurrentFull); // All slots are full in current + binding.addRemove.setEnabled(enabled); + binding.addRemove.setAlpha(enabled ? 1 : 0.5F); + } + + public boolean isDraggable() { + return draggable; + } + + public void setDragging(final boolean isDragging) { + if (isDragging) { + if (highlightColor != 0) { + itemView.setBackgroundColor(highlightColor); + } else { + itemView.setAlpha(0.5F); + } + return; + } + itemView.setAlpha(1); + itemView.setBackground(originalBgColor); + } +} diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemAnimatedMediaViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemAnimatedMediaViewHolder.java index c08bb1d9..7bc5d173 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemAnimatedMediaViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemAnimatedMediaViewHolder.java @@ -8,8 +8,13 @@ import androidx.core.util.Pair; import androidx.recyclerview.widget.ItemTouchHelper; import com.facebook.drawee.backends.pipeline.Fresco; +import com.google.common.collect.ImmutableList; +import java.util.List; + +import awais.instagrabber.R; import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback; +import awais.instagrabber.customviews.DirectItemContextMenu; import awais.instagrabber.databinding.LayoutDmAnimatedMediaBinding; import awais.instagrabber.databinding.LayoutDmBaseBinding; import awais.instagrabber.repositories.responses.AnimatedMediaFixedHeight; @@ -19,6 +24,7 @@ import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectItemAnimatedMedia; import awais.instagrabber.repositories.responses.directmessages.DirectThread; import awais.instagrabber.utils.NumberUtils; +import awais.instagrabber.utils.Utils; public class DirectItemAnimatedMediaViewHolder extends DirectItemViewHolder { @@ -65,4 +71,14 @@ public class DirectItemAnimatedMediaViewHolder extends DirectItemViewHolder { public int getSwipeDirection() { return ItemTouchHelper.ACTION_STATE_IDLE; } + + @Override + protected List getLongClickOptions() { + return ImmutableList.of( + new DirectItemContextMenu.MenuItem(R.id.detail, R.string.dms_inbox_giphy, item -> { + Utils.openURL(itemView.getContext(), "https://giphy.com/gifs/" + item.getAnimatedMedia().getId()); + return null; + }) + ); + } } diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemDefaultViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemDefaultViewHolder.java index 2f874898..c9c86664 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemDefaultViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemDefaultViewHolder.java @@ -24,6 +24,7 @@ public class DirectItemDefaultViewHolder extends DirectItemViewHolder { final DirectItemCallback callback) { super(baseBinding, currentUser, thread, callback); this.binding = binding; + setItemView(binding.getRoot()); } @Override @@ -32,6 +33,11 @@ public class DirectItemDefaultViewHolder extends DirectItemViewHolder { binding.tvMessage.setText(context.getText(R.string.dms_inbox_raven_message_unknown)); } + @Override + protected boolean showBackground() { + return true; + } + @Override protected boolean allowLongClick() { return false; diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemLinkViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemLinkViewHolder.java index 2129cc2b..98c9fb14 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemLinkViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemLinkViewHolder.java @@ -5,7 +5,13 @@ import android.view.ViewGroup; import androidx.annotation.NonNull; +import com.google.common.collect.ImmutableList; + +import java.util.List; + +import awais.instagrabber.R; import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback; +import awais.instagrabber.customviews.DirectItemContextMenu; import awais.instagrabber.databinding.LayoutDmBaseBinding; import awais.instagrabber.databinding.LayoutDmLinkBinding; import awais.instagrabber.repositories.responses.User; @@ -14,6 +20,7 @@ import awais.instagrabber.repositories.responses.directmessages.DirectItemLink; import awais.instagrabber.repositories.responses.directmessages.DirectItemLinkContext; import awais.instagrabber.repositories.responses.directmessages.DirectThread; import awais.instagrabber.utils.TextUtils; +import awais.instagrabber.utils.Utils; public class DirectItemLinkViewHolder extends DirectItemViewHolder { @@ -80,4 +87,16 @@ public class DirectItemLinkViewHolder extends DirectItemViewHolder { protected boolean showBackground() { return true; } + + @Override + protected List getLongClickOptions() { + return ImmutableList.of( + new DirectItemContextMenu.MenuItem(R.id.copy, R.string.copy, item -> { + final DirectItemLink link = item.getLink(); + if (link == null || TextUtils.isEmpty(link.getText())) return null; + Utils.copyText(itemView.getContext(), link.getText()); + return null; + }) + ); + } } diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaShareViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaShareViewHolder.java index 8874e818..dddd9315 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaShareViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemMediaShareViewHolder.java @@ -12,11 +12,14 @@ import androidx.recyclerview.widget.ItemTouchHelper; import com.facebook.drawee.drawable.ScalingUtils; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; import com.facebook.drawee.generic.RoundingParams; +import com.google.common.collect.ImmutableList; +import java.util.List; import java.util.Objects; import awais.instagrabber.R; import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback; +import awais.instagrabber.customviews.DirectItemContextMenu; import awais.instagrabber.databinding.LayoutDmBaseBinding; import awais.instagrabber.databinding.LayoutDmMediaShareBinding; import awais.instagrabber.models.enums.DirectItemType; @@ -30,6 +33,7 @@ import awais.instagrabber.repositories.responses.directmessages.DirectItemFelixS import awais.instagrabber.repositories.responses.directmessages.DirectThread; import awais.instagrabber.utils.NumberUtils; import awais.instagrabber.utils.ResponseBodyUtils; +import awais.instagrabber.utils.Utils; public class DirectItemMediaShareViewHolder extends DirectItemViewHolder { private static final String TAG = DirectItemMediaShareViewHolder.class.getSimpleName(); @@ -38,6 +42,7 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder { private final RoundingParams incomingRoundingParams; private final RoundingParams outgoingRoundingParams; private DirectItemType itemType; + private Caption caption; public DirectItemMediaShareViewHolder(@NonNull final LayoutDmBaseBinding baseBinding, @NonNull final LayoutDmMediaShareBinding binding, @@ -113,7 +118,7 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder { } private void setupCaption(@NonNull final Media media) { - final Caption caption = media.getCaption(); + caption = media.getCaption(); if (caption != null) { binding.caption.setVisibility(View.VISIBLE); binding.caption.setText(caption.getText()); @@ -177,4 +182,16 @@ public class DirectItemMediaShareViewHolder extends DirectItemViewHolder { } return super.getSwipeDirection(); } + + @Override + protected List getLongClickOptions() { + final ImmutableList.Builder builder = ImmutableList.builder(); + if (caption != null && !TextUtils.isEmpty(caption.getText())) { + builder.add(new DirectItemContextMenu.MenuItem(R.id.copy, R.string.copy_caption, item -> { + Utils.copyText(itemView.getContext(), caption.getText()); + return null; + })); + } + return builder.build(); + } } diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java index 949b2a9a..6198faf1 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemRavenMediaViewHolder.java @@ -16,7 +16,6 @@ import awais.instagrabber.databinding.LayoutDmBaseBinding; import awais.instagrabber.databinding.LayoutDmRavenMediaBinding; import awais.instagrabber.models.enums.MediaItemType; import awais.instagrabber.models.enums.RavenMediaViewMode; -import awais.instagrabber.repositories.responses.ImageVersions2; import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.directmessages.DirectItem; diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemReelShareViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemReelShareViewHolder.java index bb139332..aa1666ab 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemReelShareViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemReelShareViewHolder.java @@ -8,13 +8,16 @@ import androidx.constraintlayout.widget.ConstraintLayout; import com.facebook.drawee.generic.GenericDraweeHierarchyBuilder; import com.facebook.drawee.generic.RoundingParams; +import com.google.common.collect.ImmutableList; + +import java.util.List; import awais.instagrabber.R; import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback; +import awais.instagrabber.customviews.DirectItemContextMenu; import awais.instagrabber.databinding.LayoutDmBaseBinding; import awais.instagrabber.databinding.LayoutDmReelShareBinding; import awais.instagrabber.models.enums.MediaItemType; -import awais.instagrabber.repositories.responses.ImageVersions2; import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.directmessages.DirectItem; @@ -22,10 +25,12 @@ import awais.instagrabber.repositories.responses.directmessages.DirectItemReelSh import awais.instagrabber.repositories.responses.directmessages.DirectThread; import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.TextUtils; +import awais.instagrabber.utils.Utils; public class DirectItemReelShareViewHolder extends DirectItemViewHolder { private final LayoutDmReelShareBinding binding; + private String type; public DirectItemReelShareViewHolder(@NonNull final LayoutDmBaseBinding baseBinding, @NonNull final LayoutDmReelShareBinding binding, @@ -40,7 +45,7 @@ public class DirectItemReelShareViewHolder extends DirectItemViewHolder { @Override public void bindItem(final DirectItem item, final MessageDirection messageDirection) { final DirectItemReelShare reelShare = item.getReelShare(); - final String type = reelShare.getType(); + type = reelShare.getType(); if (type == null) return; final boolean isSelf = isSelf(item); final Media media = reelShare.getMedia(); @@ -170,4 +175,20 @@ public class DirectItemReelShareViewHolder extends DirectItemViewHolder { protected boolean canForward() { return false; } + + @Override + protected List getLongClickOptions() { + final ImmutableList.Builder builder = ImmutableList.builder(); + if (type != null && type.equals("reply")) { + builder.add(new DirectItemContextMenu.MenuItem(R.id.copy, R.string.copy_reply, item -> { + final DirectItemReelShare reelShare = item.getReelShare(); + if (reelShare == null) return null; + final String text = reelShare.getText(); + if (TextUtils.isEmpty(text)) return null; + Utils.copyText(itemView.getContext(), text); + return null; + })); + } + return builder.build(); + } } diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemTextViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemTextViewHolder.java index b0eaa779..d95817ea 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemTextViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemTextViewHolder.java @@ -2,12 +2,20 @@ package awais.instagrabber.adapters.viewholder.directmessages; import androidx.annotation.NonNull; +import com.google.common.collect.ImmutableList; + +import java.util.List; + +import awais.instagrabber.R; import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback; +import awais.instagrabber.customviews.DirectItemContextMenu; import awais.instagrabber.databinding.LayoutDmBaseBinding; import awais.instagrabber.databinding.LayoutDmTextBinding; import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectThread; +import awais.instagrabber.utils.TextUtils; +import awais.instagrabber.utils.Utils; public class DirectItemTextViewHolder extends DirectItemViewHolder { @@ -35,4 +43,15 @@ public class DirectItemTextViewHolder extends DirectItemViewHolder { protected boolean showBackground() { return true; } + + @Override + protected List getLongClickOptions() { + return ImmutableList.of( + new DirectItemContextMenu.MenuItem(R.id.copy, R.string.copy, item -> { + if (TextUtils.isEmpty(item.getText())) return null; + Utils.copyText(itemView.getContext(), item.getText()); + return null; + }) + ); + } } diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java index 9580abd8..96476b10 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemViewHolder.java @@ -134,7 +134,7 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple containerLayoutParams.setMarginStart(0); containerLayoutParams.setMarginEnd(0); } - if (itemType == DirectItemType.TEXT || itemType == DirectItemType.LINK) { + if (itemType == DirectItemType.TEXT || itemType == DirectItemType.LINK || itemType == DirectItemType.UNKNOWN) { binding.messageInfo.setPadding(0, 0, dmRadius, dmRadiusSmall); } else { if (showMessageInfo()) { @@ -543,22 +543,13 @@ public abstract class DirectItemViewHolder extends RecyclerView.ViewHolder imple if (thread.getInputMode() != 1 && messageDirection == MessageDirection.OUTGOING) { builder.add(new DirectItemContextMenu.MenuItem(R.id.unsend, R.string.dms_inbox_unsend)); } - final DirectItemType itemType = item.getItemType(); - switch (itemType) { - case ANIMATED_MEDIA: - builder.add(new DirectItemContextMenu.MenuItem(R.id.detail, R.string.dms_inbox_giphy)); - break; - case VOICE_MEDIA: - builder.add(new DirectItemContextMenu.MenuItem(R.id.detail, R.string.action_download)); - break; - } final boolean showReactions = thread.getInputMode() != 1 && allowReaction(); final ImmutableList menuItems = builder.build(); if (!showReactions && menuItems.isEmpty()) return; final DirectItemContextMenu menu = new DirectItemContextMenu(itemView.getContext(), showReactions, menuItems); menu.setOnDismissListener(() -> setSelected(false)); menu.setOnReactionClickListener(emoji -> callback.onReaction(item, emoji)); - menu.setOnOptionSelectListener(itemId -> callback.onOptionSelect(item, itemId)); + menu.setOnOptionSelectListener((itemId, cb) -> callback.onOptionSelect(item, itemId, cb)); menu.show(itemView, location); } diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemVoiceMediaViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemVoiceMediaViewHolder.java index 13399b06..255cceae 100644 --- a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemVoiceMediaViewHolder.java +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemVoiceMediaViewHolder.java @@ -12,12 +12,14 @@ import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.SimpleExoPlayer; import com.google.android.exoplayer2.source.ProgressiveMediaSource; import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; +import com.google.common.collect.ImmutableList; import com.google.common.primitives.Floats; import java.util.List; import awais.instagrabber.R; import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback; +import awais.instagrabber.customviews.DirectItemContextMenu; import awais.instagrabber.databinding.LayoutDmBaseBinding; import awais.instagrabber.databinding.LayoutDmVoiceMediaBinding; import awais.instagrabber.repositories.responses.Audio; @@ -174,6 +176,13 @@ public class DirectItemVoiceMediaViewHolder extends DirectItemViewHolder { return false; } + @Override + protected List getLongClickOptions() { + return ImmutableList.of( + new DirectItemContextMenu.MenuItem(R.id.download, R.string.action_download) + ); + } + private static class AudioItemState { private boolean prepared; diff --git a/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemXmaViewHolder.java b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemXmaViewHolder.java new file mode 100644 index 00000000..1b1a9fda --- /dev/null +++ b/app/src/main/java/awais/instagrabber/adapters/viewholder/directmessages/DirectItemXmaViewHolder.java @@ -0,0 +1,69 @@ +package awais.instagrabber.adapters.viewholder.directmessages; + +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.core.util.Pair; +import androidx.recyclerview.widget.ItemTouchHelper; + +import com.facebook.drawee.backends.pipeline.Fresco; + +import awais.instagrabber.adapters.DirectItemsAdapter.DirectItemCallback; +import awais.instagrabber.databinding.LayoutDmAnimatedMediaBinding; +import awais.instagrabber.databinding.LayoutDmBaseBinding; +import awais.instagrabber.repositories.responses.User; +import awais.instagrabber.repositories.responses.directmessages.DirectItem; +import awais.instagrabber.repositories.responses.directmessages.DirectItemXma; +import awais.instagrabber.repositories.responses.directmessages.DirectThread; +import awais.instagrabber.utils.NumberUtils; + +public class DirectItemXmaViewHolder extends DirectItemViewHolder { + + private final LayoutDmAnimatedMediaBinding binding; + + public DirectItemXmaViewHolder(@NonNull final LayoutDmBaseBinding baseBinding, + @NonNull final LayoutDmAnimatedMediaBinding binding, + final User currentUser, + final DirectThread thread, + final DirectItemCallback callback) { + super(baseBinding, currentUser, thread, callback); + this.binding = binding; + setItemView(binding.getRoot()); + } + + @Override + public void bindItem(final DirectItem item, final MessageDirection messageDirection) { + final DirectItemXma xma = item.getXma(); + final DirectItemXma.XmaUrlInfo playableUrlInfo = xma.getPlayableUrlInfo(); + final DirectItemXma.XmaUrlInfo previewUrlInfo = xma.getPreviewUrlInfo(); + if (playableUrlInfo == null && previewUrlInfo == null) { + binding.ivAnimatedMessage.setController(null); + return; + } + final DirectItemXma.XmaUrlInfo urlInfo = playableUrlInfo != null ? playableUrlInfo : previewUrlInfo; + final String url = urlInfo.getUrl(); + final Pair widthHeight = NumberUtils.calculateWidthHeight( + urlInfo.getHeight(), + urlInfo.getWidth(), + mediaImageMaxHeight, + mediaImageMaxWidth + ); + binding.ivAnimatedMessage.setVisibility(View.VISIBLE); + final ViewGroup.LayoutParams layoutParams = binding.ivAnimatedMessage.getLayoutParams(); + final int width = widthHeight.first != null ? widthHeight.first : 0; + final int height = widthHeight.second != null ? widthHeight.second : 0; + layoutParams.width = width; + layoutParams.height = height; + binding.ivAnimatedMessage.requestLayout(); + binding.ivAnimatedMessage.setController(Fresco.newDraweeControllerBuilder() + .setUri(url) + .setAutoPlayAnimations(true) + .build()); + } + + @Override + public int getSwipeDirection() { + return ItemTouchHelper.ACTION_STATE_IDLE; + } +} diff --git a/app/src/main/java/awais/instagrabber/customviews/DirectItemContextMenu.java b/app/src/main/java/awais/instagrabber/customviews/DirectItemContextMenu.java index f615b7fc..65a18141 100644 --- a/app/src/main/java/awais/instagrabber/customviews/DirectItemContextMenu.java +++ b/app/src/main/java/awais/instagrabber/customviews/DirectItemContextMenu.java @@ -29,12 +29,14 @@ import androidx.constraintlayout.widget.ConstraintLayout; import androidx.core.util.Pair; import java.util.List; +import java.util.function.Function; import awais.instagrabber.R; import awais.instagrabber.animations.RoundedRectRevealOutlineProvider; import awais.instagrabber.customviews.emoji.Emoji; import awais.instagrabber.customviews.emoji.ReactionsManager; import awais.instagrabber.databinding.LayoutDirectItemOptionsBinding; +import awais.instagrabber.repositories.responses.directmessages.DirectItem; import static android.view.View.MeasureSpec.makeMeasureSpec; @@ -345,7 +347,7 @@ public class DirectItemContextMenu extends PopupWindow { textView.setText(context.getString(menuItem.getTitleRes())); textView.setOnClickListener(v -> { if (onOptionSelectListener != null) { - onOptionSelectListener.onSelect(menuItem.getItemId()); + onOptionSelectListener.onSelect(menuItem.getItemId(), menuItem.getCallback()); } dismiss(); }); @@ -397,9 +399,19 @@ public class DirectItemContextMenu extends PopupWindow { @StringRes private final int titleRes; + /** + * Callback function + */ + private final Function callback; + public MenuItem(@IdRes final int itemId, @StringRes final int titleRes) { + this(itemId, titleRes, null); + } + + public MenuItem(@IdRes final int itemId, @StringRes final int titleRes, @Nullable final Function callback) { this.itemId = itemId; this.titleRes = titleRes; + this.callback = callback; } public int getItemId() { @@ -409,10 +421,14 @@ public class DirectItemContextMenu extends PopupWindow { public int getTitleRes() { return titleRes; } + + public Function getCallback() { + return callback; + } } public interface OnOptionSelectListener { - void onSelect(int itemId); + void onSelect(int itemId, @Nullable Function callback); } public interface OnReactionClickListener { diff --git a/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java b/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java index cdab91c9..b2456e0a 100644 --- a/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java +++ b/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java @@ -22,6 +22,7 @@ import androidx.work.WorkManager; import com.google.common.collect.ImmutableList; import java.util.ArrayList; +import java.util.Arrays; import java.util.Collections; import java.util.List; @@ -74,11 +75,10 @@ public class PostsRecyclerView extends RecyclerView { } final List models = mediaViewModel.getList().getValue(); final List modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models); - if (settingsHelper.getBoolean(Constants.TOGGLE_KEYWORD_FILTER)){ + if (settingsHelper.getBoolean(Constants.TOGGLE_KEYWORD_FILTER)) { final ArrayList items = new ArrayList<>(settingsHelper.getStringSet(Constants.KEYWORD_FILTERS)); modelsCopy.addAll(new KeywordsFilterUtils(items).filter(result)); - } - else { + } else { modelsCopy.addAll(result); } mediaViewModel.getList().postValue(modelsCopy); @@ -194,7 +194,9 @@ public class PostsRecyclerView extends RecyclerView { private void initSelf() { mediaViewModel = new ViewModelProvider(viewModelStoreOwner).get(MediaViewModel.class); mediaViewModel.getList().observe(lifeCycleOwner, list -> { - if (list.size() > 0) feedAdapter.submitList(list, () -> { + if (list.size() <= 0) return; + feedAdapter.submitList(list, () -> { + // postDelayed(this::fetchMoreIfPossible, 1000); if (!shouldScrollToTop) return; smoothScrollToPosition(0); shouldScrollToTop = false; @@ -217,6 +219,20 @@ public class PostsRecyclerView extends RecyclerView { dispatchFetchStatus(); } + private void fetchMoreIfPossible() { + if (!postFetcher.hasMore()) return; + if (feedAdapter.getItemCount() == 0) return; + final LayoutManager layoutManager = getLayoutManager(); + if (!(layoutManager instanceof StaggeredGridLayoutManager)) return; + final int[] itemPositions = ((StaggeredGridLayoutManager) layoutManager).findLastCompletelyVisibleItemPositions(null); + final boolean allNoPosition = Arrays.stream(itemPositions).allMatch(position -> position == RecyclerView.NO_POSITION); + if (allNoPosition) return; + final boolean match = Arrays.stream(itemPositions).anyMatch(position -> position == feedAdapter.getItemCount() - 1); + if (!match) return; + postFetcher.fetch(); + dispatchFetchStatus(); + } + private void initDownloadWorkerListener() { WorkManager.getInstance(getContext()) .getWorkInfosByTagLiveData("download") diff --git a/app/src/main/java/awais/instagrabber/db/datasources/FavoriteDataSource.java b/app/src/main/java/awais/instagrabber/db/datasources/FavoriteDataSource.java index 36798b08..bc013926 100644 --- a/app/src/main/java/awais/instagrabber/db/datasources/FavoriteDataSource.java +++ b/app/src/main/java/awais/instagrabber/db/datasources/FavoriteDataSource.java @@ -47,7 +47,7 @@ public class FavoriteDataSource { } public final void insertOrUpdateFavorite(@NonNull final Favorite favorite) { - if (favorite.getId() > 0) { + if (favorite.getId() != 0) { favoriteDao.updateFavorites(favorite); return; } diff --git a/app/src/main/java/awais/instagrabber/dialogs/TabOrderPreferenceDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/TabOrderPreferenceDialogFragment.java new file mode 100644 index 00000000..0ac2dd23 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/dialogs/TabOrderPreferenceDialogFragment.java @@ -0,0 +1,275 @@ +package awais.instagrabber.dialogs; + +import android.app.Dialog; +import android.content.Context; +import android.graphics.Canvas; +import android.os.Bundle; +import android.util.Pair; +import android.view.View; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.fragment.app.DialogFragment; +import androidx.recyclerview.widget.ItemTouchHelper; +import androidx.recyclerview.widget.ItemTouchHelper.SimpleCallback; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import com.google.android.material.dialog.MaterialAlertDialogBuilder; +import com.google.common.collect.ImmutableList; + +import java.util.List; +import java.util.stream.Collectors; + +import awais.instagrabber.R; +import awais.instagrabber.adapters.DirectUsersAdapter; +import awais.instagrabber.adapters.TabsAdapter; +import awais.instagrabber.adapters.viewholder.TabViewHolder; +import awais.instagrabber.fragments.settings.PreferenceKeys; +import awais.instagrabber.models.Tab; +import awais.instagrabber.utils.Utils; + +import static androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_DRAG; +import static androidx.recyclerview.widget.ItemTouchHelper.DOWN; +import static androidx.recyclerview.widget.ItemTouchHelper.UP; + +public class TabOrderPreferenceDialogFragment extends DialogFragment { + private Callback callback; + private Context context; + private List tabsInPref; + private ItemTouchHelper itemTouchHelper; + private AlertDialog dialog; + private List newOrderTabs; + private List newOtherTabs; + + private final TabsAdapter.TabAdapterCallback tabAdapterCallback = new TabsAdapter.TabAdapterCallback() { + @Override + public void onStartDrag(final TabViewHolder viewHolder) { + if (itemTouchHelper == null || viewHolder == null) return; + itemTouchHelper.startDrag(viewHolder); + } + + @Override + public void onOrderChange(final List newOrderTabs) { + if (newOrderTabs == null || tabsInPref == null || dialog == null) return; + TabOrderPreferenceDialogFragment.this.newOrderTabs = newOrderTabs; + setSaveButtonState(newOrderTabs); + } + + @Override + public void onAdd(final Tab tab) { + // Add this tab to newOrderTabs + newOrderTabs = ImmutableList.builder() + .addAll(newOrderTabs) + .add(tab) + .build(); + // Remove this tab from newOtherTabs + if (newOtherTabs != null) { + newOtherTabs = newOtherTabs.stream() + .filter(t -> !t.equals(tab)) + .collect(Collectors.toList()); + } + setSaveButtonState(newOrderTabs); + // submit these tab lists to adapter + if (adapter == null) return; + adapter.submitList(newOrderTabs, newOtherTabs, () -> list.postDelayed(() -> adapter.notifyDataSetChanged(), 300)); + } + + @Override + public void onRemove(final Tab tab) { + // Remove this tab from newOrderTabs + newOrderTabs = newOrderTabs.stream() + .filter(t -> !t.equals(tab)) + .collect(Collectors.toList()); + // Add this tab to newOtherTabs + if (newOtherTabs != null) { + newOtherTabs = ImmutableList.builder() + .addAll(newOtherTabs) + .add(tab) + .build(); + } + setSaveButtonState(newOrderTabs); + // submit these tab lists to adapter + if (adapter == null) return; + adapter.submitList(newOrderTabs, newOtherTabs, () -> list.postDelayed(() -> { + adapter.notifyDataSetChanged(); + if (tab.getNavigationRootId() == R.id.direct_messages_nav_graph) { + final ConfirmDialogFragment dialogFragment = ConfirmDialogFragment.newInstance( + 111, 0, R.string.dm_remove_warning, R.string.ok, 0, 0 + ); + dialogFragment.show(getChildFragmentManager(), "dm_warning_dialog"); + } + }, 500)); + } + + private void setSaveButtonState(final List newOrderTabs) { + dialog.getButton(AlertDialog.BUTTON_POSITIVE) + .setEnabled(!newOrderTabs.equals(tabsInPref)); + } + }; + private final SimpleCallback simpleCallback = new SimpleCallback(UP | DOWN, 0) { + private int movePosition = RecyclerView.NO_POSITION; + + @Override + public int getMovementFlags(@NonNull final RecyclerView recyclerView, @NonNull final RecyclerView.ViewHolder viewHolder) { + if (viewHolder instanceof DirectUsersAdapter.HeaderViewHolder) return 0; + if (viewHolder instanceof TabViewHolder && !((TabViewHolder) viewHolder).isDraggable()) return 0; + return super.getMovementFlags(recyclerView, viewHolder); + } + + @Override + public void onChildDraw(@NonNull final Canvas c, + @NonNull final RecyclerView recyclerView, + @NonNull final RecyclerView.ViewHolder viewHolder, + final float dX, + final float dY, + final int actionState, + final boolean isCurrentlyActive) { + if (actionState != ACTION_STATE_DRAG) { + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + return; + } + final TabsAdapter adapter = (TabsAdapter) recyclerView.getAdapter(); + if (adapter == null) { + super.onChildDraw(c, recyclerView, viewHolder, dX, dY, actionState, isCurrentlyActive); + return; + } + // Do not allow dragging into 'Other tabs' category + float edgeY = dY; + final int lastPosition = adapter.getCurrentCount() - 1; + final View view = viewHolder.itemView; + // final int topEdge = recyclerView.getTop(); + final int bottomEdge = view.getHeight() * adapter.getCurrentCount() - view.getBottom(); + // if (movePosition == 0 && dY < topEdge) { + // edgeY = topEdge; + // } else + if (movePosition >= lastPosition && dY >= bottomEdge) { + edgeY = bottomEdge; + } + super.onChildDraw(c, recyclerView, viewHolder, dX, edgeY, actionState, isCurrentlyActive); + } + + @Override + public boolean onMove(@NonNull final RecyclerView recyclerView, + @NonNull final RecyclerView.ViewHolder viewHolder, + @NonNull final RecyclerView.ViewHolder target) { + final TabsAdapter adapter = (TabsAdapter) recyclerView.getAdapter(); + if (adapter == null) return false; + movePosition = target.getBindingAdapterPosition(); + if (movePosition >= adapter.getCurrentCount()) { + return false; + } + final int from = viewHolder.getBindingAdapterPosition(); + final int to = target.getBindingAdapterPosition(); + adapter.moveItem(from, to); + // adapter.notifyItemMoved(from, to); + return true; + } + + @Override + public void onSwiped(@NonNull final RecyclerView.ViewHolder viewHolder, final int direction) {} + + @Override + public void onSelectedChanged(@Nullable final RecyclerView.ViewHolder viewHolder, final int actionState) { + super.onSelectedChanged(viewHolder, actionState); + if (!(viewHolder instanceof TabViewHolder)) { + movePosition = RecyclerView.NO_POSITION; + return; + } + if (actionState == ACTION_STATE_DRAG) { + ((TabViewHolder) viewHolder).setDragging(true); + movePosition = viewHolder.getBindingAdapterPosition(); + } + } + + @Override + public void clearView(@NonNull final RecyclerView recyclerView, + @NonNull final RecyclerView.ViewHolder viewHolder) { + super.clearView(recyclerView, viewHolder); + ((TabViewHolder) viewHolder).setDragging(false); + movePosition = RecyclerView.NO_POSITION; + } + }; + private TabsAdapter adapter; + private RecyclerView list; + + public static TabOrderPreferenceDialogFragment newInstance() { + final Bundle args = new Bundle(); + final TabOrderPreferenceDialogFragment fragment = new TabOrderPreferenceDialogFragment(); + fragment.setArguments(args); + return fragment; + } + + public TabOrderPreferenceDialogFragment() {} + + @Override + public void onAttach(@NonNull final Context context) { + super.onAttach(context); + try { + callback = (Callback) getParentFragment(); + } catch (ClassCastException e) { + // throw new ClassCastException("Calling fragment must implement TabOrderPreferenceDialogFragment.Callback interface"); + } + this.context = context; + } + + @NonNull + @Override + public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { + return new MaterialAlertDialogBuilder(context) + .setView(createView()) + .setPositiveButton(R.string.save, (d, w) -> { + final boolean hasChanged = newOrderTabs != null && !newOrderTabs.equals(tabsInPref); + if (hasChanged) { + saveNewOrder(); + } + if (callback == null) return; + callback.onSave(hasChanged); + }) + .setNegativeButton(R.string.cancel, (dialog, which) -> { + if (callback == null) return; + callback.onCancel(); + }) + .create(); + } + + private void saveNewOrder() { + final String newOrderString = newOrderTabs.stream() + .map(Tab::getGraphName) + .collect(Collectors.joining(",")); + Utils.settingsHelper.putString(PreferenceKeys.PREF_TAB_ORDER, newOrderString); + } + + @Override + public void onStart() { + super.onStart(); + final Dialog dialog = getDialog(); + if (!(dialog instanceof AlertDialog)) return; + this.dialog = (AlertDialog) dialog; + this.dialog.getButton(AlertDialog.BUTTON_POSITIVE).setEnabled(false); + } + + @NonNull + private View createView() { + list = new RecyclerView(context); + list.setLayoutManager(new LinearLayoutManager(context)); + itemTouchHelper = new ItemTouchHelper(simpleCallback); + itemTouchHelper.attachToRecyclerView(list); + adapter = new TabsAdapter(tabAdapterCallback); + list.setAdapter(adapter); + final Pair, List> navTabListPair = Utils.getNavTabList(context); + tabsInPref = navTabListPair.first; + // initially set newOrderTabs and newOtherTabs same as current tabs + newOrderTabs = navTabListPair.first; + newOtherTabs = navTabListPair.second; + adapter.submitList(navTabListPair.first, navTabListPair.second); + return list; + } + + public interface Callback { + void onSave(final boolean orderHasChanged); + + void onCancel(); + } +} diff --git a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java index 65db4826..c42809c5 100644 --- a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java @@ -65,13 +65,14 @@ import awais.instagrabber.webservices.GraphQLService; import awais.instagrabber.webservices.LocationService; import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.StoriesService; -//import awaisomereport.LogCollector; import static androidx.core.content.PermissionChecker.checkSelfPermission; import static awais.instagrabber.utils.DownloadUtils.WRITE_PERMISSION; -//import static awais.instagrabber.utils.Utils.logCollector; import static awais.instagrabber.utils.Utils.settingsHelper; +//import awaisomereport.LogCollector; +//import static awais.instagrabber.utils.Utils.logCollector; + public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "LocationFragment"; private static final int STORAGE_PERM_REQUEST_CODE = 8020; diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java index 34d4ed1c..bf13abb6 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.java @@ -20,8 +20,6 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.fragment.app.Fragment; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; -import androidx.lifecycle.ViewModelStoreOwner; -import androidx.navigation.NavController; import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.LinearLayoutManager; @@ -29,7 +27,6 @@ import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; import com.google.android.material.badge.BadgeDrawable; import com.google.android.material.badge.BadgeUtils; -import com.google.android.material.bottomnavigation.BottomNavigationView; import com.google.android.material.snackbar.Snackbar; import java.util.List; @@ -66,9 +63,7 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh super.onCreate(savedInstanceState); fragmentActivity = (MainActivity) getActivity(); if (fragmentActivity != null) { - final NavController navController = NavHostFragment.findNavController(this); - final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph); - viewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectInboxViewModel.class); + viewModel = new ViewModelProvider(fragmentActivity).get(DirectInboxViewModel.class); } setHasOptionsMenu(true); } @@ -101,6 +96,7 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh } } + @SuppressLint("UnsafeExperimentalUsageError") @Override public void onPause() { super.onPause(); @@ -201,11 +197,6 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh break; } }); - viewModel.getUnseenCount().observe(getViewLifecycleOwner(), unseenCountResource -> { - if (unseenCountResource == null) return; - final Integer unseenCount = unseenCountResource.data; - setBottomNavBarBadge(unseenCount == null ? 0 : unseenCount); - }); viewModel.getPendingRequestsTotal().observe(getViewLifecycleOwner(), this::attachPendingRequestsBadge); } @@ -213,12 +204,7 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh private void attachPendingRequestsBadge(@Nullable final Integer count) { if (pendingRequestsMenuItem == null) { final Handler handler = new Handler(); - handler.postDelayed(new Runnable() { - @Override - public void run() { - attachPendingRequestsBadge(count); - } - }, 500); + handler.postDelayed(() -> attachPendingRequestsBadge(count), 500); return; } if (pendingRequestTotalBadgeDrawable == null) { @@ -277,20 +263,4 @@ public class DirectMessageInboxFragment extends Fragment implements SwipeRefresh }); binding.inboxList.addOnScrollListener(lazyLoader); } - - private void setBottomNavBarBadge(final int unseenCount) { - final BottomNavigationView bottomNavView = fragmentActivity.getBottomNavView(); - final BadgeDrawable badge = bottomNavView.getOrCreateBadge(R.id.direct_messages_nav_graph); - if (badge == null) return; - if (unseenCount == 0) { - badge.setVisible(false); - badge.clearNumber(); - return; - } - if (badge.getVerticalOffset() != 10) { - badge.setVerticalOffset(10); - } - badge.setNumber(unseenCount); - badge.setVisible(true); - } } diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java index a960cd36..f98e4094 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.java @@ -79,11 +79,13 @@ public class DirectMessageSettingsFragment extends Fragment implements ConfirmDi final DirectMessageSettingsFragmentArgs args = DirectMessageSettingsFragmentArgs.fromBundle(arguments); final MainActivity fragmentActivity = (MainActivity) requireActivity(); final AppStateViewModel appStateViewModel = new ViewModelProvider(fragmentActivity).get(AppStateViewModel.class); - viewModel = new ViewModelProvider(this, new DirectSettingsViewModelFactory(fragmentActivity.getApplication(), - args.getThreadId(), - args.getPending(), - appStateViewModel.getCurrentUser())) - .get(DirectSettingsViewModel.class); + final DirectSettingsViewModelFactory viewModelFactory = new DirectSettingsViewModelFactory( + fragmentActivity.getApplication(), + args.getThreadId(), + args.getPending(), + appStateViewModel.getCurrentUser() + ); + viewModel = new ViewModelProvider(this, viewModelFactory).get(DirectSettingsViewModel.class); } @NonNull diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java index acb1dcde..a6b1cb4c 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java @@ -58,6 +58,7 @@ import java.io.File; import java.util.ArrayList; import java.util.List; import java.util.Set; +import java.util.function.Function; import awais.instagrabber.ProfileNavGraphDirections; import awais.instagrabber.R; @@ -258,7 +259,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact } @Override - public void onOptionSelect(final DirectItem item, final int itemId) { + public void onOptionSelect(final DirectItem item, final int itemId, final Function cb) { if (itemId == R.id.unsend) { handleSentMessage(viewModel.unsend(item)); return; @@ -275,21 +276,17 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact final NavController navController = NavHostFragment.findNavController(DirectMessageThreadFragment.this); navController.navigate(actionGlobalUserSearch); } - if (itemId == R.id.detail) { - final Context context = getContext(); - if (context == null) return; - final DirectItemType itemType = item.getItemType(); - switch (itemType) { - case ANIMATED_MEDIA: - Utils.openURL(context, "https://giphy.com/gifs/" + item.getAnimatedMedia().getId()); - break; - case VOICE_MEDIA: - downloadItem(item.getVoiceMedia() == null ? null : item.getVoiceMedia().getMedia(), context); - break; - } + if (itemId == R.id.download) { + downloadItem(item); + return; + } + // otherwise call callback if present + if (cb != null) { + cb.apply(item); } } }; + private final DirectItemLongClickListener directItemLongClickListener = position -> { // viewModel.setSelectedPosition(position); }; @@ -319,6 +316,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact }; private final MutableLiveData inputLength = new MutableLiveData<>(0); private MenuItem markAsSeenMenuItem; + private Media tempMedia; @Override public void onCreate(@Nullable final Bundle savedInstanceState) { @@ -329,11 +327,13 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact final Bundle arguments = getArguments(); if (arguments == null) return; final DirectMessageThreadFragmentArgs fragmentArgs = DirectMessageThreadFragmentArgs.fromBundle(arguments); - viewModel = new ViewModelProvider(this, new DirectThreadViewModelFactory(fragmentActivity.getApplication(), - fragmentArgs.getThreadId(), - fragmentArgs.getPending(), - appStateViewModel.getCurrentUser())) - .get(DirectThreadViewModel.class); + final DirectThreadViewModelFactory viewModelFactory = new DirectThreadViewModelFactory( + fragmentActivity.getApplication(), + fragmentArgs.getThreadId(), + fragmentArgs.getPending(), + appStateViewModel.getCurrentUser() + ); + viewModel = new ViewModelProvider(this, viewModelFactory).get(DirectThreadViewModel.class); setHasOptionsMenu(true); } @@ -454,7 +454,9 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact final Context context = getContext(); if (context == null) return; if (requestCode == STORAGE_PERM_REQUEST_CODE && grantResults[0] == PackageManager.PERMISSION_GRANTED) { - // downloadItem(context); + if (tempMedia == null) return; + downloadItem(context, tempMedia); + return; } if (requestCode == AUDIO_RECORD_PERM_REQUEST_CODE) { if (PermissionUtils.hasAudioRecordPerms(context)) { @@ -1317,18 +1319,31 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact appExecutors.mainThread().execute(prevTitleRunnable, 1000); } + private void downloadItem(final DirectItem item) { + final Context context = getContext(); + if (context == null) return; + final DirectItemType itemType = item.getItemType(); + //noinspection SwitchStatementWithTooFewBranches + switch (itemType) { + case VOICE_MEDIA: + downloadItem(context, item.getVoiceMedia() == null ? null : item.getVoiceMedia().getMedia()); + break; + } + } + // currently ONLY for voice - private void downloadItem(final Media media, final Context context) { + private void downloadItem(@NonNull final Context context, final Media media) { if (media == null) { Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); - } else { - if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) { - DownloadUtils.download(context, media); - } else { - requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); - } - Toast.makeText(context, R.string.downloader_downloading_media, Toast.LENGTH_SHORT).show(); + return; } + if (ContextCompat.checkSelfPermission(context, DownloadUtils.PERMS[0]) == PackageManager.PERMISSION_GRANTED) { + DownloadUtils.download(context, media); + Toast.makeText(context, R.string.downloader_downloading_media, Toast.LENGTH_SHORT).show(); + return; + } + tempMedia = media; + requestPermissions(DownloadUtils.PERMS, STORAGE_PERM_REQUEST_CODE); } @NonNull diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectPendingInboxFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectPendingInboxFragment.java index 8ee0d1c4..bf459ef1 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectPendingInboxFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectPendingInboxFragment.java @@ -13,8 +13,6 @@ import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.fragment.app.Fragment; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; -import androidx.lifecycle.ViewModelStoreOwner; -import androidx.navigation.NavController; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; @@ -24,7 +22,6 @@ import com.google.android.material.snackbar.Snackbar; import java.util.Collections; import java.util.List; -import awais.instagrabber.R; import awais.instagrabber.activities.MainActivity; import awais.instagrabber.adapters.DirectMessageInboxAdapter; import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge; @@ -51,9 +48,7 @@ public class DirectPendingInboxFragment extends Fragment implements SwipeRefresh super.onCreate(savedInstanceState); fragmentActivity = (MainActivity) getActivity(); if (fragmentActivity != null) { - final NavController navController = NavHostFragment.findNavController(this); - final ViewModelStoreOwner viewModelStoreOwner = navController.getViewModelStoreOwner(R.id.direct_messages_nav_graph); - viewModel = new ViewModelProvider(viewModelStoreOwner).get(DirectPendingInboxViewModel.class); + viewModel = new ViewModelProvider(fragmentActivity).get(DirectPendingInboxViewModel.class); } } 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 4b3bfcfd..cc2ce13a 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.java @@ -12,7 +12,6 @@ import android.text.style.RelativeSizeSpan; import android.text.style.StyleSpan; import android.util.Log; import android.view.ActionMode; -import android.view.Gravity; import android.view.LayoutInflater; import android.view.Menu; import android.view.MenuInflater; @@ -307,6 +306,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe private AccountRepository accountRepository; private FavoriteRepository favoriteRepository; private AppStateViewModel appStateViewModel; + private boolean disableDm = false; @Override public void onCreate(@Nullable final Bundle savedInstanceState) { @@ -322,8 +322,10 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe mediaService = isLoggedIn ? MediaService.getInstance(null, null, 0) : null; userService = isLoggedIn ? UserService.getInstance() : null; graphQLService = isLoggedIn ? null : GraphQLService.getInstance(); - accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(getContext())); - favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(getContext())); + final Context context = getContext(); + if (context == null) return; + accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(context)); + favoriteRepository = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(context)); appStateViewModel = new ViewModelProvider(fragmentActivity).get(AppStateViewModel.class); setHasOptionsMenu(true); } @@ -578,6 +580,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe } private void init() { + disableDm = !Utils.isNavRootInCurrentTabs("direct_messages_nav_graph"); if (getArguments() != null) { final ProfileFragmentArgs fragmentArgs = ProfileFragmentArgs.fromBundle(getArguments()); username = fragmentArgs.getUsername(); @@ -604,12 +607,16 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe usernameTemp = usernameTemp.substring(1); } if (TextUtils.isEmpty(usernameTemp)) { - profileModel = appStateViewModel.getCurrentUser(); - username = profileModel.getUsername(); - setUsernameDelayed(); - setProfileDetails(); + appStateViewModel.getCurrentUserLiveData().observe(getViewLifecycleOwner(), user -> { + if (user == null) return; + profileModel = user; + username = profileModel.getUsername(); + setUsernameDelayed(); + setProfileDetails(); + }); + return; } - else if (isLoggedIn) { + if (isLoggedIn) { userService.getUsernameInfo(usernameTemp, new ServiceCallback() { @Override public void onSuccess(final User user) { @@ -643,26 +650,25 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe } catch (final Throwable ignored) {} } }); + return; } - else { - graphQLService.fetchUser(usernameTemp, new ServiceCallback() { - @Override - public void onSuccess(final User user) { - profileModel = user; - setProfileDetails(); - } + graphQLService.fetchUser(usernameTemp, new ServiceCallback() { + @Override + public void onSuccess(final User user) { + profileModel = user; + setProfileDetails(); + } - @Override - public void onFailure(final Throwable t) { - Log.e(TAG, "Error fetching profile", t); - final Context context = getContext(); - try { - if (t == null) Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_LONG).show(); - else Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); - } catch (final Throwable ignored) {} - } - }); - } + @Override + public void onFailure(final Throwable t) { + Log.e(TAG, "Error fetching profile", t); + final Context context = getContext(); + try { + if (t == null) Toast.makeText(context, R.string.error_loading_profile, Toast.LENGTH_LONG).show(); + else Toast.makeText(context, t.getMessage(), Toast.LENGTH_SHORT).show(); + } catch (final Throwable ignored) {} + } + }); } private void setProfileDetails() { @@ -936,7 +942,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe } profileDetailsBinding.btnSaved.setVisibility(View.GONE); profileDetailsBinding.btnLiked.setVisibility(View.GONE); - profileDetailsBinding.btnDM.setVisibility(View.VISIBLE); + profileDetailsBinding.btnDM.setVisibility(disableDm ? View.GONE : View.VISIBLE); profileDetailsBinding.btnFollow.setVisibility(View.VISIBLE); final Context context = getContext(); if (context == null) return; @@ -987,6 +993,7 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe } private void updateAccountInfo() { + if (profileModel == null) return; accountRepository.insertOrUpdateAccount( profileModel.getPk(), profileModel.getUsername(), @@ -1113,23 +1120,25 @@ public class ProfileFragment extends Fragment implements SwipeRefreshLayout.OnRe PostItemType.TAGGED); NavHostFragment.findNavController(this).navigate(action); }); - profileDetailsBinding.btnDM.setOnClickListener(v -> { - profileDetailsBinding.btnDM.setEnabled(false); - new CreateThreadAction(cookie, profileModel.getPk(), thread -> { - if (thread == null) { - Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); + if (!disableDm) { + profileDetailsBinding.btnDM.setOnClickListener(v -> { + profileDetailsBinding.btnDM.setEnabled(false); + new CreateThreadAction(cookie, profileModel.getPk(), thread -> { + if (thread == null) { + Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); + profileDetailsBinding.btnDM.setEnabled(true); + return; + } + final InboxManager inboxManager = DirectMessagesManager.getInstance().getInboxManager(); + if (!inboxManager.containsThread(thread.getThreadId())) { + thread.setTemp(true); + inboxManager.addThread(thread, 0); + } + fragmentActivity.navigateToThread(thread.getThreadId(), profileModel.getUsername()); profileDetailsBinding.btnDM.setEnabled(true); - return; - } - final InboxManager inboxManager = DirectMessagesManager.getInstance().getInboxManager(); - if (!inboxManager.containsThread(thread.getThreadId())) { - thread.setTemp(true); - inboxManager.addThread(thread, 0); - } - fragmentActivity.navigateToThread(thread.getThreadId(), profileModel.getUsername()); - profileDetailsBinding.btnDM.setEnabled(true); - }).execute(); - }); + }).execute(); + }); + } profileDetailsBinding.mainProfileImage.setOnClickListener(v -> { if (!hasStories) { // show profile pic diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/GeneralPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/GeneralPreferencesFragment.java index f63c4a7a..2eba325e 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/GeneralPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/GeneralPreferencesFragment.java @@ -1,7 +1,7 @@ package awais.instagrabber.fragments.settings; import android.content.Context; -import android.content.res.TypedArray; +import android.util.Pair; import androidx.annotation.NonNull; import androidx.preference.ListPreference; @@ -12,13 +12,17 @@ import androidx.preference.SwitchPreferenceCompat; import java.util.List; import awais.instagrabber.R; +import awais.instagrabber.dialogs.ConfirmDialogFragment; +import awais.instagrabber.dialogs.TabOrderPreferenceDialogFragment; +import awais.instagrabber.models.Tab; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.TextUtils; +import awais.instagrabber.utils.Utils; import static awais.instagrabber.utils.Utils.settingsHelper; -public class GeneralPreferencesFragment extends BasePreferencesFragment { +public class GeneralPreferencesFragment extends BasePreferencesFragment implements TabOrderPreferenceDialogFragment.Callback { @Override void setupPreferenceScreen(final PreferenceScreen screen) { @@ -28,12 +32,14 @@ public class GeneralPreferencesFragment extends BasePreferencesFragment { final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0; if (isLoggedIn) { screen.addPreference(getDefaultTabPreference(context)); + screen.addPreference(getTabOrderPreference(context)); } screen.addPreference(getUpdateCheckPreference(context)); screen.addPreference(getFlagSecurePreference(context)); - final List preferences = FlavorSettings.getInstance().getPreferences(context, - getChildFragmentManager(), - SettingCategory.GENERAL); + final List preferences = FlavorSettings.getInstance() + .getPreferences(context, + getChildFragmentManager(), + SettingCategory.GENERAL); if (preferences != null) { for (final Preference preference : preferences) { screen.addPreference(preference); @@ -44,24 +50,36 @@ public class GeneralPreferencesFragment extends BasePreferencesFragment { private Preference getDefaultTabPreference(@NonNull final Context context) { final ListPreference preference = new ListPreference(context); preference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance()); - final TypedArray mainNavIds = getResources().obtainTypedArray(R.array.main_nav_ids); - final int length = mainNavIds.length(); - final String[] values = new String[length]; - for (int i = 0; i < length; i++) { - final int resourceId = mainNavIds.getResourceId(i, -1); - if (resourceId < 0) continue; - values[i] = getResources().getResourceEntryName(resourceId); - } - mainNavIds.recycle(); + final Pair, List> listPair = Utils.getNavTabList(context); + final List tabs = listPair.first; + final String[] titles = tabs.stream() + .map(Tab::getTitle) + .toArray(String[]::new); + final String[] navGraphFileNames = tabs.stream() + .map(Tab::getGraphName) + .toArray(String[]::new); preference.setKey(Constants.DEFAULT_TAB); preference.setTitle(R.string.pref_start_screen); preference.setDialogTitle(R.string.pref_start_screen); - preference.setEntries(R.array.main_nav_ids_values); - preference.setEntryValues(values); + preference.setEntries(titles); + preference.setEntryValues(navGraphFileNames); preference.setIconSpaceReserved(false); return preference; } + @NonNull + private Preference getTabOrderPreference(@NonNull final Context context) { + final Preference preference = new Preference(context); + preference.setTitle(R.string.tab_order); + preference.setIconSpaceReserved(false); + preference.setOnPreferenceClickListener(preference1 -> { + final TabOrderPreferenceDialogFragment dialogFragment = TabOrderPreferenceDialogFragment.newInstance(); + dialogFragment.show(getChildFragmentManager(), "tab_order_dialog"); + return true; + }); + return preference; + } + private Preference getUpdateCheckPreference(@NonNull final Context context) { final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context); preference.setKey(Constants.CHECK_UPDATES); @@ -82,4 +100,22 @@ public class GeneralPreferencesFragment extends BasePreferencesFragment { return true; }); } + + @Override + public void onSave(final boolean orderHasChanged) { + if (!orderHasChanged) return; + final ConfirmDialogFragment dialogFragment = ConfirmDialogFragment.newInstance( + 111, + 0, + R.string.tab_order_start_next_launch, + R.string.ok, + 0, + 0); + dialogFragment.show(getChildFragmentManager(), "tab_order_set_dialog"); + } + + @Override + public void onCancel() { + + } } diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java index 684888c5..6e9e13fd 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java @@ -27,6 +27,7 @@ import java.util.List; import awais.instagrabber.BuildConfig; import awais.instagrabber.R; import awais.instagrabber.activities.Login; +import awais.instagrabber.activities.MainActivity; import awais.instagrabber.databinding.PrefAccountSwitcherBinding; import awais.instagrabber.db.datasources.AccountDataSource; import awais.instagrabber.db.entities.Account; @@ -38,6 +39,7 @@ import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.FlavorTown; import awais.instagrabber.utils.TextUtils; +import awais.instagrabber.utils.Utils; import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.UserService; @@ -55,8 +57,10 @@ public class MorePreferencesFragment extends BasePreferencesFragment { void setupPreferenceScreen(final PreferenceScreen screen) { final String cookie = settingsHelper.getString(Constants.COOKIE); final boolean isLoggedIn = !TextUtils.isEmpty(cookie) && CookieUtils.getUserIdFromCookie(cookie) > 0; + final MainActivity activity = (MainActivity) getActivity(); // screen.addPreference(new MoreHeaderPreference(getContext())); final Context context = getContext(); + final Resources resources = context.getResources(); if (context == null) return; accountRepository = AccountRepository.getInstance(AccountDataSource.getInstance(context)); final PreferenceCategory accountCategory = new PreferenceCategory(context); @@ -135,13 +139,30 @@ public class MorePreferencesFragment extends BasePreferencesFragment { screen.addPreference(getDivider(context)); final NavController navController = NavHostFragment.findNavController(this); if (isLoggedIn) { - screen.addPreference(getPreference(R.string.action_notif, R.drawable.ic_not_liked, preference -> { - if (isSafeToNavigate(navController)) { - final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("notif"); - navController.navigate(navDirections); - } - return true; - })); + boolean showActivity = true; + boolean showExplore = false; + if (activity != null) { + showActivity = !Utils.isNavRootInCurrentTabs("notification_viewer_nav_graph"); + showExplore = !Utils.isNavRootInCurrentTabs("discover_nav_graph"); + } + if (showActivity) { + screen.addPreference(getPreference(R.string.action_notif, R.drawable.ic_not_liked, preference -> { + if (isSafeToNavigate(navController)) { + final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("notif"); + navController.navigate(navDirections); + } + return true; + })); + } + if (showExplore) { + screen.addPreference(getPreference(R.string.title_discover, R.drawable.ic_explore_24, preference -> { + if (isSafeToNavigate(navController)) { + navController.navigate(R.id.discover_nav_graph); + } + return true; + })); + } + screen.addPreference(getPreference(R.string.action_ayml, R.drawable.ic_suggested_users, preference -> { if (isSafeToNavigate(navController)) { final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("ayml"); @@ -157,13 +178,21 @@ public class MorePreferencesFragment extends BasePreferencesFragment { return true; })); } - screen.addPreference(getPreference(R.string.title_favorites, R.drawable.ic_star_24, preference -> { - if (isSafeToNavigate(navController)) { - final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToFavoritesFragment(); - navController.navigate(navDirections); - } - return true; - })); + + // Check if favorites has been added as a tab. And if so, do not add in this list + boolean showFavorites = true; + if (activity != null) { + showFavorites = !Utils.isNavRootInCurrentTabs("favorites_nav_graph"); + } + if (showFavorites) { + screen.addPreference(getPreference(R.string.title_favorites, R.drawable.ic_star_24, preference -> { + if (isSafeToNavigate(navController)) { + final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToFavoritesFragment(); + navController.navigate(navDirections); + } + return true; + })); + } screen.addPreference(getDivider(context)); screen.addPreference(getPreference(R.string.action_settings, R.drawable.ic_outline_settings_24, preference -> { diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/NotificationsPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/NotificationsPreferencesFragment.java index 72f69f79..50d284e7 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/NotificationsPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/NotificationsPreferencesFragment.java @@ -15,7 +15,7 @@ public class NotificationsPreferencesFragment extends BasePreferencesFragment { final Context context = getContext(); if (context == null) return; screen.addPreference(getActivityNotificationsPreference(context)); - screen.addPreference(getDMNotificationsPreference(context)); + // screen.addPreference(getDMNotificationsPreference(context)); } private Preference getActivityNotificationsPreference(@NonNull final Context context) { diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.java b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.java index 3482cf7f..5287a1a5 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.java @@ -6,4 +6,5 @@ public final class PreferenceKeys { public static final String PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT = "enable_dm_auto_refresh_freq_unit"; public static final String PREF_ENABLE_DM_AUTO_REFRESH_FREQ_NUMBER = "enable_dm_auto_refresh_freq_number"; public static final String PREF_ENABLE_SENTRY = "enable_sentry"; + public static final String PREF_TAB_ORDER = "tab_order"; } diff --git a/app/src/main/java/awais/instagrabber/models/Tab.java b/app/src/main/java/awais/instagrabber/models/Tab.java new file mode 100644 index 00000000..e4e608be --- /dev/null +++ b/app/src/main/java/awais/instagrabber/models/Tab.java @@ -0,0 +1,110 @@ +package awais.instagrabber.models; + +import androidx.annotation.DrawableRes; +import androidx.annotation.IdRes; +import androidx.annotation.NavigationRes; +import androidx.annotation.NonNull; + +import java.util.Objects; + +public class Tab { + private final int iconResId; + private final String title; + private final boolean removable; + + /** + * This is name part of the navigation resource + * eg: @navigation/graphName + */ + private final String graphName; + + /** + * This is the actual resource id of the navigation resource (R.navigation.graphName = navigationResId) + */ + private final int navigationResId; + + /** + * This is the resource id of the root navigation tag of the navigation resource. + *

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

So this field would equal to the value of R.id.direct_messages_nav_graph + */ + private final int navigationRootId; + + /** + * This is the start destination of the nav graph + */ + private final int startDestinationFragmentId; + + public Tab(@DrawableRes final int iconResId, + @NonNull final String title, + final boolean removable, + @NonNull final String graphName, + @NavigationRes final int navigationResId, + @IdRes final int navigationRootId, + @IdRes final int startDestinationFragmentId) { + this.iconResId = iconResId; + this.title = title; + this.removable = removable; + this.graphName = graphName; + this.navigationResId = navigationResId; + this.navigationRootId = navigationRootId; + this.startDestinationFragmentId = startDestinationFragmentId; + } + + public int getIconResId() { + return iconResId; + } + + public String getTitle() { + return title; + } + + public boolean isRemovable() { + return removable; + } + + public String getGraphName() { + return graphName; + } + + public int getNavigationResId() { + return navigationResId; + } + + public int getNavigationRootId() { + return navigationRootId; + } + + public int getStartDestinationFragmentId() { + return startDestinationFragmentId; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final Tab tab = (Tab) o; + return iconResId == tab.iconResId && + removable == tab.removable && + navigationResId == tab.navigationResId && + navigationRootId == tab.navigationRootId && + startDestinationFragmentId == tab.startDestinationFragmentId && + Objects.equals(title, tab.title) && + Objects.equals(graphName, tab.graphName); + } + + @Override + public int hashCode() { + return Objects.hash(iconResId, title, removable, graphName, navigationResId, navigationRootId, startDestinationFragmentId); + } + + @NonNull + @Override + public String toString() { + return "Tab{" + + "title='" + title + '\'' + + ", removable=" + removable + + ", graphName='" + graphName + '\'' + + '}'; + } +} diff --git a/app/src/main/java/awais/instagrabber/models/enums/DirectItemType.java b/app/src/main/java/awais/instagrabber/models/enums/DirectItemType.java index 8f6f5ceb..83d655f2 100755 --- a/app/src/main/java/awais/instagrabber/models/enums/DirectItemType.java +++ b/app/src/main/java/awais/instagrabber/models/enums/DirectItemType.java @@ -7,6 +7,7 @@ import java.util.HashMap; import java.util.Map; public enum DirectItemType implements Serializable { + UNKNOWN(0), @SerializedName("text") TEXT(1), @SerializedName("like") @@ -40,7 +41,9 @@ public enum DirectItemType implements Serializable { @SerializedName("felix_share") FELIX_SHARE(16), // media_share but igtv @SerializedName("location") - LOCATION(17); + LOCATION(17), + @SerializedName("xma") + XMA(18); // self avatar stickers private final int id; private static final Map map = new HashMap<>(); @@ -60,6 +63,7 @@ public enum DirectItemType implements Serializable { } public static DirectItemType valueOf(final int id) { + if (!map.containsKey(id)) return DirectItemType.UNKNOWN; return map.get(id); } diff --git a/app/src/main/java/awais/instagrabber/repositories/SearchRepository.java b/app/src/main/java/awais/instagrabber/repositories/SearchRepository.java index 7dda390d..148e8f43 100644 --- a/app/src/main/java/awais/instagrabber/repositories/SearchRepository.java +++ b/app/src/main/java/awais/instagrabber/repositories/SearchRepository.java @@ -3,7 +3,6 @@ package awais.instagrabber.repositories; import java.util.Map; import awais.instagrabber.repositories.responses.search.SearchResponse; - import retrofit2.Call; import retrofit2.http.GET; import retrofit2.http.QueryMap; diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItem.java b/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItem.java index 1845c61a..52ad8818 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItem.java +++ b/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItem.java @@ -41,6 +41,7 @@ public class DirectItem implements Cloneable, Serializable { private final DirectItem repliedToMessage; private final DirectItemVoiceMedia voiceMedia; private final Location location; + private final DirectItemXma xma; private final int hideInThread; private Date date; private boolean isPending; @@ -72,6 +73,7 @@ public class DirectItem implements Cloneable, Serializable { final DirectItem repliedToMessage, final DirectItemVoiceMedia voiceMedia, final Location location, + final DirectItemXma xma, final int hideInThread, final boolean showForwardAttribution) { this.itemId = itemId; @@ -99,6 +101,7 @@ public class DirectItem implements Cloneable, Serializable { this.repliedToMessage = repliedToMessage; this.voiceMedia = voiceMedia; this.location = location; + this.xma = xma; this.hideInThread = hideInThread; this.showForwardAttribution = showForwardAttribution; } @@ -208,6 +211,10 @@ public class DirectItem implements Cloneable, Serializable { return location; } + public DirectItemXma getXma() { + return xma; + } + public int getHideInThread() { return hideInThread; } @@ -286,6 +293,7 @@ public class DirectItem implements Cloneable, Serializable { Objects.equals(repliedToMessage, that.repliedToMessage) && Objects.equals(voiceMedia, that.voiceMedia) && Objects.equals(location, that.location) && + Objects.equals(xma, that.xma) && Objects.equals(date, that.date); } @@ -294,6 +302,6 @@ public class DirectItem implements Cloneable, Serializable { return Objects .hash(itemId, userId, timestamp, itemType, text, like, link, clientContext, reelShare, storyShare, mediaShare, profile, placeholder, media, previewMedias, actionLog, videoCallEvent, clip, felixShare, visualMedia, animatedMedia, reactions, repliedToMessage, - voiceMedia, location, hideInThread, date, isPending, showForwardAttribution); + voiceMedia, location, xma, hideInThread, date, isPending, showForwardAttribution); } } diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemXma.java b/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemXma.java new file mode 100644 index 00000000..518e6efc --- /dev/null +++ b/app/src/main/java/awais/instagrabber/repositories/responses/directmessages/DirectItemXma.java @@ -0,0 +1,104 @@ +package awais.instagrabber.repositories.responses.directmessages; + +import androidx.annotation.NonNull; + +import java.io.Serializable; +import java.util.Objects; + +public class DirectItemXma { + private final XmaUrlInfo previewUrlInfo; + private final XmaUrlInfo playableUrlInfo; + + public DirectItemXma(final XmaUrlInfo previewUrlInfo, final XmaUrlInfo playableUrlInfo) { + this.previewUrlInfo = previewUrlInfo; + this.playableUrlInfo = playableUrlInfo; + } + + public XmaUrlInfo getPreviewUrlInfo() { + return previewUrlInfo; + } + + public XmaUrlInfo getPlayableUrlInfo() { + return playableUrlInfo; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final DirectItemXma that = (DirectItemXma) o; + return Objects.equals(previewUrlInfo, that.previewUrlInfo) && + Objects.equals(playableUrlInfo, that.playableUrlInfo); + } + + @Override + public int hashCode() { + return Objects.hash(previewUrlInfo, playableUrlInfo); + } + + @NonNull + @Override + public String toString() { + return "DirectItemXma{" + + "previewUrlInfo=" + previewUrlInfo + + ", playableUrlInfo=" + playableUrlInfo + + '}'; + } + + public static class XmaUrlInfo implements Serializable { + private final String url; + private final long urlExpirationTimestampUs; + private final int width; + private final int height; + + public XmaUrlInfo(final String url, final long urlExpirationTimestampUs, final int width, final int height) { + this.url = url; + this.urlExpirationTimestampUs = urlExpirationTimestampUs; + this.width = width; + this.height = height; + } + + public String getUrl() { + return url; + } + + public long getUrlExpirationTimestampUs() { + return urlExpirationTimestampUs; + } + + public int getWidth() { + return width; + } + + public int getHeight() { + return height; + } + + @Override + public boolean equals(final Object o) { + if (this == o) return true; + if (o == null || getClass() != o.getClass()) return false; + final XmaUrlInfo that = (XmaUrlInfo) o; + return urlExpirationTimestampUs == that.urlExpirationTimestampUs && + width == that.width && + height == that.height && + Objects.equals(url, that.url); + } + + @Override + public int hashCode() { + return Objects.hash(url, urlExpirationTimestampUs, width, height); + } + + @NonNull + @Override + public String toString() { + return "XmaUrlInfo{" + + "url='" + url + '\'' + + ", urlExpirationTimestampUs=" + urlExpirationTimestampUs + + ", width=" + width + + ", height=" + height + + '}'; + } + } +} diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/notification/NotificationCounts.java b/app/src/main/java/awais/instagrabber/repositories/responses/notification/NotificationCounts.java index 10074f70..dfad97e0 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/notification/NotificationCounts.java +++ b/app/src/main/java/awais/instagrabber/repositories/responses/notification/NotificationCounts.java @@ -1,7 +1,5 @@ package awais.instagrabber.repositories.responses.notification; -import androidx.annotation.NonNull; - public class NotificationCounts { private final int commentLikes; private final int usertags; diff --git a/app/src/main/java/awais/instagrabber/repositories/responses/search/SearchResponse.java b/app/src/main/java/awais/instagrabber/repositories/responses/search/SearchResponse.java index 04db0286..d3f0e143 100644 --- a/app/src/main/java/awais/instagrabber/repositories/responses/search/SearchResponse.java +++ b/app/src/main/java/awais/instagrabber/repositories/responses/search/SearchResponse.java @@ -2,8 +2,6 @@ package awais.instagrabber.repositories.responses.search; import java.util.List; -import awais.instagrabber.repositories.responses.User; - public class SearchResponse { // app private final List list; diff --git a/app/src/main/java/awais/instagrabber/utils/DMUtils.java b/app/src/main/java/awais/instagrabber/utils/DMUtils.java index 674e8722..12aa2e07 100644 --- a/app/src/main/java/awais/instagrabber/utils/DMUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/DMUtils.java @@ -44,9 +44,9 @@ public final class DMUtils { public static boolean isRead(@NonNull final DirectThread thread) { final boolean read; -// if (thread.getDirectStory() != null) { -// return false; -// } + // if (thread.getDirectStory() != null) { + // return false; + // } final DirectItem item = thread.getFirstDirectItem(); final long viewerId = thread.getViewerId(); if (item != null && item.getUserId() == viewerId) { @@ -188,6 +188,9 @@ public final class DMUtils { break; } break; + case XMA: + subtitle = resources.getString(R.string.dms_inbox_shared_sticker, username != null ? username : ""); + break; default: message = resources.getString(R.string.dms_inbox_raven_message_unknown); } @@ -213,7 +216,14 @@ public final class DMUtils { .filter(Objects::nonNull) .filter(user -> user.getPk() == userId) .findFirst(); - return senderOptional.map(User::getUsername).orElse(null); + return senderOptional.map(user -> { + // return full name for fb users + final String username = user.getUsername(); + if (TextUtils.isEmpty(username)) { + return user.getFullName(); + } + return username; + }).orElse(null); } public static String getMediaSpecificSubtitle(final String username, final Resources resources, final MediaItemType mediaType) { diff --git a/app/src/main/java/awais/instagrabber/utils/DirectItemFactory.java b/app/src/main/java/awais/instagrabber/utils/DirectItemFactory.java index 85e6f9ae..3ef9ddee 100644 --- a/app/src/main/java/awais/instagrabber/utils/DirectItemFactory.java +++ b/app/src/main/java/awais/instagrabber/utils/DirectItemFactory.java @@ -51,6 +51,7 @@ public final class DirectItemFactory { repliedToMessage, null, null, + null, 0, false); } @@ -132,6 +133,7 @@ public final class DirectItemFactory { null, null, null, + null, 0, false); } @@ -213,6 +215,7 @@ public final class DirectItemFactory { null, voiceMedia, null, + null, 0, false); } @@ -253,6 +256,7 @@ public final class DirectItemFactory { null, null, null, + null, 0, false ); diff --git a/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java b/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java index ca5ca21c..a0a2e190 100755 --- a/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/ExportImportUtils.java @@ -156,12 +156,21 @@ public final class ExportImportUtils { query, favoriteType, favsObject.optString("s"), - favoriteType == FavoriteType.HASHTAG ? null - : favsObject.optString("pic_url"), + favoriteType == FavoriteType.USER ? favsObject.optString("pic_url") : null, new Date(favsObject.getLong("d"))); // Log.d(TAG, "importJson: favoriteModel: " + favoriteModel); - FavoriteRepository.getInstance(FavoriteDataSource.getInstance(context)) - .insertOrUpdateFavorite(favorite, null); + final FavoriteRepository favRepo = FavoriteRepository.getInstance(FavoriteDataSource.getInstance(context)); + favRepo.getFavorite(query, favoriteType, new RepositoryCallback() { + @Override + public void onSuccess(final Favorite result) { + // local has priority since it's more frequently updated + } + + @Override + public void onDataNotAvailable() { + favRepo.insertOrUpdateFavorite(favorite, null); + } + }); } } diff --git a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java index 1ff49767..9ae6cfd4 100755 --- a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java +++ b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.java @@ -16,6 +16,7 @@ import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_D import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT; import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_DM_NOTIFICATIONS; import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_ENABLE_SENTRY; +import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_TAB_ORDER; import static awais.instagrabber.utils.Constants.APP_LANGUAGE; import static awais.instagrabber.utils.Constants.APP_THEME; import static awais.instagrabber.utils.Constants.APP_UA; @@ -37,12 +38,12 @@ import static awais.instagrabber.utils.Constants.DOWNLOAD_USER_FOLDER; import static awais.instagrabber.utils.Constants.FLAG_SECURE; import static awais.instagrabber.utils.Constants.FOLDER_PATH; import static awais.instagrabber.utils.Constants.FOLDER_SAVE_TO; +import static awais.instagrabber.utils.Constants.KEYWORD_FILTERS; import static awais.instagrabber.utils.Constants.MARK_AS_SEEN; import static awais.instagrabber.utils.Constants.MUTED_VIDEOS; import static awais.instagrabber.utils.Constants.PREF_DARK_THEME; import static awais.instagrabber.utils.Constants.PREF_EMOJI_VARIANTS; import static awais.instagrabber.utils.Constants.PREF_HASHTAG_POSTS_LAYOUT; -import static awais.instagrabber.utils.Constants.KEYWORD_FILTERS; import static awais.instagrabber.utils.Constants.PREF_LIGHT_THEME; import static awais.instagrabber.utils.Constants.PREF_LIKED_POSTS_LAYOUT; import static awais.instagrabber.utils.Constants.PREF_LOCATION_POSTS_LAYOUT; @@ -155,7 +156,7 @@ public final class SettingsHelper { CUSTOM_DATE_TIME_FORMAT, DEVICE_UUID, SKIPPED_VERSION, DEFAULT_TAB, PREF_DARK_THEME, PREF_LIGHT_THEME, PREF_POSTS_LAYOUT, PREF_PROFILE_POSTS_LAYOUT, PREF_TOPIC_POSTS_LAYOUT, PREF_HASHTAG_POSTS_LAYOUT, PREF_LOCATION_POSTS_LAYOUT, PREF_LIKED_POSTS_LAYOUT, PREF_TAGGED_POSTS_LAYOUT, PREF_SAVED_POSTS_LAYOUT, - STORY_SORT, PREF_EMOJI_VARIANTS, PREF_REACTIONS, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT}) + STORY_SORT, PREF_EMOJI_VARIANTS, PREF_REACTIONS, PREF_ENABLE_DM_AUTO_REFRESH_FREQ_UNIT, PREF_TAB_ORDER}) public @interface StringSettings {} @StringDef({DOWNLOAD_USER_FOLDER, FOLDER_SAVE_TO, AUTOPLAY_VIDEOS, SHOW_QUICK_ACCESS_DIALOG, MUTED_VIDEOS, diff --git a/app/src/main/java/awais/instagrabber/utils/TextUtils.java b/app/src/main/java/awais/instagrabber/utils/TextUtils.java index 64eddd22..448c2a90 100644 --- a/app/src/main/java/awais/instagrabber/utils/TextUtils.java +++ b/app/src/main/java/awais/instagrabber/utils/TextUtils.java @@ -2,8 +2,6 @@ package awais.instagrabber.utils; import android.content.Context; import android.text.SpannableString; -import android.text.SpannableStringBuilder; -import android.text.Spanned; import android.text.format.DateFormat; import android.text.format.DateUtils; import android.text.style.URLSpan; @@ -11,17 +9,12 @@ import android.util.Patterns; import androidx.annotation.NonNull; -import java.io.UnsupportedEncodingException; -import java.net.URLEncoder; import java.util.ArrayList; import java.util.Collections; import java.util.Date; import java.util.List; import java.util.Locale; import java.util.regex.Matcher; -import java.util.regex.Pattern; - -import awais.instagrabber.customviews.CommentMentionClickSpan; public final class TextUtils { // extracted from String class diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index bf716e5d..6f966c33 100644 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/app/src/main/java/awais/instagrabber/utils/Utils.java @@ -8,6 +8,7 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.res.Resources; +import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Rect; import android.graphics.drawable.Drawable; @@ -30,6 +31,7 @@ import android.webkit.MimeTypeMap; import android.widget.Toast; import androidx.annotation.DrawableRes; +import androidx.annotation.IdRes; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; @@ -40,6 +42,8 @@ import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat; import com.google.android.exoplayer2.database.ExoDatabaseProvider; import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor; import com.google.android.exoplayer2.upstream.cache.SimpleCache; +import com.google.common.collect.ImmutableList; +import com.google.common.collect.Ordering; import com.google.common.io.Files; import org.json.JSONObject; @@ -47,22 +51,25 @@ import org.json.JSONObject; import java.io.File; import java.lang.reflect.Field; import java.text.SimpleDateFormat; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; import java.util.HashMap; +import java.util.List; import java.util.Map; - -//import javax.crypto.Mac; -//import javax.crypto.spec.SecretKeySpec; +import java.util.stream.Collectors; import awais.instagrabber.R; +import awais.instagrabber.fragments.settings.PreferenceKeys; import awais.instagrabber.models.PostsLayoutPreferences; +import awais.instagrabber.models.Tab; import awais.instagrabber.models.enums.FavoriteType; -//import awaisomereport.LogCollector; public final class Utils { private static final String TAG = "Utils"; private static final int VIDEO_CACHE_MAX_BYTES = 10 * 1024 * 1024; -// public static LogCollector logCollector; + // public static LogCollector logCollector; public static SettingsHelper settingsHelper; public static boolean sessionVolumeFull = false; public static final MimeTypeMap mimeTypeMap = MimeTypeMap.getSingleton(); @@ -74,6 +81,7 @@ public final class Utils { private static int actionBarHeight; public static Handler applicationHandler; public static String cacheDir; + public static String tabOrderString; private static int defaultStatusBarColor; public static int convertDpToPx(final float dp) { @@ -93,34 +101,34 @@ public final class Utils { } public static Map sign(final Map form) { -// final String signed = sign(Constants.SIGNATURE_KEY, new JSONObject(form).toString()); -// if (signed == null) { -// return null; -// } + // final String signed = sign(Constants.SIGNATURE_KEY, new JSONObject(form).toString()); + // if (signed == null) { + // return null; + // } final Map map = new HashMap<>(); -// map.put("ig_sig_key_version", Constants.SIGNATURE_VERSION); -// map.put("signed_body", signed); + // map.put("ig_sig_key_version", Constants.SIGNATURE_VERSION); + // map.put("signed_body", signed); map.put("signed_body", "SIGNATURE." + new JSONObject(form).toString()); return map; } -// public static String sign(final String key, final String message) { -// try { -// final Mac hasher = Mac.getInstance("HmacSHA256"); -// hasher.init(new SecretKeySpec(key.getBytes(), "HmacSHA256")); -// byte[] hash = hasher.doFinal(message.getBytes()); -// final StringBuilder hexString = new StringBuilder(); -// for (byte b : hash) { -// final String hex = Integer.toHexString(0xff & b); -// if (hex.length() == 1) hexString.append('0'); -// hexString.append(hex); -// } -// return hexString.toString() + "." + message; -// } catch (Exception e) { -// Log.e(TAG, "Error signing", e); -// return null; -// } -// } + // public static String sign(final String key, final String message) { + // try { + // final Mac hasher = Mac.getInstance("HmacSHA256"); + // hasher.init(new SecretKeySpec(key.getBytes(), "HmacSHA256")); + // byte[] hash = hasher.doFinal(message.getBytes()); + // final StringBuilder hexString = new StringBuilder(); + // for (byte b : hash) { + // final String hex = Integer.toHexString(0xff & b); + // if (hex.length() == 1) hexString.append('0'); + // hexString.append(hex); + // } + // return hexString.toString() + "." + message; + // } catch (Exception e) { + // Log.e(TAG, "Error signing", e); + // return null; + // } + // } public static String getMimeType(@NonNull final Uri uri, final ContentResolver contentResolver) { String mimeType; @@ -371,4 +379,116 @@ public final class Utils { if (window == null) return; window.clearFlags(WindowManager.LayoutParams.FLAG_KEEP_SCREEN_ON); } + + public static void moveItem(int sourceIndex, int targetIndex, List list) { + if (sourceIndex <= targetIndex) { + Collections.rotate(list.subList(sourceIndex, targetIndex + 1), -1); + } else { + Collections.rotate(list.subList(targetIndex, sourceIndex + 1), 1); + } + } + + private static final List NON_REMOVABLE_NAV_ROOT_IDS = ImmutableList.of(R.id.profile_nav_graph, R.id.more_nav_graph); + + @NonNull + public static Pair, List> getNavTabList(@NonNull final Context context) { + final Resources resources = context.getResources(); + final String[] titleArray = resources.getStringArray(R.array.main_nav_titles); + + TypedArray typedArray = resources.obtainTypedArray(R.array.main_nav_graphs); + int length = typedArray.length(); + final String[] navGraphNames = new String[length]; + final int[] navigationResIds = new int[length]; + for (int i = 0; i < length; i++) { + final int resourceId = typedArray.getResourceId(i, 0); + if (resourceId == 0) continue; + navigationResIds[i] = resourceId; + navGraphNames[i] = resources.getResourceEntryName(resourceId); + } + typedArray.recycle(); + + typedArray = resources.obtainTypedArray(R.array.main_nav_graph_root_ids); + length = typedArray.length(); + final int[] navRootIds = new int[length]; + for (int i = 0; i < length; i++) { + final int resourceId = typedArray.getResourceId(i, 0); + if (resourceId == 0) continue; + navRootIds[i] = resourceId; + } + typedArray.recycle(); + + typedArray = resources.obtainTypedArray(R.array.main_nav_drawables); + length = typedArray.length(); + final int[] iconIds = new int[length]; + for (int i = 0; i < length; i++) { + final int resourceId = typedArray.getResourceId(i, 0); + if (resourceId == 0) continue; + iconIds[i] = resourceId; + } + typedArray.recycle(); + + typedArray = resources.obtainTypedArray(R.array.main_nav_start_dest_frag_ids); + length = typedArray.length(); + final int[] startDestFragIds = new int[length]; + for (int i = 0; i < length; i++) { + final int resourceId = typedArray.getResourceId(i, 0); + if (resourceId == 0) continue; + startDestFragIds[i] = resourceId; + } + typedArray.recycle(); + + final List currentOrderGraphNames = getCurrentOrderOfGraphNamesFromPref(navGraphNames); + + if (titleArray.length != iconIds.length || titleArray.length != navGraphNames.length) { + throw new RuntimeException(String.format("Array lengths don't match!: titleArray%s, navGraphNames: %s, iconIds: %s", + Arrays.toString(titleArray), Arrays.toString(navGraphNames), Arrays.toString(iconIds))); + } + final List tabs = new ArrayList<>(); + final List otherTabs = new ArrayList<>(); // Will contain tabs not in current list + for (int i = 0; i < length; i++) { + final String navGraphName = navGraphNames[i]; + final int navRootId = navRootIds[i]; + final Tab tab = new Tab(iconIds[i], + titleArray[i], + !NON_REMOVABLE_NAV_ROOT_IDS.contains(navRootId), + navGraphName, + navigationResIds[i], + navRootId, + startDestFragIds[i]); + if (!currentOrderGraphNames.contains(navGraphName)) { + otherTabs.add(tab); + continue; + } + tabs.add(tab); + } + Collections.sort(tabs, Ordering.explicit(currentOrderGraphNames).onResultOf(tab -> { + if (tab == null) return null; + return tab.getGraphName(); + })); + return new Pair<>(tabs, otherTabs); + } + + @NonNull + private static List getCurrentOrderOfGraphNamesFromPref(@NonNull final String[] navGraphNames) { + tabOrderString = settingsHelper.getString(PreferenceKeys.PREF_TAB_ORDER); + final List navGraphNameList = Arrays.asList(navGraphNames); + if (TextUtils.isEmpty(tabOrderString)) { + // Use top 5 entries for default list + return navGraphNameList.subList(0, 5); + } + // Make sure that the list from preference does not contain any invalid values + final List orderGraphNames = Arrays.stream(tabOrderString.split(",")) + .filter(s -> !TextUtils.isEmpty(s)) + .filter(navGraphNameList::contains) + .collect(Collectors.toList()); + if (orderGraphNames.isEmpty()) { + // Use top 5 entries for default list + return navGraphNameList.subList(0, 5); + } + return orderGraphNames; + } + + public static boolean isNavRootInCurrentTabs(final String navRootString) { + return tabOrderString.contains(navRootString); + } } diff --git a/app/src/main/java/awais/instagrabber/viewmodels/AppStateViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/AppStateViewModel.java index dcea12ed..ab1cbabb 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/AppStateViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/AppStateViewModel.java @@ -1,9 +1,12 @@ package awais.instagrabber.viewmodels; import android.app.Application; +import android.util.Log; import androidx.annotation.NonNull; import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; import awais.instagrabber.db.datasources.AccountDataSource; import awais.instagrabber.db.repositories.AccountRepository; @@ -21,8 +24,8 @@ public class AppStateViewModel extends AndroidViewModel { private final String cookie; private final boolean isLoggedIn; + private final MutableLiveData currentUser = new MutableLiveData<>(); - private User currentUser; private AccountRepository accountRepository; private UserService userService; @@ -38,6 +41,10 @@ public class AppStateViewModel extends AndroidViewModel { } public User getCurrentUser() { + return currentUser.getValue(); + } + + public LiveData getCurrentUserLiveData() { return currentUser; } @@ -46,11 +53,13 @@ public class AppStateViewModel extends AndroidViewModel { userService.getUserInfo(uid, new ServiceCallback() { @Override public void onSuccess(final User user) { - currentUser = user; + currentUser.postValue(user); } @Override - public void onFailure(final Throwable t) {} + public void onFailure(final Throwable t) { + Log.e(TAG, "onFailure: ", t); + } }); } } diff --git a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java index 1ca3c263..3329d6c8 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/DirectThreadViewModel.java @@ -241,6 +241,7 @@ public class DirectThreadViewModel extends AndroidViewModel { if (users != null && users.getValue() != null) { final List userList = users.getValue(); match = userList.stream() + .filter(Objects::nonNull) .filter(user -> user.getPk() == userId) .findFirst() .orElse(null); @@ -250,6 +251,7 @@ public class DirectThreadViewModel extends AndroidViewModel { if (leftUsers != null && leftUsers.getValue() != null) { final List userList = leftUsers.getValue(); match = userList.stream() + .filter(Objects::nonNull) .filter(user -> user.getPk() == userId) .findFirst() .orElse(null); diff --git a/app/src/main/java/awais/instagrabber/webservices/FeedService.java b/app/src/main/java/awais/instagrabber/webservices/FeedService.java index 3496f8b7..3b4baddd 100644 --- a/app/src/main/java/awais/instagrabber/webservices/FeedService.java +++ b/app/src/main/java/awais/instagrabber/webservices/FeedService.java @@ -12,12 +12,12 @@ import java.util.Map; import java.util.UUID; import awais.instagrabber.repositories.FeedRepository; +import awais.instagrabber.repositories.responses.Media; +import awais.instagrabber.repositories.responses.PostsFetchResponse; import awais.instagrabber.repositories.responses.feed.EndOfFeedDemarcator; import awais.instagrabber.repositories.responses.feed.EndOfFeedGroup; import awais.instagrabber.repositories.responses.feed.EndOfFeedGroupSet; import awais.instagrabber.repositories.responses.feed.FeedFetchResponse; -import awais.instagrabber.repositories.responses.Media; -import awais.instagrabber.repositories.responses.PostsFetchResponse; import awais.instagrabber.utils.TextUtils; import retrofit2.Call; import retrofit2.Callback; diff --git a/app/src/main/java/awais/instagrabber/webservices/NewsService.java b/app/src/main/java/awais/instagrabber/webservices/NewsService.java index 5eec9e8c..63fede99 100644 --- a/app/src/main/java/awais/instagrabber/webservices/NewsService.java +++ b/app/src/main/java/awais/instagrabber/webservices/NewsService.java @@ -12,12 +12,12 @@ import java.util.stream.Collectors; import awais.instagrabber.repositories.NewsRepository; import awais.instagrabber.repositories.responses.AymlResponse; import awais.instagrabber.repositories.responses.AymlUser; -import awais.instagrabber.repositories.responses.notification.NotificationCounts; -import awais.instagrabber.repositories.responses.UserSearchResponse; import awais.instagrabber.repositories.responses.NewsInboxResponse; +import awais.instagrabber.repositories.responses.User; +import awais.instagrabber.repositories.responses.UserSearchResponse; import awais.instagrabber.repositories.responses.notification.Notification; import awais.instagrabber.repositories.responses.notification.NotificationArgs; -import awais.instagrabber.repositories.responses.User; +import awais.instagrabber.repositories.responses.notification.NotificationCounts; import awais.instagrabber.utils.Constants; import retrofit2.Call; import retrofit2.Callback; diff --git a/app/src/main/java/awais/instagrabber/webservices/SearchService.java b/app/src/main/java/awais/instagrabber/webservices/SearchService.java index 39b5bd65..c9efa68f 100644 --- a/app/src/main/java/awais/instagrabber/webservices/SearchService.java +++ b/app/src/main/java/awais/instagrabber/webservices/SearchService.java @@ -1,15 +1,10 @@ package awais.instagrabber.webservices; -import androidx.annotation.NonNull; - import com.google.common.collect.ImmutableMap; import awais.instagrabber.repositories.SearchRepository; import awais.instagrabber.repositories.responses.search.SearchResponse; -import awais.instagrabber.utils.TextUtils; import retrofit2.Call; -import retrofit2.Callback; -import retrofit2.Response; import retrofit2.Retrofit; public class SearchService extends BaseService { diff --git a/app/src/main/res/drawable/ic_discover.xml b/app/src/main/res/drawable/ic_discover.xml deleted file mode 100755 index 7a1ab400..00000000 --- a/app/src/main/res/drawable/ic_discover.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_explore_24.xml b/app/src/main/res/drawable/ic_explore_24.xml new file mode 100644 index 00000000..0ac168a2 --- /dev/null +++ b/app/src/main/res/drawable/ic_explore_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_feed.xml b/app/src/main/res/drawable/ic_feed.xml deleted file mode 100755 index c163a23d..00000000 --- a/app/src/main/res/drawable/ic_feed.xml +++ /dev/null @@ -1,9 +0,0 @@ - - - diff --git a/app/src/main/res/drawable/ic_home_24.xml b/app/src/main/res/drawable/ic_home_24.xml new file mode 100644 index 00000000..3a4c7dac --- /dev/null +++ b/app/src/main/res/drawable/ic_home_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_message_24.xml b/app/src/main/res/drawable/ic_message_24.xml new file mode 100644 index 00000000..a7adce5a --- /dev/null +++ b/app/src/main/res/drawable/ic_message_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_person_24.xml b/app/src/main/res/drawable/ic_person_24.xml new file mode 100644 index 00000000..6bdced2d --- /dev/null +++ b/app/src/main/res/drawable/ic_person_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_round_add_circle_24.xml b/app/src/main/res/drawable/ic_round_add_circle_24.xml new file mode 100644 index 00000000..1906afe5 --- /dev/null +++ b/app/src/main/res/drawable/ic_round_add_circle_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_round_drag_handle_24.xml b/app/src/main/res/drawable/ic_round_drag_handle_24.xml new file mode 100644 index 00000000..3f4f79ca --- /dev/null +++ b/app/src/main/res/drawable/ic_round_drag_handle_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/drawable/ic_round_remove_circle_24.xml b/app/src/main/res/drawable/ic_round_remove_circle_24.xml new file mode 100644 index 00000000..68d85995 --- /dev/null +++ b/app/src/main/res/drawable/ic_round_remove_circle_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 866770aa..97bcb0bb 100755 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -47,7 +47,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" - app:labelVisibilityMode="labeled" - app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior" - app:menu="@menu/main_bottom_navigation_menu" /> + app:labelVisibilityMode="auto" + app:layout_behavior="@string/hide_bottom_view_on_scroll_behavior" /> \ No newline at end of file diff --git a/app/src/main/res/layout/item_tab_order_pref.xml b/app/src/main/res/layout/item_tab_order_pref.xml new file mode 100644 index 00000000..8c2045c9 --- /dev/null +++ b/app/src/main/res/layout/item_tab_order_pref.xml @@ -0,0 +1,45 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/menu/logged_out_bottom_navigation_menu.xml b/app/src/main/res/menu/logged_out_bottom_navigation_menu.xml deleted file mode 100644 index e93193a0..00000000 --- a/app/src/main/res/menu/logged_out_bottom_navigation_menu.xml +++ /dev/null @@ -1,12 +0,0 @@ - -

- - - - \ No newline at end of file diff --git a/app/src/main/res/menu/main_bottom_navigation_menu.xml b/app/src/main/res/menu/main_bottom_navigation_menu.xml deleted file mode 100644 index 6e981c7c..00000000 --- a/app/src/main/res/menu/main_bottom_navigation_menu.xml +++ /dev/null @@ -1,30 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/navigation/favorites_nav_graph.xml b/app/src/main/res/navigation/favorites_nav_graph.xml new file mode 100644 index 00000000..1a288bf1 --- /dev/null +++ b/app/src/main/res/navigation/favorites_nav_graph.xml @@ -0,0 +1,43 @@ + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/more_nav_graph.xml b/app/src/main/res/navigation/more_nav_graph.xml index 84ae9423..0274390e 100644 --- a/app/src/main/res/navigation/more_nav_graph.xml +++ b/app/src/main/res/navigation/more_nav_graph.xml @@ -11,6 +11,7 @@ + + app:nullable="false" + android:defaultValue="notif"/> HH:mm:ss H:mm:ss - + + @id/direct_messages_nav_graph + @id/feed_nav_graph + @id/profile_nav_graph + @id/discover_nav_graph + @id/more_nav_graph + + @id/favorites_nav_graph + @id/notification_viewer_nav_graph + + + @navigation/direct_messages_nav_graph @navigation/feed_nav_graph @navigation/profile_nav_graph @navigation/discover_nav_graph @navigation/more_nav_graph + @navigation/favorites_nav_graph + @navigation/notification_viewer_nav_graph - + + @string/title_dm @string/feed @string/profile @string/title_discover @string/more + @string/title_favorites + @string/title_notifications - - @navigation/profile_nav_graph - @navigation/more_nav_graph + + + @drawable/ic_message_24 + @drawable/ic_home_24 + @drawable/ic_person_24 + @drawable/ic_explore_24 + @drawable/ic_more_horiz_24 + @drawable/ic_star_24 + @drawable/ic_not_liked + + + + @id/directMessagesInboxFragment + @id/feedFragment + @id/profileFragment + @id/discoverFragment + @id/morePreferencesFragment + @id/favoritesFragment + @id/notificationsViewer + + + + + @string/light_white_theme @string/light_barinsta_theme diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index 7319700f..8f77fc7f 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -4,4 +4,5 @@ + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 20ab88c0..ab8c25f3 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -477,4 +477,10 @@ Select an email app to send crash logs Skip this update You\'re already on the latest version + Screen order + Other tabs + The tab order will be reflected on next launch + If saved, all DM related features will be disabled on next launch + Copy caption + Copy reply