Merge branch 'master' into per-flavor-update-check

This commit is contained in:
Austin Huang 2021-04-02 13:50:49 -04:00 committed by GitHub
commit 0c11053b93
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
67 changed files with 1755 additions and 389 deletions

View File

@ -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"

View File

@ -8,7 +8,6 @@ import android.content.ComponentName;
import android.content.Context;
import android.content.Intent;
import android.content.ServiceConnection;
import android.content.res.TypedArray;
import android.database.MatrixCursor;
import android.net.Uri;
import android.os.Build;
@ -24,6 +23,7 @@ import android.view.WindowManager;
import android.widget.AutoCompleteTextView;
import android.widget.Toast;
import androidx.annotation.IdRes;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
@ -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<Integer> SHOW_BOTTOM_VIEW_DESTINATIONS = Arrays.asList(
R.id.directMessagesInboxFragment,
R.id.feedFragment,
R.id.profileFragment,
R.id.discoverFragment,
R.id.morePreferencesFragment);
private static final Map<Integer, Integer> NAV_TO_MENU_ID_MAP = new HashMap<>();
private static final String FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex";
private static final String LAST_SELECT_NAV_MENU_ID = "lastSelectedNavMenuId";
private ActivityMainBinding binding;
private LiveData<NavController> currentNavControllerLiveData;
@ -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<BottomNavigationView> behavior;
private List<Tab> currentTabs;
private List<Integer> 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<NavBackStackEntry> 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<SearchItem> result = new ArrayList<SearchItem>();
final List<SearchItem> result = new ArrayList<>();
if (isLoggedIn) {
if (body.getList() != null) result.addAll(searchHash ? body.getList()
.stream()
.filter(i -> i.getUser() == null)
.collect(Collectors.toList()) : body.getList());
}
else {
if (body.getList() != null) {
result.addAll(searchHash ? body.getList()
.stream()
.filter(i -> i.getUser() == null)
.collect(Collectors.toList())
: body.getList());
}
} else {
if (body.getUsers() != null && !searchHash) result.addAll(body.getUsers());
if (body.getHashtags() != null) result.addAll(body.getHashtags());
if (body.getPlaces() != null) result.addAll(body.getPlaces());
@ -420,9 +429,10 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
@Override
public void onFailure(@NonNull final Call<SearchResponse> call,
Throwable t) {
if (!call.isCanceled() && t != null)
@NonNull Throwable t) {
if (!call.isCanceled()) {
Log.e(TAG, "Exception on search:", t);
}
}
};
@ -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<Integer> mainNavList = getMainNavList(main_nav_ids);
if (setDefaultFromSettings) {
final String defaultTabResNameString = settingsHelper.getString(Constants.DEFAULT_TAB);
try {
int navId = 0;
if (!TextUtils.isEmpty(defaultTabResNameString)) {
navId = getResources().getIdentifier(defaultTabResNameString, "navigation", getPackageName());
}
final int defaultNavId = navId <= 0 ? R.navigation.profile_nav_graph
: navId;
final int index = mainNavList.indexOf(defaultNavId);
if (index >= 0) firstFragmentGraphIndex = index;
setBottomNavSelectedItem(defaultNavId);
} catch (NumberFormatException e) {
Log.e(TAG, "Error parsing id", e);
}
private void setupBottomNavigationBar(final boolean setDefaultTabFromSettings) {
currentTabs = !isLoggedIn ? setupAnonBottomNav() : setupMainBottomNav();
final List<Integer> mainNavList = currentTabs.stream()
.map(Tab::getNavigationResId)
.collect(Collectors.toList());
showBottomViewDestinations = currentTabs.stream()
.map(Tab::getStartDestinationFragmentId)
.collect(Collectors.toList());
if (setDefaultTabFromSettings) {
setSelectedTab(currentTabs);
} else {
binding.bottomNavView.setSelectedItemId(lastSelectedNavMenuId);
}
final LiveData<NavController> navControllerLiveData = setupWithNavController(
binding.bottomNavView,
@ -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<Tab> tabs) {
final String defaultTabResNameString = settingsHelper.getString(Constants.DEFAULT_TAB);
try {
int navId = 0;
if (!TextUtils.isEmpty(defaultTabResNameString)) {
navId = getResources().getIdentifier(defaultTabResNameString, "navigation", getPackageName());
}
final int navGraph = isLoggedIn ? R.navigation.feed_nav_graph
: R.navigation.profile_nav_graph;
final int defaultNavId = navId <= 0 ? navGraph : navId;
int index = Iterators.indexOf(tabs.iterator(), tab -> {
if (tab == null) return false;
return tab.getNavigationResId() == defaultNavId;
});
if (index < 0 || index >= tabs.size()) index = 0;
firstFragmentGraphIndex = index;
setBottomNavSelectedTab(tabs.get(index));
} catch (Exception e) {
Log.e(TAG, "Error parsing id", e);
}
}
@NonNull
private List<Integer> getMainNavList(final int main_nav_ids) {
final TypedArray navIds = getResources().obtainTypedArray(main_nav_ids);
final List<Integer> mainNavList = new ArrayList<>(navIds.length());
final int length = navIds.length();
for (int i = 0; i < length; i++) {
final int resourceId = navIds.getResourceId(i, -1);
if (resourceId < 0) continue;
mainNavList.add(resourceId);
private List<Tab> setupAnonBottomNav() {
final int selectedItemId = binding.bottomNavView.getSelectedItemId();
final Tab profileTab = new Tab(R.drawable.ic_person_24,
getString(R.string.profile),
false,
"profile_nav_graph",
R.navigation.profile_nav_graph,
R.id.profile_nav_graph,
R.id.profileFragment);
final Tab moreTab = new Tab(R.drawable.ic_more_horiz_24,
getString(R.string.more),
false,
"more_nav_graph",
R.navigation.more_nav_graph,
R.id.more_nav_graph,
R.id.morePreferencesFragment);
final Menu menu = binding.bottomNavView.getMenu();
menu.clear();
menu.add(0, profileTab.getNavigationRootId(), 0, profileTab.getTitle()).setIcon(profileTab.getIconResId());
menu.add(0, moreTab.getNavigationRootId(), 0, moreTab.getTitle()).setIcon(moreTab.getIconResId());
if (selectedItemId != R.id.profile_nav_graph && selectedItemId != R.id.more_nav_graph) {
setBottomNavSelectedTab(profileTab);
}
navIds.recycle();
return mainNavList;
return ImmutableList.of(profileTab, moreTab);
}
private List<Tab> setupMainBottomNav() {
final Menu menu = binding.bottomNavView.getMenu();
menu.clear();
final List<Tab> navTabList = Utils.getNavTabList(this).first;
for (final Tab tab : navTabList) {
menu.add(0, tab.getNavigationRootId(), 0, tab.getTitle()).setIcon(tab.getIconResId());
}
return navTabList;
}
private void setBottomNavSelectedTab(@NonNull final Tab tab) {
binding.bottomNavView.setSelectedItemId(tab.getNavigationRootId());
}
private void setBottomNavSelectedTab(@SuppressWarnings("SameParameterValue") @IdRes final int navGraphRootId) {
binding.bottomNavView.setSelectedItemId(navGraphRootId);
}
// @NonNull
// private List<Integer> getMainNavList(final int main_nav_ids) {
// final TypedArray navIds = getResources().obtainTypedArray(main_nav_ids);
// final List<Integer> mainNavList = new ArrayList<>(navIds.length());
// final int length = navIds.length();
// for (int i = 0; i < length; i++) {
// final int resourceId = navIds.getResourceId(i, -1);
// if (resourceId < 0) continue;
// mainNavList.add(resourceId);
// }
// navIds.recycle();
// return mainNavList;
// }
private void setupNavigation(final Toolbar toolbar, final NavController navController) {
if (navController == null) return;
NavigationUI.setupWithNavController(toolbar, navController);
@ -564,7 +615,7 @@ public class MainActivity extends BaseLanguageActivity implements FragmentManage
final int destinationId = destination.getId();
@SuppressLint("RestrictedApi") final Deque<NavBackStackEntry> backStack = navController.getBackStack();
setupMenu(backStack.size(), destinationId);
final boolean contains = SHOW_BOTTOM_VIEW_DESTINATIONS.contains(destinationId);
final boolean contains = showBottomViewDestinations.contains(destinationId);
binding.bottomNavView.setVisibility(contains ? View.VISIBLE : View.GONE);
if (contains && behavior != null) {
behavior.slideUp(binding.bottomNavView);
@ -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<Tab> getCurrentTabs() {
return currentTabs;
}
// public boolean isNavRootInCurrentTabs(@IdRes final int navRootId) {
// return showBottomViewDestinations.stream().anyMatch(id -> id == navRootId);
// }
private void setNavBarDMUnreadCountBadge(final int unseenCount) {
final BadgeDrawable badge = binding.bottomNavView.getOrCreateBadge(R.id.direct_messages_nav_graph);
if (badge == null) return;
if (unseenCount == 0) {
badge.setVisible(false);
badge.clearNumber();
return;
}
if (badge.getVerticalOffset() != 10) {
badge.setVerticalOffset(10);
}
badge.setNumber(unseenCount);
badge.setVisible(true);
}
}

View File

@ -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<RecyclerView.
final LayoutDmRavenMediaBinding binding = LayoutDmRavenMediaBinding.inflate(layoutInflater, baseBinding.message, false);
return new DirectItemRavenMediaViewHolder(baseBinding, binding, currentUser, thread, callback);
}
case XMA: {
final LayoutDmAnimatedMediaBinding binding = LayoutDmAnimatedMediaBinding.inflate(layoutInflater, baseBinding.message, false);
return new DirectItemXmaViewHolder(baseBinding, binding, currentUser, thread, callback);
}
case UNKNOWN:
default: {
final LayoutDmTextBinding binding = LayoutDmTextBinding.inflate(layoutInflater, baseBinding.message, false);
return new DirectItemDefaultViewHolder(baseBinding, binding, currentUser, thread, callback);
@ -240,7 +247,11 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
if (itemOrHeader.isHeader()) {
return -1;
}
return itemOrHeader.item.getItemType().getId();
final DirectItemType itemType = itemOrHeader.item.getItemType();
if (itemType == null) {
return 0;
}
return itemType.getId();
}
@Override
@ -394,7 +405,7 @@ public final class DirectItemsAdapter extends RecyclerView.Adapter<RecyclerView.
void onReactionClick(DirectItem item, int position);
void onOptionSelect(DirectItem item, @IdRes int itemId);
void onOptionSelect(DirectItem item, @IdRes int itemId, final Function<DirectItem, Void> callback);
}
public interface DirectItemInternalLongClickListener {

View File

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

View File

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

View File

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

View File

@ -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<DirectItemContextMenu.MenuItem> 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;
})
);
}
}

View File

@ -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;

View File

@ -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<DirectItemContextMenu.MenuItem> 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;
})
);
}
}

View File

@ -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<DirectItemContextMenu.MenuItem> getLongClickOptions() {
final ImmutableList.Builder<DirectItemContextMenu.MenuItem> 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();
}
}

View File

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

View File

@ -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<DirectItemContextMenu.MenuItem> getLongClickOptions() {
final ImmutableList.Builder<DirectItemContextMenu.MenuItem> 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();
}
}

View File

@ -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<DirectItemContextMenu.MenuItem> 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;
})
);
}
}

View File

@ -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<DirectItemContextMenu.MenuItem> 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);
}

View File

@ -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<DirectItemContextMenu.MenuItem> getLongClickOptions() {
return ImmutableList.of(
new DirectItemContextMenu.MenuItem(R.id.download, R.string.action_download)
);
}
private static class AudioItemState {
private boolean prepared;

View File

@ -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<Integer, Integer> 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;
}
}

View File

@ -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<DirectItem, Void> 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<DirectItem, Void> 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<DirectItem, Void> getCallback() {
return callback;
}
}
public interface OnOptionSelectListener {
void onSelect(int itemId);
void onSelect(int itemId, @Nullable Function<DirectItem, Void> callback);
}
public interface OnReactionClickListener {

View File

@ -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<Media> models = mediaViewModel.getList().getValue();
final List<Media> modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models);
if (settingsHelper.getBoolean(Constants.TOGGLE_KEYWORD_FILTER)){
if (settingsHelper.getBoolean(Constants.TOGGLE_KEYWORD_FILTER)) {
final ArrayList<String> 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")

View File

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

View File

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

View File

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

View File

@ -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);
}
}

View File

@ -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

View File

@ -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<DirectItem, Void> 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<Integer> 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

View File

@ -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);
}
}

View File

@ -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<User>() {
@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<User>() {
@Override
public void onSuccess(final User user) {
profileModel = user;
setProfileDetails();
}
graphQLService.fetchUser(usernameTemp, new ServiceCallback<User>() {
@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

View File

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

View File

@ -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 -> {

View File

@ -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) {

View File

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

View File

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

View File

@ -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<Integer, DirectItemType> 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);
}

View File

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

View File

@ -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);
}
}

View File

@ -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 +
'}';
}
}
}

View File

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

View File

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

View File

@ -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) {

View File

@ -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
);

View File

@ -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<Favorite>() {
@Override
public void onSuccess(final Favorite result) {
// local has priority since it's more frequently updated
}
@Override
public void onDataNotAvailable() {
favRepo.insertOrUpdateFavorite(favorite, null);
}
});
}
}

View File

@ -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,

View File

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

View File

@ -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<String, String> sign(final Map<String, Object> 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<String, String> 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 <T> void moveItem(int sourceIndex, int targetIndex, List<T> list) {
if (sourceIndex <= targetIndex) {
Collections.rotate(list.subList(sourceIndex, targetIndex + 1), -1);
} else {
Collections.rotate(list.subList(targetIndex, sourceIndex + 1), 1);
}
}
private static final List<Integer> NON_REMOVABLE_NAV_ROOT_IDS = ImmutableList.of(R.id.profile_nav_graph, R.id.more_nav_graph);
@NonNull
public static Pair<List<Tab>, List<Tab>> getNavTabList(@NonNull final Context context) {
final Resources resources = context.getResources();
final String[] titleArray = resources.getStringArray(R.array.main_nav_titles);
TypedArray typedArray = resources.obtainTypedArray(R.array.main_nav_graphs);
int length = typedArray.length();
final String[] navGraphNames = new String[length];
final int[] navigationResIds = new int[length];
for (int i = 0; i < length; i++) {
final int resourceId = typedArray.getResourceId(i, 0);
if (resourceId == 0) continue;
navigationResIds[i] = resourceId;
navGraphNames[i] = resources.getResourceEntryName(resourceId);
}
typedArray.recycle();
typedArray = resources.obtainTypedArray(R.array.main_nav_graph_root_ids);
length = typedArray.length();
final int[] navRootIds = new int[length];
for (int i = 0; i < length; i++) {
final int resourceId = typedArray.getResourceId(i, 0);
if (resourceId == 0) continue;
navRootIds[i] = resourceId;
}
typedArray.recycle();
typedArray = resources.obtainTypedArray(R.array.main_nav_drawables);
length = typedArray.length();
final int[] iconIds = new int[length];
for (int i = 0; i < length; i++) {
final int resourceId = typedArray.getResourceId(i, 0);
if (resourceId == 0) continue;
iconIds[i] = resourceId;
}
typedArray.recycle();
typedArray = resources.obtainTypedArray(R.array.main_nav_start_dest_frag_ids);
length = typedArray.length();
final int[] startDestFragIds = new int[length];
for (int i = 0; i < length; i++) {
final int resourceId = typedArray.getResourceId(i, 0);
if (resourceId == 0) continue;
startDestFragIds[i] = resourceId;
}
typedArray.recycle();
final List<String> currentOrderGraphNames = getCurrentOrderOfGraphNamesFromPref(navGraphNames);
if (titleArray.length != iconIds.length || titleArray.length != navGraphNames.length) {
throw new RuntimeException(String.format("Array lengths don't match!: titleArray%s, navGraphNames: %s, iconIds: %s",
Arrays.toString(titleArray), Arrays.toString(navGraphNames), Arrays.toString(iconIds)));
}
final List<Tab> tabs = new ArrayList<>();
final List<Tab> otherTabs = new ArrayList<>(); // Will contain tabs not in current list
for (int i = 0; i < length; i++) {
final String navGraphName = navGraphNames[i];
final int navRootId = navRootIds[i];
final Tab tab = new Tab(iconIds[i],
titleArray[i],
!NON_REMOVABLE_NAV_ROOT_IDS.contains(navRootId),
navGraphName,
navigationResIds[i],
navRootId,
startDestFragIds[i]);
if (!currentOrderGraphNames.contains(navGraphName)) {
otherTabs.add(tab);
continue;
}
tabs.add(tab);
}
Collections.sort(tabs, Ordering.explicit(currentOrderGraphNames).onResultOf(tab -> {
if (tab == null) return null;
return tab.getGraphName();
}));
return new Pair<>(tabs, otherTabs);
}
@NonNull
private static List<String> getCurrentOrderOfGraphNamesFromPref(@NonNull final String[] navGraphNames) {
tabOrderString = settingsHelper.getString(PreferenceKeys.PREF_TAB_ORDER);
final List<String> navGraphNameList = Arrays.asList(navGraphNames);
if (TextUtils.isEmpty(tabOrderString)) {
// Use top 5 entries for default list
return navGraphNameList.subList(0, 5);
}
// Make sure that the list from preference does not contain any invalid values
final List<String> orderGraphNames = Arrays.stream(tabOrderString.split(","))
.filter(s -> !TextUtils.isEmpty(s))
.filter(navGraphNameList::contains)
.collect(Collectors.toList());
if (orderGraphNames.isEmpty()) {
// Use top 5 entries for default list
return navGraphNameList.subList(0, 5);
}
return orderGraphNames;
}
public static boolean isNavRootInCurrentTabs(final String navRootString) {
return tabOrderString.contains(navRootString);
}
}

View File

@ -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<User> 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<User> getCurrentUserLiveData() {
return currentUser;
}
@ -46,11 +53,13 @@ public class AppStateViewModel extends AndroidViewModel {
userService.getUserInfo(uid, new ServiceCallback<User>() {
@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);
}
});
}
}

View File

@ -241,6 +241,7 @@ public class DirectThreadViewModel extends AndroidViewModel {
if (users != null && users.getValue() != null) {
final List<User> 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<User> userList = leftUsers.getValue();
match = userList.stream()
.filter(Objects::nonNull)
.filter(user -> user.getPk() == userId)
.findFirst()
.orElse(null);

View File

@ -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;

View File

@ -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;

View File

@ -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 {

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -13,7 +13,8 @@
<argument
android:name="type"
app:argType="string"
app:nullable="false" />
app:nullable="false"
android:defaultValue="notif"/>
<argument
android:name="targetId"
android:defaultValue="0L"

View File

@ -88,24 +88,61 @@
<item>HH:mm:ss</item>
<item>H:mm:ss</item>
</string-array>
<array name="main_nav_ids">
<array name="main_nav_graph_root_ids">
<item>@id/direct_messages_nav_graph</item>
<item>@id/feed_nav_graph</item>
<item>@id/profile_nav_graph</item>
<item>@id/discover_nav_graph</item>
<item>@id/more_nav_graph</item>
<!-- New graphs should go below -->
<item>@id/favorites_nav_graph</item>
<item>@id/notification_viewer_nav_graph</item>
</array>
<!-- Nav graphs should correspond 1-to-1 with the above nav graph ids -->
<array name="main_nav_graphs">
<item>@navigation/direct_messages_nav_graph</item>
<item>@navigation/feed_nav_graph</item>
<item>@navigation/profile_nav_graph</item>
<item>@navigation/discover_nav_graph</item>
<item>@navigation/more_nav_graph</item>
<item>@navigation/favorites_nav_graph</item>
<item>@navigation/notification_viewer_nav_graph</item>
</array>
<string-array name="main_nav_ids_values" translatable="false">
<!-- Titles should correspond 1-to-1 with the above nav graphs -->
<string-array name="main_nav_titles" translatable="false">
<item>@string/title_dm</item>
<item>@string/feed</item>
<item>@string/profile</item>
<item>@string/title_discover</item>
<item>@string/more</item>
<item>@string/title_favorites</item>
<item>@string/title_notifications</item>
</string-array>
<array name="logged_out_main_nav_ids">
<item>@navigation/profile_nav_graph</item>
<item>@navigation/more_nav_graph</item>
<!-- Drawable should correspond 1-to-1 with the above titles -->
<array name="main_nav_drawables" translatable="false">
<item>@drawable/ic_message_24</item>
<item>@drawable/ic_home_24</item>
<item>@drawable/ic_person_24</item>
<item>@drawable/ic_explore_24</item>
<item>@drawable/ic_more_horiz_24</item>
<item>@drawable/ic_star_24</item>
<item>@drawable/ic_not_liked</item>
</array>
<!-- fragmentIds should correspond 1-to-1 with the above drawabled -->
<!-- these are the start destination of the corresponding nav graphs -->
<array name="main_nav_start_dest_frag_ids" translatable="false">
<item>@id/directMessagesInboxFragment</item>
<item>@id/feedFragment</item>
<item>@id/profileFragment</item>
<item>@id/discoverFragment</item>
<item>@id/morePreferencesFragment</item>
<item>@id/favoritesFragment</item>
<item>@id/notificationsViewer</item>
</array>
<!--<array name="logged_out_main_nav_graphs">-->
<!-- <item>@navigation/profile_nav_graph</item>-->
<!-- <item>@navigation/more_nav_graph</item>-->
<!--</array>-->
<string-array name="light_themes" translatable="false">
<item>@string/light_white_theme</item>
<item>@string/light_barinsta_theme</item>

View File

@ -4,4 +4,5 @@
<item name="unsend" type="id" />
<item name="forward" type="id" />
<item name="detail" type="id" />
<item name="copy" type="id" />
</resources>

View File

@ -477,4 +477,10 @@
<string name="crash_report_title">Select an email app to send crash logs</string>
<string name="skip_update">Skip this update</string>
<string name="on_latest_version">You\'re already on the latest version</string>
<string name="tab_order">Screen order</string>
<string name="other_tabs">Other tabs</string>
<string name="tab_order_start_next_launch">The tab order will be reflected on next launch</string>
<string name="dm_remove_warning">If saved, all DM related features will be disabled on next launch</string>
<string name="copy_caption">Copy caption</string>
<string name="copy_reply">Copy reply</string>
</resources>