Browse Source

Merge branch 'feature/multistack-navigation' of https://github.com/ammargitham/barinsta into pr/1542

renovate/androidx.fragment-fragment-ktx-1.x
Austin Huang 3 years ago
parent
commit
96a377156d
No known key found for this signature in database GPG Key ID: 84C23AA04587A91F
  1. 142
      app/src/main/java/awais/instagrabber/activities/MainActivity.kt
  2. 1
      app/src/main/java/awais/instagrabber/customviews/ChatMessageLayout.java
  3. 75
      app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java
  4. 3
      app/src/main/java/awais/instagrabber/customviews/helpers/PostFetcher.java
  5. 78
      app/src/main/java/awais/instagrabber/dialogs/PostLoadingDialogFragment.kt
  6. 2
      app/src/main/java/awais/instagrabber/dialogs/TabOrderPreferenceDialogFragment.java
  7. 53
      app/src/main/java/awais/instagrabber/fragments/FollowViewerFragment.kt
  8. 1
      app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java
  9. 1
      app/src/main/java/awais/instagrabber/fragments/LocationFragment.java
  10. 13
      app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java
  11. 1
      app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt
  12. 2
      app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java
  13. 312
      app/src/main/java/awais/instagrabber/fragments/UserSearchFragment.java
  14. 252
      app/src/main/java/awais/instagrabber/fragments/UserSearchFragment.kt
  15. 2
      app/src/main/java/awais/instagrabber/fragments/UserSearchMode.kt
  16. 49
      app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.kt
  17. 57
      app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt
  18. 2
      app/src/main/java/awais/instagrabber/fragments/settings/GeneralPreferencesFragment.java
  19. 6
      app/src/main/java/awais/instagrabber/models/Tab.kt
  20. 23
      app/src/main/java/awais/instagrabber/utils/BarinstaDeepLinkHelper.kt
  21. 21
      app/src/main/java/awais/instagrabber/utils/KeywordsFilterUtils.kt
  22. 30
      app/src/main/java/awais/instagrabber/utils/NavigationHelper.kt
  23. 6
      app/src/main/java/awais/instagrabber/viewmodels/FollowViewModel.kt
  24. 97
      app/src/main/java/awais/instagrabber/viewmodels/MediaViewModel.java
  25. 61
      app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt
  26. 13
      app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt
  27. 2
      app/src/main/java/awais/instagrabber/viewmodels/UserSearchViewModel.java
  28. 1
      app/src/main/java/thoughtbot/expandableadapter/ExpandableGroup.java
  29. 3
      app/src/main/res/layout/activity_main.xml
  30. 1
      app/src/main/res/layout/fragment_followers_viewer.xml
  31. 28
      app/src/main/res/menu/bottom_nav_menu.xml
  32. 333
      app/src/main/res/navigation/direct_messages_nav_graph.xml
  33. 518
      app/src/main/res/navigation/discover_nav_graph.xml
  34. 484
      app/src/main/res/navigation/favorites_nav_graph.xml
  35. 520
      app/src/main/res/navigation/feed_nav_graph.xml
  36. 539
      app/src/main/res/navigation/more_nav_graph.xml
  37. 495
      app/src/main/res/navigation/notification_viewer_nav_graph.xml
  38. 567
      app/src/main/res/navigation/profile_nav_graph.xml
  39. 12
      app/src/main/res/navigation/root_nav_graph.xml
  40. 108
      app/src/main/res/navigation/settings_nav_graph.xml
  41. 9
      app/src/main/res/values/ids.xml
  42. 18
      app/src/test/java/awais/instagrabber/common/Adapters.kt
  43. 5
      app/src/test/java/awais/instagrabber/viewmodels/ProfileFragmentViewModelTest.kt

142
app/src/main/java/awais/instagrabber/activities/MainActivity.kt

@ -14,8 +14,6 @@ import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.View import android.view.View
import android.view.WindowManager import android.view.WindowManager
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import androidx.appcompat.widget.Toolbar import androidx.appcompat.widget.Toolbar
import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.core.app.NotificationManagerCompat import androidx.core.app.NotificationManagerCompat
@ -26,9 +24,7 @@ import androidx.core.view.WindowInsetsCompat
import androidx.emoji.text.EmojiCompat import androidx.emoji.text.EmojiCompat
import androidx.emoji.text.EmojiCompat.InitCallback import androidx.emoji.text.EmojiCompat.InitCallback
import androidx.emoji.text.FontRequestEmojiCompatConfig import androidx.emoji.text.FontRequestEmojiCompatConfig
import androidx.fragment.app.FragmentManager
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.lifecycleScope
import androidx.navigation.NavController import androidx.navigation.NavController
import androidx.navigation.NavDestination import androidx.navigation.NavDestination
import androidx.navigation.NavGraph import androidx.navigation.NavGraph
@ -36,7 +32,6 @@ import androidx.navigation.NavGraphNavigator
import androidx.navigation.fragment.NavHostFragment import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.ui.* import androidx.navigation.ui.*
import awais.instagrabber.BuildConfig import awais.instagrabber.BuildConfig
import awais.instagrabber.NavGraphDirections
import awais.instagrabber.R import awais.instagrabber.R
import awais.instagrabber.customviews.emoji.EmojiVariantManager import awais.instagrabber.customviews.emoji.EmojiVariantManager
import awais.instagrabber.customviews.helpers.RootViewDeferringInsetsCallback import awais.instagrabber.customviews.helpers.RootViewDeferringInsetsCallback
@ -54,37 +49,28 @@ import awais.instagrabber.utils.*
import awais.instagrabber.utils.AppExecutors.tasksThread import awais.instagrabber.utils.AppExecutors.tasksThread
import awais.instagrabber.utils.DownloadUtils.ReselectDocumentTreeException import awais.instagrabber.utils.DownloadUtils.ReselectDocumentTreeException
import awais.instagrabber.utils.TextUtils.isEmpty import awais.instagrabber.utils.TextUtils.isEmpty
import awais.instagrabber.utils.TextUtils.shortcodeToId
import awais.instagrabber.utils.emoji.EmojiParser import awais.instagrabber.utils.emoji.EmojiParser
import awais.instagrabber.viewmodels.AppStateViewModel import awais.instagrabber.viewmodels.AppStateViewModel
import awais.instagrabber.viewmodels.DirectInboxViewModel import awais.instagrabber.viewmodels.DirectInboxViewModel
import awais.instagrabber.webservices.GraphQLRepository
import awais.instagrabber.webservices.MediaRepository
import com.google.android.material.appbar.AppBarLayout import com.google.android.material.appbar.AppBarLayout
import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior import com.google.android.material.appbar.AppBarLayout.ScrollingViewBehavior
import com.google.android.material.appbar.CollapsingToolbarLayout import com.google.android.material.appbar.CollapsingToolbarLayout
import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.bottomnavigation.BottomNavigationView
import com.google.android.material.textfield.TextInputLayout import com.google.android.material.textfield.TextInputLayout
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.* import java.util.*
class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedListener {
class MainActivity : BaseLanguageActivity() {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
private lateinit var navController: NavController private lateinit var navController: NavController
private lateinit var appBarConfiguration: AppBarConfiguration private lateinit var appBarConfiguration: AppBarConfiguration
// private var currentNavControllerLiveData: LiveData<NavController>? = null
private var searchMenuItem: MenuItem? = null private var searchMenuItem: MenuItem? = null
private var startNavRootId: Int = 0 private var startNavRootId: Int = 0
// private var firstFragmentGraphIndex = 0
private var lastSelectedNavMenuId = 0 private var lastSelectedNavMenuId = 0
private var isActivityCheckerServiceBound = false private var isActivityCheckerServiceBound = false
private var isBackStackEmpty = false
private var isLoggedIn = false private var isLoggedIn = false
private var deviceUuid: String? = null private var deviceUuid: String? = null
private var csrfToken: String? = null private var csrfToken: String? = null
@ -106,8 +92,6 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
isActivityCheckerServiceBound = false isActivityCheckerServiceBound = false
} }
} }
private val mediaRepository: MediaRepository by lazy { MediaRepository.getInstance() }
private val graphQLRepository: GraphQLRepository by lazy { GraphQLRepository.getInstance() }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
try { try {
@ -156,7 +140,6 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
if (isLoggedIn && Utils.settingsHelper.getBoolean(PreferenceKeys.CHECK_ACTIVITY)) { if (isLoggedIn && Utils.settingsHelper.getBoolean(PreferenceKeys.CHECK_ACTIVITY)) {
bindActivityCheckerService() bindActivityCheckerService()
} }
supportFragmentManager.addOnBackStackChangedListener(this)
// Initialise the internal map // Initialise the internal map
tasksThread.execute { tasksThread.execute {
EmojiParser.getInstance(this) EmojiParser.getInstance(this)
@ -235,7 +218,6 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
override fun onCreateOptionsMenu(menu: Menu): Boolean { override fun onCreateOptionsMenu(menu: Menu): Boolean {
menuInflater.inflate(R.menu.main_menu, menu) menuInflater.inflate(R.menu.main_menu, menu)
searchMenuItem = menu.findItem(R.id.search) searchMenuItem = menu.findItem(R.id.search)
// val navController = currentNavControllerLiveData?.value
val currentDestination = navController.currentDestination val currentDestination = navController.currentDestination
if (currentDestination != null) { if (currentDestination != null) {
val backStack = navController.backQueue val backStack = navController.backQueue
@ -246,9 +228,8 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
override fun onOptionsItemSelected(item: MenuItem): Boolean { override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == R.id.search) { if (item.itemId == R.id.search) {
// val navController = currentNavControllerLiveData?.value ?: return false
try { try {
navController.navigate(R.id.action_global_search)
navController.navigate(getSearchDeepLink())
return true return true
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "onOptionsItemSelected: ", e) Log.e(TAG, "onOptionsItemSelected: ", e)
@ -301,27 +282,24 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
instance = null instance = null
} }
override fun onBackPressed() {
val backStack = navController.backQueue
val currentNavControllerBackStack = backStack.size
if (isTaskRoot && isBackStackEmpty && currentNavControllerBackStack == 2) {
finishAfterTransition()
return
}
if (!isFinishing) {
try {
super.onBackPressed()
} catch (e: Exception) {
Log.e(TAG, "onBackPressed: ", e)
finish()
}
}
}
override fun onBackStackChanged() {
val backStackEntryCount = supportFragmentManager.backStackEntryCount
isBackStackEmpty = backStackEntryCount == 0
}
// override fun onBackPressed() {
// Log.d(TAG, "onBackPressed: ")
// navController.navigateUp()
// val backStack = navController.backQueue
// val currentNavControllerBackStack = backStack.size
// if (isTaskRoot && isBackStackEmpty && currentNavControllerBackStack == 2) {
// finishAfterTransition()
// return
// }
// if (!isFinishing) {
// try {
// super.onBackPressed()
// } catch (e: Exception) {
// Log.e(TAG, "onBackPressed: ", e)
// finish()
// }
// }
// }
private fun createNotificationChannels() { private fun createNotificationChannels() {
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return
@ -374,16 +352,10 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
val navigator = navigatorProvider.getNavigator<NavGraphNavigator>("navigation") val navigator = navigatorProvider.getNavigator<NavGraphNavigator>("navigation")
val rootNavGraph = NavGraph(navigator) val rootNavGraph = NavGraph(navigator)
val navInflater = navController.navInflater val navInflater = navController.navInflater
val destinations = currentTabs.map {
val navGraph = navInflater.inflate(R.navigation.nav_graph)
navGraph.id = it.navigationRootId
navGraph.label = "${it.title}_nav_graph".lowercase(Locale.getDefault())
navGraph.setStartDestination(it.startDestinationFragmentId)
return@map navGraph
}
val topLevelDestinations = currentTabs.map { navInflater.inflate(it.navigationResId) }
rootNavGraph.id = R.id.root_nav_graph rootNavGraph.id = R.id.root_nav_graph
rootNavGraph.label = "root_nav_graph" rootNavGraph.label = "root_nav_graph"
rootNavGraph.addDestinations(destinations)
rootNavGraph.addDestinations(topLevelDestinations)
rootNavGraph.setStartDestination(if (startNavRootId != 0) startNavRootId else R.id.profile_nav_graph) rootNavGraph.setStartDestination(if (startNavRootId != 0) startNavRootId else R.id.profile_nav_graph)
navController.graph = rootNavGraph navController.graph = rootNavGraph
binding.bottomNavView.setupWithNavController(navController) binding.bottomNavView.setupWithNavController(navController)
@ -536,8 +508,7 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
fun navigateToThread(threadId: String?, threadTitle: String?) { fun navigateToThread(threadId: String?, threadTitle: String?) {
if (threadId == null || threadTitle == null) return if (threadId == null || threadTitle == null) return
try { try {
val action = NavGraphDirections.actionGlobalDirectThread(threadId, threadTitle)
navController.navigate(action)
navController.navigate(getDirectThreadDeepLink(threadId, threadTitle))
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "navigateToThread: ", e) Log.e(TAG, "navigateToThread: ", e)
} }
@ -564,8 +535,7 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
private fun showProfileView(intentModel: IntentModel) { private fun showProfileView(intentModel: IntentModel) {
try { try {
val username = intentModel.text val username = intentModel.text
val action = NavGraphDirections.actionGlobalProfile().setUsername(username)
navController.navigate(action)
navController.navigate(getProfileDeepLink(username))
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "showProfileView: ", e) Log.e(TAG, "showProfileView: ", e)
} }
@ -574,42 +544,18 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
private fun showPostView(intentModel: IntentModel) { private fun showPostView(intentModel: IntentModel) {
val shortCode = intentModel.text val shortCode = intentModel.text
// Log.d(TAG, "shortCode: " + shortCode); // Log.d(TAG, "shortCode: " + shortCode);
val alertDialog = AlertDialog.Builder(this)
.setCancelable(false)
.setView(R.layout.dialog_opening_post)
.create()
alertDialog.show()
lifecycleScope.launch(Dispatchers.IO) {
try {
val media = if (isLoggedIn) mediaRepository.fetch(shortcodeToId(shortCode)) else graphQLRepository.fetchPost(shortCode)
withContext(Dispatchers.Main) {
if (media == null) {
Toast.makeText(applicationContext, R.string.post_not_found, Toast.LENGTH_SHORT).show()
return@withContext
}
try { try {
val action = NavGraphDirections.actionGlobalPost(media, 0)
navController.navigate(action)
navController.navigate(getPostDeepLink(shortCode))
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "showPostView: ", e) Log.e(TAG, "showPostView: ", e)
} }
} }
} catch (e: Exception) {
Log.e(TAG, "showPostView: ", e)
} finally {
withContext(Dispatchers.Main) {
alertDialog.dismiss()
}
}
}
}
private fun showLocationView(intentModel: IntentModel) { private fun showLocationView(intentModel: IntentModel) {
val locationId = intentModel.text val locationId = intentModel.text
// Log.d(TAG, "locationId: " + locationId); // Log.d(TAG, "locationId: " + locationId);
try { try {
val action = NavGraphDirections.actionGlobalLocation(locationId.toLong())
navController.navigate(action)
navController.navigate(getLocationDeepLink(locationId))
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "showLocationView: ", e) Log.e(TAG, "showLocationView: ", e)
} }
@ -619,8 +565,7 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
val hashtag = intentModel.text val hashtag = intentModel.text
// Log.d(TAG, "hashtag: " + hashtag); // Log.d(TAG, "hashtag: " + hashtag);
try { try {
val action = NavGraphDirections.actionGlobalHashTag(hashtag)
navController.navigate(action)
navController.navigate(getHashtagDeepLink(hashtag))
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "showHashtagView: ", e) Log.e(TAG, "showHashtagView: ", e)
} }
@ -628,8 +573,7 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
private fun showActivityView() { private fun showActivityView() {
try { try {
val action = NavGraphDirections.actionGlobalNotifications().apply { type = "notif" }
navController.navigate(action)
navController.navigate(getNotificationsDeepLink("notif"))
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "showActivityView: ", e) Log.e(TAG, "showActivityView: ", e)
} }
@ -649,21 +593,21 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL
val bottomNavView: BottomNavigationView val bottomNavView: BottomNavigationView
get() = binding.bottomNavView get() = binding.bottomNavView
fun setCollapsingView(view: View) {
try {
binding.collapsingToolbarLayout.addView(view, 0)
} catch (e: Exception) {
Log.e(TAG, "setCollapsingView: ", e)
}
}
fun removeCollapsingView(view: View) {
try {
binding.collapsingToolbarLayout.removeView(view)
} catch (e: Exception) {
Log.e(TAG, "removeCollapsingView: ", e)
}
}
// fun setCollapsingView(view: View) {
// try {
// binding.collapsingToolbarLayout.addView(view, 0)
// } catch (e: Exception) {
// Log.e(TAG, "setCollapsingView: ", e)
// }
// }
//
// fun removeCollapsingView(view: View) {
// try {
// binding.collapsingToolbarLayout.removeView(view)
// } catch (e: Exception) {
// Log.e(TAG, "removeCollapsingView: ", e)
// }
// }
fun resetToolbar() { fun resetToolbar() {
binding.appBarLayout.visibility = View.VISIBLE binding.appBarLayout.visibility = View.VISIBLE

1
app/src/main/java/awais/instagrabber/customviews/ChatMessageLayout.java

@ -9,7 +9,6 @@ import android.widget.TextView;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.constraintlayout.widget.ConstraintLayout;
import awais.instagrabber.R; import awais.instagrabber.R;

75
app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java

@ -3,12 +3,11 @@ package awais.instagrabber.customviews;
import android.content.Context; import android.content.Context;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.util.Log; import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.lifecycle.LifecycleOwner; import androidx.lifecycle.LifecycleOwner;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelProvider;
import androidx.lifecycle.ViewModelStoreOwner; import androidx.lifecycle.ViewModelStoreOwner;
import androidx.recyclerview.widget.LinearSmoothScroller; import androidx.recyclerview.widget.LinearSmoothScroller;
@ -27,24 +26,18 @@ import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
import java.util.Collections; import java.util.Collections;
import java.util.List; import java.util.List;
import java.util.function.Function;
import awais.instagrabber.adapters.FeedAdapterV2; import awais.instagrabber.adapters.FeedAdapterV2;
import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration; import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration;
import awais.instagrabber.customviews.helpers.PostFetcher; import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge; import awais.instagrabber.customviews.helpers.RecyclerLazyLoaderAtEdge;
import awais.instagrabber.fragments.settings.PreferenceKeys;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.PostsLayoutPreferences;
import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.utils.KeywordsFilterUtils;
import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.ResponseBodyUtils;
import awais.instagrabber.utils.Utils; import awais.instagrabber.utils.Utils;
import awais.instagrabber.viewmodels.MediaViewModel; import awais.instagrabber.viewmodels.MediaViewModel;
import awais.instagrabber.workers.DownloadWorker; import awais.instagrabber.workers.DownloadWorker;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class PostsRecyclerView extends RecyclerView { public class PostsRecyclerView extends RecyclerView {
private static final String TAG = "PostsRecyclerView"; private static final String TAG = "PostsRecyclerView";
@ -52,7 +45,6 @@ public class PostsRecyclerView extends RecyclerView {
private PostsLayoutPreferences layoutPreferences; private PostsLayoutPreferences layoutPreferences;
private PostFetcher.PostFetchService postFetchService; private PostFetcher.PostFetchService postFetchService;
private Transition transition; private Transition transition;
private PostFetcher postFetcher;
private ViewModelStoreOwner viewModelStoreOwner; private ViewModelStoreOwner viewModelStoreOwner;
private FeedAdapterV2 feedAdapter; private FeedAdapterV2 feedAdapter;
private LifecycleOwner lifeCycleOwner; private LifecycleOwner lifeCycleOwner;
@ -63,40 +55,9 @@ public class PostsRecyclerView extends RecyclerView {
private FeedAdapterV2.FeedItemCallback feedItemCallback; private FeedAdapterV2.FeedItemCallback feedItemCallback;
private boolean shouldScrollToTop; private boolean shouldScrollToTop;
private FeedAdapterV2.SelectionModeCallback selectionModeCallback; private FeedAdapterV2.SelectionModeCallback selectionModeCallback;
private Function<ViewGroup, View> headerViewCreator;
private Function<View, Void> headerBinder;
private boolean refresh = true;
private final List<FetchStatusChangeListener> fetchStatusChangeListeners = new ArrayList<>(); private final List<FetchStatusChangeListener> fetchStatusChangeListeners = new ArrayList<>();
private final FetchListener<List<Media>> fetchListener = new FetchListener<List<Media>>() {
@Override
public void onResult(final List<Media> result) {
if (refresh) {
refresh = false;
mediaViewModel.getList().postValue(result);
shouldScrollToTop = true;
dispatchFetchStatus();
return;
}
final List<Media> models = mediaViewModel.getList().getValue();
final List<Media> modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models);
if (settingsHelper.getBoolean(PreferenceKeys.TOGGLE_KEYWORD_FILTER)) {
final ArrayList<String> items = new ArrayList<>(settingsHelper.getStringSet(PreferenceKeys.KEYWORD_FILTERS));
modelsCopy.addAll(new KeywordsFilterUtils(items).filter(result));
} else {
modelsCopy.addAll(result);
}
mediaViewModel.getList().postValue(modelsCopy);
dispatchFetchStatus();
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "onFailure: ", t);
}
};
private final RecyclerView.SmoothScroller smoothScroller = new LinearSmoothScroller(getContext()) { private final RecyclerView.SmoothScroller smoothScroller = new LinearSmoothScroller(getContext()) {
@Override @Override
protected int getVerticalSnapPreference() { protected int getVerticalSnapPreference() {
@ -199,18 +160,22 @@ public class PostsRecyclerView extends RecyclerView {
private void initSelf() { private void initSelf() {
try { try {
mediaViewModel = new ViewModelProvider(viewModelStoreOwner).get(MediaViewModel.class);
mediaViewModel = new ViewModelProvider(
viewModelStoreOwner,
new MediaViewModel.ViewModelFactory(postFetchService)
).get(MediaViewModel.class);
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "initSelf: ", e); Log.e(TAG, "initSelf: ", e);
} }
if (mediaViewModel == null) return; if (mediaViewModel == null) return;
mediaViewModel.getList().observe(lifeCycleOwner, list -> feedAdapter.submitList(list, () -> {
final LiveData<List<Media>> mediaListLiveData = mediaViewModel.getList();
mediaListLiveData.observe(lifeCycleOwner, list -> feedAdapter.submitList(list, () -> {
dispatchFetchStatus();
// postDelayed(this::fetchMoreIfPossible, 1000); // postDelayed(this::fetchMoreIfPossible, 1000);
if (!shouldScrollToTop) return; if (!shouldScrollToTop) return;
shouldScrollToTop = false; shouldScrollToTop = false;
post(() -> smoothScrollToPosition(0)); post(() -> smoothScrollToPosition(0));
})); }));
postFetcher = new PostFetcher(postFetchService, fetchListener);
if (layoutPreferences.getHasGap()) { if (layoutPreferences.getHasGap()) {
addItemDecoration(gridSpacingItemDecoration); addItemDecoration(gridSpacingItemDecoration);
} }
@ -218,18 +183,20 @@ public class PostsRecyclerView extends RecyclerView {
setNestedScrollingEnabled(true); setNestedScrollingEnabled(true);
setItemAnimator(null); setItemAnimator(null);
lazyLoader = new RecyclerLazyLoaderAtEdge(layoutManager, (page) -> { lazyLoader = new RecyclerLazyLoaderAtEdge(layoutManager, (page) -> {
if (postFetcher.hasMore()) {
postFetcher.fetch();
if (mediaViewModel.hasMore()) {
mediaViewModel.fetch();
dispatchFetchStatus(); dispatchFetchStatus();
} }
}); });
addOnScrollListener(lazyLoader); addOnScrollListener(lazyLoader);
postFetcher.fetch();
if (mediaListLiveData.getValue() == null || mediaListLiveData.getValue().isEmpty()) {
mediaViewModel.fetch();
dispatchFetchStatus(); dispatchFetchStatus();
} }
}
private void fetchMoreIfPossible() { private void fetchMoreIfPossible() {
if (!postFetcher.hasMore()) return;
if (!mediaViewModel.hasMore()) return;
if (feedAdapter.getItemCount() == 0) return; if (feedAdapter.getItemCount() == 0) return;
final LayoutManager layoutManager = getLayoutManager(); final LayoutManager layoutManager = getLayoutManager();
if (!(layoutManager instanceof StaggeredGridLayoutManager)) return; if (!(layoutManager instanceof StaggeredGridLayoutManager)) return;
@ -238,7 +205,7 @@ public class PostsRecyclerView extends RecyclerView {
if (allNoPosition) return; if (allNoPosition) return;
final boolean match = Arrays.stream(itemPositions).anyMatch(position -> position == feedAdapter.getItemCount() - 1); final boolean match = Arrays.stream(itemPositions).anyMatch(position -> position == feedAdapter.getItemCount() - 1);
if (!match) return; if (!match) return;
postFetcher.fetch();
mediaViewModel.fetch();
dispatchFetchStatus(); dispatchFetchStatus();
} }
@ -268,6 +235,7 @@ public class PostsRecyclerView extends RecyclerView {
private List<String> getDisplayUrl(final Media feedModel) { private List<String> getDisplayUrl(final Media feedModel) {
List<String> urls = Collections.emptyList(); List<String> urls = Collections.emptyList();
if (feedModel == null || feedModel.getType() == null) return urls;
switch (feedModel.getType()) { switch (feedModel.getType()) {
case MEDIA_TYPE_IMAGE: case MEDIA_TYPE_IMAGE:
case MEDIA_TYPE_VIDEO: case MEDIA_TYPE_VIDEO:
@ -320,20 +288,18 @@ public class PostsRecyclerView extends RecyclerView {
} }
public void refresh() { public void refresh() {
refresh = true;
shouldScrollToTop = true;
if (lazyLoader != null) { if (lazyLoader != null) {
lazyLoader.resetState(); lazyLoader.resetState();
} }
if (postFetcher != null) {
// mediaViewModel.getList().postValue(Collections.emptyList());
postFetcher.reset();
postFetcher.fetch();
if (mediaViewModel != null) {
mediaViewModel.refresh();
} }
dispatchFetchStatus(); dispatchFetchStatus();
} }
public boolean isFetching() { public boolean isFetching() {
return postFetcher != null && postFetcher.isFetching();
return mediaViewModel != null && mediaViewModel.isFetching();
} }
public PostsRecyclerView addFetchStatusChangeListener(final FetchStatusChangeListener fetchStatusChangeListener) { public PostsRecyclerView addFetchStatusChangeListener(final FetchStatusChangeListener fetchStatusChangeListener) {
@ -369,6 +335,7 @@ public class PostsRecyclerView extends RecyclerView {
protected void onDetachedFromWindow() { protected void onDetachedFromWindow() {
super.onDetachedFromWindow(); super.onDetachedFromWindow();
lifeCycleOwner = null; lifeCycleOwner = null;
initCalled = false;
} }
@Override @Override

3
app/src/main/java/awais/instagrabber/customviews/helpers/PostFetcher.java

@ -21,7 +21,7 @@ public class PostFetcher {
} }
public void fetch() { public void fetch() {
if (!fetching) {
if (fetching) return;
fetching = true; fetching = true;
postFetchService.fetch(new FetchListener<List<Media>>() { postFetchService.fetch(new FetchListener<List<Media>>() {
@Override @Override
@ -36,7 +36,6 @@ public class PostFetcher {
} }
}); });
} }
}
public void reset() { public void reset() {
postFetchService.reset(); postFetchService.reset();

78
app/src/main/java/awais/instagrabber/dialogs/PostLoadingDialogFragment.kt

@ -0,0 +1,78 @@
package awais.instagrabber.dialogs
import android.app.Dialog
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.widget.Toast
import androidx.fragment.app.DialogFragment
import androidx.lifecycle.lifecycleScope
import androidx.navigation.fragment.findNavController
import awais.instagrabber.R
import awais.instagrabber.utils.*
import awais.instagrabber.utils.extensions.TAG
import awais.instagrabber.webservices.GraphQLRepository
import awais.instagrabber.webservices.MediaRepository
import com.google.android.material.dialog.MaterialAlertDialogBuilder
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import kotlinx.coroutines.withContext
import java.util.*
class PostLoadingDialogFragment : DialogFragment() {
private var isLoggedIn: Boolean = false
private val mediaRepository: MediaRepository by lazy { MediaRepository.getInstance() }
private val graphQLRepository: GraphQLRepository by lazy { GraphQLRepository.getInstance() }
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val cookie = Utils.settingsHelper.getString(Constants.COOKIE)
var userId: Long = 0
var csrfToken: String? = null
if (cookie.isNotBlank()) {
userId = getUserIdFromCookie(cookie)
csrfToken = getCsrfTokenFromCookie(cookie)
}
if (cookie.isBlank() || userId == 0L || csrfToken.isNullOrBlank()) {
isLoggedIn = false
return
}
isLoggedIn = true
}
override fun onCreateDialog(savedInstanceState: Bundle?): Dialog {
return MaterialAlertDialogBuilder(requireContext())
.setCancelable(false)
.setView(R.layout.dialog_opening_post)
.create()
}
override fun onAttach(context: Context) {
super.onAttach(context)
val arguments = PostLoadingDialogFragmentArgs.fromBundle(arguments ?: return)
val shortCode = arguments.shortCode
lifecycleScope.launch(Dispatchers.IO) {
try {
val media = if (isLoggedIn) mediaRepository.fetch(TextUtils.shortcodeToId(shortCode)) else graphQLRepository.fetchPost(shortCode)
withContext(Dispatchers.Main) {
if (media == null) {
Toast.makeText(context, R.string.post_not_found, Toast.LENGTH_SHORT).show()
return@withContext
}
try {
findNavController().navigate(PostLoadingDialogFragmentDirections.actionToPost(media, 0))
} catch (e: Exception) {
Log.e(TAG, "showPostView: ", e)
}
}
} catch (e: Exception) {
Log.e(TAG, "showPostView: ", e)
} finally {
withContext(Dispatchers.Main) {
dismiss()
}
}
}
}
}

2
app/src/main/java/awais/instagrabber/dialogs/TabOrderPreferenceDialogFragment.java

@ -238,7 +238,7 @@ public class TabOrderPreferenceDialogFragment extends DialogFragment {
private void saveNewOrder() { private void saveNewOrder() {
final String newOrderString = newOrderTabs final String newOrderString = newOrderTabs
.stream() .stream()
.map(tab -> NavigationHelperKt.geNavGraphNameForNavRootId(tab.getNavigationRootId()))
.map(tab -> NavigationHelperKt.getNavGraphNameForNavRootId(tab.getNavigationRootId()))
.collect(Collectors.joining(",")); .collect(Collectors.joining(","));
Utils.settingsHelper.putString(PreferenceKeys.PREF_TAB_ORDER, newOrderString); Utils.settingsHelper.putString(PreferenceKeys.PREF_TAB_ORDER, newOrderString);
} }

53
app/src/main/java/awais/instagrabber/fragments/FollowViewerFragment.kt

@ -1,51 +1,30 @@
package awais.instagrabber.fragments package awais.instagrabber.fragments
import android.content.Context
import android.content.res.Resources
import android.os.Bundle import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.Menu
import android.view.MenuInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.Toast
import androidx.annotation.NonNull
import androidx.annotation.Nullable
import android.view.*
import androidx.appcompat.app.ActionBar import androidx.appcompat.app.ActionBar
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.navigation.fragment.NavHostFragment
import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout import androidx.swiperefreshlayout.widget.SwipeRefreshLayout
import java.util.ArrayList
import awais.instagrabber.R import awais.instagrabber.R
import awais.instagrabber.adapters.FollowAdapter import awais.instagrabber.adapters.FollowAdapter
import awais.instagrabber.customviews.helpers.RecyclerLazyLoader import awais.instagrabber.customviews.helpers.RecyclerLazyLoader
import awais.instagrabber.databinding.FragmentFollowersViewerBinding import awais.instagrabber.databinding.FragmentFollowersViewerBinding
import awais.instagrabber.models.Resource import awais.instagrabber.models.Resource
import awais.instagrabber.repositories.responses.User import awais.instagrabber.repositories.responses.User
import awais.instagrabber.utils.AppExecutors
import awais.instagrabber.viewmodels.FollowViewModel import awais.instagrabber.viewmodels.FollowViewModel
import thoughtbot.expandableadapter.ExpandableGroup import thoughtbot.expandableadapter.ExpandableGroup
import java.util.*
class FollowViewerFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener { class FollowViewerFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener {
private val followModels: ArrayList<User> = ArrayList<User>()
private val followingModels: ArrayList<User> = ArrayList<User>()
private val followersModels: ArrayList<User> = ArrayList<User>()
private val allFollowing: ArrayList<User> = ArrayList<User>()
private val moreAvailable = true
private var isFollowersList = false private var isFollowersList = false
private var isCompare = false private var isCompare = false
private var shouldRefresh = true private var shouldRefresh = true
private var searching = false private var searching = false
private var profileId: Long = 0
private var username: String? = null private var username: String? = null
private var namePost: String? = null private var namePost: String? = null
private var type = 0 private var type = 0
@ -125,7 +104,7 @@ class FollowViewerFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener {
type = if (isFollowersList) R.string.followers_type_followers else R.string.followers_type_following type = if (isFollowersList) R.string.followers_type_followers else R.string.followers_type_following
setSubtitle(type) setSubtitle(type)
val layoutManager = LinearLayoutManager(context) val layoutManager = LinearLayoutManager(context)
lazyLoader = RecyclerLazyLoader(layoutManager, { _, totalItemsCount ->
lazyLoader = RecyclerLazyLoader(layoutManager) { _, totalItemsCount ->
binding.swipeRefreshLayout.isRefreshing = true binding.swipeRefreshLayout.isRefreshing = true
val liveData = if (searching) viewModel.search(isFollowersList) val liveData = if (searching) viewModel.search(isFollowersList)
else viewModel.fetch(isFollowersList, null) else viewModel.fetch(isFollowersList, null)
@ -133,7 +112,7 @@ class FollowViewerFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener {
binding.swipeRefreshLayout.isRefreshing = it.status != Resource.Status.SUCCESS binding.swipeRefreshLayout.isRefreshing = it.status != Resource.Status.SUCCESS
layoutManager.scrollToPosition(totalItemsCount) layoutManager.scrollToPosition(totalItemsCount)
} }
})
}
binding.rvFollow.addOnScrollListener(lazyLoader) binding.rvFollow.addOnScrollListener(lazyLoader)
binding.rvFollow.layoutManager = layoutManager binding.rvFollow.layoutManager = layoutManager
viewModel.getList(isFollowersList).observe(viewLifecycleOwner) { viewModel.getList(isFollowersList).observe(viewLifecycleOwner) {
@ -167,7 +146,7 @@ class FollowViewerFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener {
} }
override fun onQueryTextChange(query: String): Boolean { override fun onQueryTextChange(query: String): Boolean {
if (query.isNullOrEmpty()) {
if (query.isEmpty()) {
if (!isCompare && searching) { if (!isCompare && searching) {
viewModel.setQuery(null, isFollowersList) viewModel.setQuery(null, isFollowersList)
viewModel.getSearch().removeObservers(viewLifecycleOwner) viewModel.getSearch().removeObservers(viewLifecycleOwner)
@ -216,7 +195,7 @@ class FollowViewerFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener {
) { ) {
val groups: ArrayList<ExpandableGroup> = ArrayList<ExpandableGroup>(1) val groups: ArrayList<ExpandableGroup> = ArrayList<ExpandableGroup>(1)
if (isCompare && followingModels != null && followersModels != null && allFollowing != null) { if (isCompare && followingModels != null && followersModels != null && allFollowing != null) {
if (followingModels.size > 0) groups.add(
if (followingModels.isNotEmpty()) groups.add(
ExpandableGroup( ExpandableGroup(
getString( getString(
R.string.followers_not_following, R.string.followers_not_following,
@ -224,7 +203,7 @@ class FollowViewerFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener {
), followingModels ), followingModels
) )
) )
if (followersModels.size > 0) groups.add(
if (followersModels.isNotEmpty()) groups.add(
ExpandableGroup( ExpandableGroup(
getString( getString(
R.string.followers_not_follower, R.string.followers_not_follower,
@ -232,7 +211,7 @@ class FollowViewerFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener {
), followersModels ), followersModels
) )
) )
if (allFollowing.size > 0) groups.add(
if (allFollowing.isNotEmpty()) groups.add(
ExpandableGroup( ExpandableGroup(
getString(R.string.followers_both_following), getString(R.string.followers_both_following),
allFollowing allFollowing
@ -244,17 +223,11 @@ class FollowViewerFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener {
adapter = FollowAdapter({ v -> adapter = FollowAdapter({ v ->
val tag = v.tag val tag = v.tag
if (tag is User) { if (tag is User) {
val model = tag
val bundle = Bundle()
bundle.putString("username", model.username)
NavHostFragment.findNavController(this).navigate(R.id.action_global_profileFragment, bundle)
findNavController().navigate(FollowViewerFragmentDirections.actionToProfile().setUsername(tag.username))
} }
}, groups)
adapter!!.toggleGroup(0)
binding.rvFollow.adapter = adapter!!
}, groups).also {
it.toggleGroup(0)
binding.rvFollow.adapter = it
} }
companion object {
private const val TAG = "FollowViewerFragment"
} }
} }

1
app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java

@ -394,7 +394,6 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe
} }
setTitle(); setTitle();
setupPosts(); setupPosts();
// fetchStories();
if (isLoggedIn) { if (isLoggedIn) {
hashtagDetailsBinding.btnFollowTag.setVisibility(View.VISIBLE); hashtagDetailsBinding.btnFollowTag.setVisibility(View.VISIBLE);
hashtagDetailsBinding.btnFollowTag.setText(hashtagModel.getFollowing() == FollowingType.FOLLOWING hashtagDetailsBinding.btnFollowTag.setText(hashtagModel.getFollowing() == FollowingType.FOLLOWING

1
app/src/main/java/awais/instagrabber/fragments/LocationFragment.java

@ -62,7 +62,6 @@ import awais.instagrabber.utils.Utils;
import awais.instagrabber.webservices.GraphQLRepository; import awais.instagrabber.webservices.GraphQLRepository;
import awais.instagrabber.webservices.LocationService; import awais.instagrabber.webservices.LocationService;
import awais.instagrabber.webservices.ServiceCallback; import awais.instagrabber.webservices.ServiceCallback;
//import awais.instagrabber.webservices.StoriesRepository;
import kotlinx.coroutines.Dispatchers; import kotlinx.coroutines.Dispatchers;
import static awais.instagrabber.utils.Utils.settingsHelper; import static awais.instagrabber.utils.Utils.settingsHelper;

13
app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java

@ -758,17 +758,18 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme
popupMenu.setOnMenuItemClickListener(item -> { popupMenu.setOnMenuItemClickListener(item -> {
final int itemId = item.getItemId(); final int itemId = item.getItemId();
if (itemId == R.id.share_dm) { if (itemId == R.id.share_dm) {
if (profileModel.isPrivate())
if (profileModel.isPrivate()) {
Toast.makeText(context, R.string.share_private_post, Toast.LENGTH_SHORT).show(); Toast.makeText(context, R.string.share_private_post, Toast.LENGTH_SHORT).show();
try {
final NavDirections actionGlobalUserSearch = PostViewV2FragmentDirections
.actionToUserSearch()
final UserSearchNavGraphDirections.ActionGlobalUserSearch actionGlobalUserSearch = UserSearchFragmentDirections
.actionGlobalUserSearch()
.setTitle(getString(R.string.share)) .setTitle(getString(R.string.share))
.setActionLabel(getString(R.string.send)) .setActionLabel(getString(R.string.send))
.setShowGroups(true) .setShowGroups(true)
.setMultiple(true) .setMultiple(true)
.setSearchMode(UserSearchMode.RAVEN);
NavHostFragment.findNavController(this).navigate(actionGlobalUserSearch);
.setSearchMode(UserSearchFragment.SearchMode.RAVEN);
final NavController navController = NavHostFragment.findNavController(PostViewV2Fragment.this);
try {
navController.navigate(actionGlobalUserSearch);
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "setupShare: ", e); Log.e(TAG, "setupShare: ", e);
} }

1
app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt

@ -17,7 +17,6 @@ import androidx.appcompat.view.ContextThemeWrapper
import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.PopupMenu
import androidx.core.view.GestureDetectorCompat import androidx.core.view.GestureDetectorCompat
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.ViewModel import androidx.lifecycle.ViewModel

2
app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java

@ -171,7 +171,7 @@ public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.O
private void openPostDialog(final Media feedModel, final int position) { private void openPostDialog(final Media feedModel, final int position) {
try { try {
final NavDirections action = TopicPostsFragmentDirections.actionGlobalPost(feedModel, position);
final NavDirections action = TopicPostsFragmentDirections.actionToPost(feedModel, position);
NavHostFragment.findNavController(TopicPostsFragment.this).navigate(action); NavHostFragment.findNavController(TopicPostsFragment.this).navigate(action);
} catch (Exception e) { } catch (Exception e) {
Log.e(TAG, "openPostDialog: ", e); Log.e(TAG, "openPostDialog: ", e);

312
app/src/main/java/awais/instagrabber/fragments/UserSearchFragment.java

@ -1,312 +0,0 @@
package awais.instagrabber.fragments;
import android.content.Context;
import android.os.Bundle;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.app.ActionBar;
import androidx.core.util.Pair;
import androidx.fragment.app.Fragment;
import androidx.lifecycle.SavedStateHandle;
import androidx.lifecycle.ViewModelProvider;
import androidx.navigation.NavBackStackEntry;
import androidx.navigation.NavController;
import androidx.navigation.fragment.NavHostFragment;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.transition.TransitionManager;
import com.google.android.material.chip.Chip;
import com.google.android.material.snackbar.Snackbar;
import java.util.Objects;
import java.util.Set;
import awais.instagrabber.activities.MainActivity;
import awais.instagrabber.adapters.UserSearchResultsAdapter;
import awais.instagrabber.customviews.helpers.TextWatcherAdapter;
import awais.instagrabber.databinding.FragmentUserSearchBinding;
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient;
import awais.instagrabber.utils.TextUtils;
import awais.instagrabber.utils.Utils;
import awais.instagrabber.utils.ViewUtils;
import awais.instagrabber.viewmodels.UserSearchViewModel;
public class UserSearchFragment extends Fragment {
private static final String TAG = UserSearchFragment.class.getSimpleName();
private FragmentUserSearchBinding binding;
private UserSearchViewModel viewModel;
private UserSearchResultsAdapter resultsAdapter;
private int paddingOffset;
private final int windowWidth = Utils.displayMetrics.widthPixels;
private final int minInputWidth = Utils.convertDpToPx(50);
private String actionLabel;
private String title;
private boolean multiple;
@Override
public void onCreate(@Nullable final Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
}
@Nullable
@Override
public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) {
binding = FragmentUserSearchBinding.inflate(inflater, container, false);
viewModel = new ViewModelProvider(this).get(UserSearchViewModel.class);
return binding.getRoot();
}
@Override
public void onViewCreated(@NonNull final View view, @Nullable final Bundle savedInstanceState) {
paddingOffset = binding.search.getPaddingStart() + binding.search.getPaddingEnd() + binding.group
.getPaddingStart() + binding.group.getPaddingEnd() + binding.group.getChipSpacingHorizontal();
init();
}
@Override
public void onDestroyView() {
super.onDestroyView();
viewModel.cleanup();
}
private void init() {
final Bundle arguments = getArguments();
if (arguments != null) {
final UserSearchFragmentArgs fragmentArgs = UserSearchFragmentArgs.fromBundle(arguments);
actionLabel = fragmentArgs.getActionLabel();
title = fragmentArgs.getTitle();
multiple = fragmentArgs.getMultiple();
viewModel.setHideThreadIds(fragmentArgs.getHideThreadIds());
viewModel.setHideUserIds(fragmentArgs.getHideUserIds());
viewModel.setSearchMode(fragmentArgs.getSearchMode());
viewModel.setShowGroups(fragmentArgs.getShowGroups());
}
setupTitles();
setupInput();
setupResults();
setupObservers();
// show cached results
viewModel.showCachedResults();
}
private void setupTitles() {
if (!TextUtils.isEmpty(actionLabel)) {
binding.done.setText(actionLabel);
}
if (!TextUtils.isEmpty(title)) {
final MainActivity activity = (MainActivity) getActivity();
if (activity != null) {
final ActionBar actionBar = activity.getSupportActionBar();
if (actionBar != null) {
actionBar.setTitle(title);
}
}
}
}
private void setupResults() {
final Context context = getContext();
if (context == null) return;
binding.results.setLayoutManager(new LinearLayoutManager(context));
resultsAdapter = new UserSearchResultsAdapter(multiple, (position, recipient, selected) -> {
if (!multiple) {
final NavController navController = NavHostFragment.findNavController(this);
if (!setResult(navController, recipient)) return;
navController.navigateUp();
return;
}
viewModel.setSelectedRecipient(recipient, !selected);
resultsAdapter.setSelectedRecipient(recipient, !selected);
if (!selected) {
createChip(recipient);
return;
}
final View chip = findChip(recipient);
if (chip == null) return;
removeChipFromGroup(chip);
});
binding.results.setAdapter(resultsAdapter);
binding.done.setOnClickListener(v -> {
final NavController navController = NavHostFragment.findNavController(this);
if (!setResult(navController, viewModel.getSelectedRecipients())) return;
navController.navigateUp();
});
}
private boolean setResult(@NonNull final NavController navController, final RankedRecipient rankedRecipient) {
final NavBackStackEntry navBackStackEntry = navController.getPreviousBackStackEntry();
if (navBackStackEntry == null) return false;
final SavedStateHandle savedStateHandle = navBackStackEntry.getSavedStateHandle();
savedStateHandle.set("result", rankedRecipient);
return true;
}
private boolean setResult(@NonNull final NavController navController, final Set<RankedRecipient> rankedRecipients) {
final NavBackStackEntry navBackStackEntry = navController.getPreviousBackStackEntry();
if (navBackStackEntry == null) return false;
final SavedStateHandle savedStateHandle = navBackStackEntry.getSavedStateHandle();
savedStateHandle.set("result", rankedRecipients);
return true;
}
private void setupInput() {
binding.search.addTextChangedListener(new TextWatcherAdapter() {
@Override
public void onTextChanged(final CharSequence s, final int start, final int before, final int count) {
// if (TextUtils.isEmpty(s)) {
// viewModel.cancelSearch();
// viewModel.clearResults();
// return;
// }
viewModel.search(s == null ? null : s.toString().trim());
}
});
binding.search.setOnKeyListener((v, keyCode, event) -> {
if (event != null && event.getAction() == KeyEvent.ACTION_DOWN && event.getKeyCode() == KeyEvent.KEYCODE_DEL) {
final View chip = getLastChip();
if (chip == null) return false;
removeChip(chip);
}
return false;
});
binding.group.setOnHierarchyChangeListener(new ViewGroup.OnHierarchyChangeListener() {
@Override
public void onChildViewAdded(final View parent, final View child) {}
@Override
public void onChildViewRemoved(final View parent, final View child) {
binding.group.post(() -> {
TransitionManager.beginDelayedTransition(binding.getRoot());
calculateInputWidth(0);
});
}
});
}
private void setupObservers() {
viewModel.getRecipients().observe(getViewLifecycleOwner(), results -> {
if (results == null) return;
switch (results.status) {
case SUCCESS:
if (results.data != null) {
resultsAdapter.submitList(results.data);
}
break;
case ERROR:
if (results.message != null) {
Snackbar.make(binding.getRoot(), results.message, Snackbar.LENGTH_LONG).show();
}
if (results.resId != 0) {
Snackbar.make(binding.getRoot(), results.resId, Snackbar.LENGTH_LONG).show();
}
if (results.data != null) {
resultsAdapter.submitList(results.data);
}
break;
case LOADING:
//noinspection DuplicateBranchesInSwitch
if (results.data != null) {
resultsAdapter.submitList(results.data);
}
break;
}
});
viewModel.showAction().observe(getViewLifecycleOwner(), showAction -> binding.done.setVisibility(showAction ? View.VISIBLE : View.GONE));
}
private void createChip(final RankedRecipient recipient) {
final Context context = getContext();
if (context == null) return;
final Chip chip = new Chip(context);
chip.setTag(recipient);
chip.setText(getRecipientText(recipient));
chip.setCloseIconVisible(true);
chip.setOnCloseIconClickListener(v -> removeChip(chip));
binding.group.post(() -> {
final Pair<Integer, Integer> measure = ViewUtils.measure(chip, binding.group);
TransitionManager.beginDelayedTransition(binding.getRoot());
calculateInputWidth(measure.second != null ? measure.second : 0);
binding.group.addView(chip, binding.group.getChildCount() - 1);
});
}
private String getRecipientText(final RankedRecipient recipient) {
if (recipient == null) return null;
if (recipient.getUser() != null) {
return recipient.getUser().getFullName();
}
if (recipient.getThread() != null) {
return recipient.getThread().getThreadTitle();
}
return null;
}
private void removeChip(@NonNull final View chip) {
final RankedRecipient recipient = (RankedRecipient) chip.getTag();
if (recipient == null) return;
viewModel.setSelectedRecipient(recipient, false);
resultsAdapter.setSelectedRecipient(recipient, false);
removeChipFromGroup(chip);
}
private View findChip(final RankedRecipient recipient) {
if (recipient == null || recipient.getUser() == null && recipient.getThread() == null) return null;
boolean isUser = recipient.getUser() != null;
final int childCount = binding.group.getChildCount();
if (childCount == 0) return null;
for (int i = childCount - 1; i >= 0; i--) {
final View child = binding.group.getChildAt(i);
if (child == null) continue;
final RankedRecipient tag = (RankedRecipient) child.getTag();
if (tag == null || isUser && tag.getUser() == null || !isUser && tag.getThread() == null) continue;
if ((isUser && tag.getUser().getPk() == recipient.getUser().getPk())
|| (!isUser && Objects.equals(tag.getThread().getThreadId(), recipient.getThread().getThreadId()))) {
return child;
}
}
return null;
}
private void removeChipFromGroup(final View chip) {
binding.group.post(() -> {
TransitionManager.beginDelayedTransition(binding.getRoot());
binding.group.removeView(chip);
});
}
private void calculateInputWidth(final int newChipWidth) {
final View lastChip = getLastChip();
int lastRight = lastChip != null ? lastChip.getRight() : 0;
final int remainingSpaceInRow = windowWidth - lastRight;
if (remainingSpaceInRow < newChipWidth) {
// next chip will go to the next row, so assume no chips present
lastRight = 0;
}
final int newRight = lastRight + newChipWidth;
final int newInputWidth = windowWidth - newRight - paddingOffset;
binding.search.getLayoutParams().width = newInputWidth < minInputWidth ? windowWidth : newInputWidth;
binding.search.requestLayout();
}
private View getLastChip() {
final int childCount = binding.group.getChildCount();
if (childCount == 0) {
return null;
}
for (int i = childCount - 1; i >= 0; i--) {
final View child = binding.group.getChildAt(i);
if (child instanceof Chip) {
return child;
}
}
return null;
}
}

252
app/src/main/java/awais/instagrabber/fragments/UserSearchFragment.kt

@ -0,0 +1,252 @@
package awais.instagrabber.fragments
import android.os.Bundle
import android.view.KeyEvent
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.view.ViewGroup.OnHierarchyChangeListener
import androidx.fragment.app.Fragment
import androidx.fragment.app.viewModels
import androidx.navigation.NavController
import androidx.navigation.fragment.NavHostFragment
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.transition.TransitionManager
import awais.instagrabber.activities.MainActivity
import awais.instagrabber.adapters.UserSearchResultsAdapter
import awais.instagrabber.customviews.helpers.TextWatcherAdapter
import awais.instagrabber.databinding.FragmentUserSearchBinding
import awais.instagrabber.models.Resource
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
import awais.instagrabber.utils.Utils
import awais.instagrabber.utils.extensions.trimAll
import awais.instagrabber.utils.measure
import awais.instagrabber.viewmodels.UserSearchViewModel
import com.google.android.material.chip.Chip
import com.google.android.material.snackbar.Snackbar
class UserSearchFragment : Fragment() {
private lateinit var binding: FragmentUserSearchBinding
private var resultsAdapter: UserSearchResultsAdapter? = null
private var paddingOffset = 0
private var actionLabel: String? = null
private var title: String? = null
private var multiple = false
private val viewModel: UserSearchViewModel by viewModels()
private val windowWidth = Utils.displayMetrics.widthPixels
private val minInputWidth = Utils.convertDpToPx(50f)
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
binding = FragmentUserSearchBinding.inflate(inflater, container, false)
return binding.root
}
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
paddingOffset = with(binding) {
search.paddingStart + search.paddingEnd + group.paddingStart + group.paddingEnd + group.chipSpacingHorizontal
}
init()
}
override fun onDestroyView() {
super.onDestroyView()
viewModel.cleanup()
}
private fun init() {
val arguments = arguments
if (arguments != null) {
val fragmentArgs = UserSearchFragmentArgs.fromBundle(arguments)
actionLabel = fragmentArgs.actionLabel
title = fragmentArgs.title
multiple = fragmentArgs.multiple
viewModel.setHideThreadIds(fragmentArgs.hideThreadIds)
viewModel.setHideUserIds(fragmentArgs.hideUserIds)
viewModel.setSearchMode(fragmentArgs.searchMode)
viewModel.setShowGroups(fragmentArgs.showGroups)
}
setupTitles()
setupInput()
setupResults()
setupObservers()
// show cached results
viewModel.showCachedResults()
}
private fun setupTitles() {
if (!actionLabel.isNullOrBlank()) {
binding.done.text = actionLabel
}
if (title.isNullOrBlank()) return
(activity as MainActivity?)?.supportActionBar?.title = title
}
private fun setupResults() {
val context = context ?: return
binding.results.layoutManager = LinearLayoutManager(context)
resultsAdapter = UserSearchResultsAdapter(multiple) { _: Int, recipient: RankedRecipient, selected: Boolean ->
if (!multiple) {
val navController = NavHostFragment.findNavController(this)
if (!setResult(navController, recipient)) return@UserSearchResultsAdapter
navController.navigateUp()
return@UserSearchResultsAdapter
}
viewModel.setSelectedRecipient(recipient, !selected)
resultsAdapter?.setSelectedRecipient(recipient, !selected)
if (!selected) {
createChip(recipient)
return@UserSearchResultsAdapter
}
val chip = findChip(recipient) ?: return@UserSearchResultsAdapter
removeChipFromGroup(chip)
}
binding.results.adapter = resultsAdapter
binding.done.setOnClickListener {
val navController = NavHostFragment.findNavController(this)
if (!setResult(navController, viewModel.selectedRecipients)) return@setOnClickListener
navController.navigateUp()
}
}
private fun setResult(navController: NavController, rankedRecipient: RankedRecipient): Boolean {
navController.previousBackStackEntry?.savedStateHandle?.set("result", rankedRecipient) ?: return false
return true
}
private fun setResult(navController: NavController, rankedRecipients: Set<RankedRecipient>): Boolean {
navController.previousBackStackEntry?.savedStateHandle?.set("result", rankedRecipients) ?: return false
return true
}
private fun setupInput() {
binding.search.addTextChangedListener(object : TextWatcherAdapter() {
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) {
viewModel.search(s.toString().trimAll())
}
})
binding.search.setOnKeyListener { _: View?, _: Int, event: KeyEvent? ->
if (event != null && event.action == KeyEvent.ACTION_DOWN && event.keyCode == KeyEvent.KEYCODE_DEL) {
val chip = lastChip ?: return@setOnKeyListener false
removeChip(chip)
}
false
}
binding.group.setOnHierarchyChangeListener(object : OnHierarchyChangeListener {
override fun onChildViewAdded(parent: View, child: View) {}
override fun onChildViewRemoved(parent: View, child: View) {
binding.group.post {
TransitionManager.beginDelayedTransition(binding.root)
calculateInputWidth(0)
}
}
})
}
private fun setupObservers() {
viewModel.recipients.observe(viewLifecycleOwner) {
if (it == null) return@observe
when (it.status) {
Resource.Status.SUCCESS -> if (it.data != null) {
resultsAdapter?.submitList(it.data)
}
Resource.Status.ERROR -> {
if (it.message != null) {
Snackbar.make(binding.root, it.message, Snackbar.LENGTH_LONG).show()
}
if (it.resId != 0) {
Snackbar.make(binding.root, it.resId, Snackbar.LENGTH_LONG).show()
}
if (it.data != null) {
resultsAdapter?.submitList(it.data)
}
}
Resource.Status.LOADING -> if (it.data != null) {
resultsAdapter?.submitList(it.data)
}
}
}
viewModel.showAction().observe(viewLifecycleOwner) { binding.done.visibility = if (it) View.VISIBLE else View.GONE }
}
private fun createChip(recipient: RankedRecipient) {
val context = context ?: return
val chip = Chip(context).apply {
tag = recipient
text = getRecipientText(recipient)
isCloseIconVisible = true
setOnCloseIconClickListener { removeChip(this) }
}
binding.group.post {
val measure = measure(chip, binding.group)
TransitionManager.beginDelayedTransition(binding.root)
calculateInputWidth(if (measure.second != null) measure.second else 0)
binding.group.addView(chip, binding.group.childCount - 1)
}
}
private fun getRecipientText(recipient: RankedRecipient?): String? = when {
recipient == null -> null
recipient.user != null -> recipient.user.fullName
recipient.thread != null -> recipient.thread.threadTitle
else -> null
}
private fun removeChip(chip: View) {
val recipient = chip.tag as RankedRecipient
viewModel.setSelectedRecipient(recipient, false)
resultsAdapter?.setSelectedRecipient(recipient, false)
removeChipFromGroup(chip)
}
private fun findChip(recipient: RankedRecipient?): View? {
if (recipient == null || recipient.user == null && recipient.thread == null) return null
val isUser = recipient.user != null
val childCount = binding.group.childCount
if (childCount == 0) return null
for (i in childCount - 1 downTo 0) {
val child = binding.group.getChildAt(i) ?: continue
val tag = child.tag as RankedRecipient
if (isUser && tag.user == null || !isUser && tag.thread == null) continue
if (isUser && tag.user?.pk == recipient.user?.pk || !isUser && tag.thread?.threadId == recipient.thread?.threadId) {
return child
}
}
return null
}
private fun removeChipFromGroup(chip: View) {
binding.group.post {
TransitionManager.beginDelayedTransition(binding.root)
binding.group.removeView(chip)
}
}
private fun calculateInputWidth(newChipWidth: Int) {
var lastRight = lastChip?.right ?: 0
val remainingSpaceInRow = windowWidth - lastRight
if (remainingSpaceInRow < newChipWidth) {
// next chip will go to the next row, so assume no chips present
lastRight = 0
}
val newRight = lastRight + newChipWidth
val newInputWidth = windowWidth - newRight - paddingOffset
binding.search.layoutParams.width = if (newInputWidth < minInputWidth) windowWidth else newInputWidth
binding.search.requestLayout()
}
private val lastChip: View?
get() {
val childCount = binding.group.childCount
if (childCount == 0) return null
for (i in childCount - 1 downTo 0) {
val child = binding.group.getChildAt(i)
if (child is Chip) {
return child
}
}
return null
}
}

2
app/src/main/java/awais/instagrabber/fragments/UserSearchMode.kt

@ -1,6 +1,6 @@
package awais.instagrabber.fragments package awais.instagrabber.fragments
enum class UserSearchMode(name: String) {
enum class UserSearchMode(val mode: String) {
USER_SEARCH("user_name"), USER_SEARCH("user_name"),
RAVEN("raven"), RAVEN("raven"),
RESHARE("reshare"); RESHARE("reshare");

49
app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.kt

@ -7,10 +7,8 @@ import android.os.Handler
import android.os.Looper import android.os.Looper
import android.util.Log import android.util.Log
import android.view.* import android.view.*
import androidx.coordinatorlayout.widget.CoordinatorLayout
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.activityViewModels import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.LinearLayoutManager
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener
@ -33,18 +31,16 @@ class DirectMessageInboxFragment : Fragment(), OnRefreshListener {
private val viewModel: DirectInboxViewModel by activityViewModels() private val viewModel: DirectInboxViewModel by activityViewModels()
private lateinit var fragmentActivity: MainActivity private lateinit var fragmentActivity: MainActivity
private lateinit var root: CoordinatorLayout
private lateinit var binding: FragmentDirectMessagesInboxBinding private lateinit var binding: FragmentDirectMessagesInboxBinding
private lateinit var inboxAdapter: DirectMessageInboxAdapter
private lateinit var lazyLoader: RecyclerLazyLoaderAtEdge private lateinit var lazyLoader: RecyclerLazyLoaderAtEdge
private var shouldRefresh = true
private var scrollToTop = false private var scrollToTop = false
private var navigating = false private var navigating = false
private var threadsObserver: Observer<List<DirectThread?>>? = null
private var pendingRequestsMenuItem: MenuItem? = null private var pendingRequestsMenuItem: MenuItem? = null
private var pendingRequestTotalBadgeDrawable: BadgeDrawable? = null private var pendingRequestTotalBadgeDrawable: BadgeDrawable? = null
private var isPendingRequestTotalBadgeAttached = false private var isPendingRequestTotalBadgeAttached = false
private var inboxAdapter: DirectMessageInboxAdapter? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -57,17 +53,11 @@ class DirectMessageInboxFragment : Fragment(), OnRefreshListener {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle?, savedInstanceState: Bundle?,
): View { ): View {
if (this::root.isInitialized) {
shouldRefresh = false
return root
}
binding = FragmentDirectMessagesInboxBinding.inflate(inflater, container, false) binding = FragmentDirectMessagesInboxBinding.inflate(inflater, container, false)
root = binding.root
return root
return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
if (!shouldRefresh) return
init() init()
} }
@ -90,11 +80,6 @@ class DirectMessageInboxFragment : Fragment(), OnRefreshListener {
} }
} }
override fun onResume() {
super.onResume()
setupObservers()
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.dm_inbox_menu, menu) inflater.inflate(R.menu.dm_inbox_menu, menu)
pendingRequestsMenuItem = menu.findItem(R.id.pending_requests) pendingRequestsMenuItem = menu.findItem(R.id.pending_requests)
@ -119,23 +104,14 @@ class DirectMessageInboxFragment : Fragment(), OnRefreshListener {
init() init()
} }
override fun onDestroy() {
super.onDestroy()
removeViewModelObservers()
viewModel.onDestroy()
}
private fun setupObservers() { private fun setupObservers() {
removeViewModelObservers()
threadsObserver = Observer { list: List<DirectThread?> ->
if (!this::inboxAdapter.isInitialized) return@Observer
inboxAdapter.submitList(list) {
viewModel.threads.observe(viewLifecycleOwner, { list: List<DirectThread?> ->
inboxAdapter?.submitList(list) {
if (!scrollToTop) return@submitList if (!scrollToTop) return@submitList
binding.inboxList.post { binding.inboxList.smoothScrollToPosition(0) } binding.inboxList.post { binding.inboxList.smoothScrollToPosition(0) }
scrollToTop = false scrollToTop = false
} }
}
threadsObserver?.let { viewModel.threads.observe(fragmentActivity, it) }
})
viewModel.inbox.observe(viewLifecycleOwner, { inboxResource: Resource<DirectInbox?>? -> viewModel.inbox.observe(viewLifecycleOwner, { inboxResource: Resource<DirectInbox?>? ->
if (inboxResource == null) return@observe if (inboxResource == null) return@observe
when (inboxResource.status) { when (inboxResource.status) {
@ -191,11 +167,6 @@ class DirectMessageInboxFragment : Fragment(), OnRefreshListener {
} }
} }
private fun removeViewModelObservers() {
threadsObserver?.let { viewModel.threads.removeObserver(it) }
// no need to explicitly remove observers whose lifecycle owner is getViewLifecycleOwner
}
private fun init() { private fun init() {
val context = context ?: return val context = context ?: return
setupObservers() setupObservers()
@ -218,10 +189,12 @@ class DirectMessageInboxFragment : Fragment(), OnRefreshListener {
} }
} }
navigating = false navigating = false
}.also {
it.setHasStableIds(true)
} }
inboxAdapter.setHasStableIds(true)
binding.inboxList.adapter = inboxAdapter binding.inboxList.adapter = inboxAdapter
lazyLoader = RecyclerLazyLoaderAtEdge(layoutManager) { viewModel.fetchInbox() }
lazyLoader.let { binding.inboxList.addOnScrollListener(it) }
lazyLoader = RecyclerLazyLoaderAtEdge(layoutManager) { viewModel.fetchInbox() }.also {
binding.inboxList.addOnScrollListener(it)
}
} }
} }

57
app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt

@ -13,7 +13,6 @@ import android.view.*
import android.widget.Toast import android.widget.Toast
import androidx.activity.OnBackPressedCallback import androidx.activity.OnBackPressedCallback
import androidx.appcompat.content.res.AppCompatResources import androidx.appcompat.content.res.AppCompatResources
import androidx.constraintlayout.motion.widget.MotionLayout
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentTransaction import androidx.fragment.app.FragmentTransaction
import androidx.lifecycle.MutableLiveData import androidx.lifecycle.MutableLiveData
@ -75,16 +74,26 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall
private var selectedMedia: List<Media>? = null private var selectedMedia: List<Media>? = null
private var actionMode: ActionMode? = null private var actionMode: ActionMode? = null
private var disableDm: Boolean = false private var disableDm: Boolean = false
private var shouldRefresh: Boolean = true
// private var shouldRefresh: Boolean = true
private var highlightsAdapter: HighlightsAdapter? = null private var highlightsAdapter: HighlightsAdapter? = null
private var layoutPreferences = Utils.getPostsLayoutPreferences(Constants.PREF_PROFILE_POSTS_LAYOUT) private var layoutPreferences = Utils.getPostsLayoutPreferences(Constants.PREF_PROFILE_POSTS_LAYOUT)
private lateinit var mainActivity: MainActivity private lateinit var mainActivity: MainActivity
private lateinit var root: MotionLayout
// private lateinit var root: MotionLayout
private lateinit var binding: FragmentProfileBinding private lateinit var binding: FragmentProfileBinding
private lateinit var appStateViewModel: AppStateViewModel private lateinit var appStateViewModel: AppStateViewModel
private lateinit var viewModel: ProfileFragmentViewModel private lateinit var viewModel: ProfileFragmentViewModel
private val userRepository by lazy { UserRepository.getInstance() }
private val friendshipRepository by lazy { FriendshipRepository.getInstance() }
private val storiesRepository by lazy { StoriesRepository.getInstance() }
private val mediaRepository by lazy { MediaRepository.getInstance() }
private val graphQLRepository by lazy { GraphQLRepository.getInstance() }
private val favoriteRepository by lazy { FavoriteRepository.getInstance(requireContext()) }
private val directMessagesRepository by lazy { DirectMessagesRepository.getInstance() }
private val confirmDialogFragmentRequestCode = 100 private val confirmDialogFragmentRequestCode = 100
private val ppOptsDialogRequestCode = 101 private val ppOptsDialogRequestCode = 101
private val bioDialogRequestCode = 102 private val bioDialogRequestCode = 102
@ -309,7 +318,15 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall
viewModel = ViewModelProvider( viewModel = ViewModelProvider(
this, this,
ProfileFragmentViewModelFactory( ProfileFragmentViewModelFactory(
FavoriteRepository.getInstance(requireContext()),
csrfToken,
deviceUuid,
userRepository,
friendshipRepository,
storiesRepository,
mediaRepository,
graphQLRepository,
favoriteRepository,
directMessagesRepository,
if (isLoggedIn) DirectMessagesManager else null, if (isLoggedIn) DirectMessagesManager else null,
this, this,
arguments arguments
@ -319,23 +336,12 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall
} }
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View { override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
if (this::root.isInitialized) {
shouldRefresh = false
return root
}
appStateViewModel.currentUserLiveData.observe(viewLifecycleOwner, viewModel::setCurrentUser)
binding = FragmentProfileBinding.inflate(inflater, container, false) binding = FragmentProfileBinding.inflate(inflater, container, false)
root = binding.root
return root
return binding.root
} }
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
if (!shouldRefresh) {
setupObservers()
return
}
init() init()
shouldRefresh = false
} }
override fun onRefresh() { override fun onRefresh() {
@ -383,6 +389,11 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall
} }
} }
override fun onDestroyView() {
super.onDestroyView()
setupPostsDone = false
}
private fun shareProfileViaDm() { private fun shareProfileViaDm() {
try { try {
val actionToUserSearch = ProfileFragmentDirections.actionToUserSearch().apply { val actionToUserSearch = ProfileFragmentDirections.actionToUserSearch().apply {
@ -428,7 +439,11 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall
} }
private fun setupObservers() { private fun setupObservers() {
viewModel.isLoggedIn.observe(viewLifecycleOwner) {} // observe so that `isLoggedIn.value` is correct
appStateViewModel.currentUserLiveData.observe(viewLifecycleOwner, viewModel::setCurrentUser)
viewModel.isLoggedIn.observe(viewLifecycleOwner) {
// observe so that `isLoggedIn.value` is correct
Log.d(TAG, "setupObservers: $it")
}
viewModel.currentUserProfileActionLiveData.observe(viewLifecycleOwner) { viewModel.currentUserProfileActionLiveData.observe(viewLifecycleOwner) {
val (currentUserResource, profileResource) = it val (currentUserResource, profileResource) = it
if (currentUserResource.status == Resource.Status.ERROR || profileResource.status == Resource.Status.ERROR) { if (currentUserResource.status == Resource.Status.ERROR || profileResource.status == Resource.Status.ERROR) {
@ -453,7 +468,7 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall
context?.let { ctx -> Toast.makeText(ctx, R.string.error_loading_profile, Toast.LENGTH_LONG).show() } context?.let { ctx -> Toast.makeText(ctx, R.string.error_loading_profile, Toast.LENGTH_LONG).show() }
return@observe return@observe
} }
root.loadLayoutDescription(R.xml.header_list_scene)
binding.root.loadLayoutDescription(R.xml.header_list_scene)
setupFavChip(profile, currentUser) setupFavChip(profile, currentUser)
setupFavButton(currentUser, profile) setupFavButton(currentUser, profile)
setupSavedButton(currentUser, profile) setupSavedButton(currentUser, profile)
@ -533,7 +548,7 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall
binding.privatePage2.visibility = VISIBLE binding.privatePage2.visibility = VISIBLE
binding.postsRecyclerView.visibility = GONE binding.postsRecyclerView.visibility = GONE
binding.swipeRefreshLayout.isRefreshing = false binding.swipeRefreshLayout.isRefreshing = false
root.getTransition(R.id.transition)?.setEnable(false)
binding.root.getTransition(R.id.transition)?.setEnable(false)
} }
private fun setupProfileContext(contextPair: Pair<String?, List<UserProfileContextLink>?>) { private fun setupProfileContext(contextPair: Pair<String?, List<UserProfileContextLink>?>) {
@ -837,7 +852,7 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall
} }
private fun showDefaultMessage() { private fun showDefaultMessage() {
root.loadLayoutDescription(R.xml.profile_fragment_no_acc_layout)
binding.root.loadLayoutDescription(R.xml.profile_fragment_no_acc_layout)
binding.privatePage1.visibility = View.VISIBLE binding.privatePage1.visibility = View.VISIBLE
binding.privatePage2.visibility = View.VISIBLE binding.privatePage2.visibility = View.VISIBLE
binding.privatePage1.setImageResource(R.drawable.ic_outline_info_24) binding.privatePage1.setImageResource(R.drawable.ic_outline_info_24)
@ -872,7 +887,7 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
super.onScrolled(recyclerView, dx, dy) super.onScrolled(recyclerView, dx, dy)
val canScrollVertically = recyclerView.canScrollVertically(-1) val canScrollVertically = recyclerView.canScrollVertically(-1)
root.getTransition(R.id.transition)?.setEnable(!canScrollVertically)
binding.root.getTransition(R.id.transition)?.setEnable(!canScrollVertically)
} }
}) })
setupPostsDone = true setupPostsDone = true

2
app/src/main/java/awais/instagrabber/fragments/settings/GeneralPreferencesFragment.java

@ -59,7 +59,7 @@ public class GeneralPreferencesFragment extends BasePreferencesFragment implemen
.map(Tab::getTitle) .map(Tab::getTitle)
.toArray(String[]::new); .toArray(String[]::new);
final String[] navGraphFileNames = tabs.stream() final String[] navGraphFileNames = tabs.stream()
.map(tab -> NavigationHelperKt.geNavGraphNameForNavRootId(tab.getNavigationRootId()))
.map(tab -> NavigationHelperKt.getNavGraphNameForNavRootId(tab.getNavigationRootId()))
.toArray(String[]::new); .toArray(String[]::new);
preference.setKey(Constants.DEFAULT_TAB); preference.setKey(Constants.DEFAULT_TAB);
preference.setTitle(R.string.pref_start_screen); preference.setTitle(R.string.pref_start_screen);

6
app/src/main/java/awais/instagrabber/models/Tab.kt

@ -2,12 +2,18 @@ package awais.instagrabber.models
import androidx.annotation.DrawableRes import androidx.annotation.DrawableRes
import androidx.annotation.IdRes import androidx.annotation.IdRes
import androidx.annotation.NavigationRes
data class Tab( data class Tab(
@param:DrawableRes val iconResId: Int, @param:DrawableRes val iconResId: Int,
val title: String, val title: String,
val isRemovable: Boolean, val isRemovable: Boolean,
/**
* This is the actual resource id of the navigation resource (R.navigation.graphName = navigationResId)
*/
@param:NavigationRes val navigationResId: Int,
/** /**
* This is the resource id of the root navigation tag of the navigation resource. * This is the resource id of the root navigation tag of the navigation resource.
* *

23
app/src/main/java/awais/instagrabber/utils/BarinstaDeepLinkHelper.kt

@ -0,0 +1,23 @@
package awais.instagrabber.utils
import android.net.Uri
import androidx.core.net.toUri
private const val domain = "barinsta"
fun getDirectThreadDeepLink(threadId: String, threadTitle: String, isPending: Boolean = false): Uri =
"$domain://dm_thread/$threadId/$threadTitle?pending=${isPending}".toUri()
fun getProfileDeepLink(username: String): Uri = "$domain://profile/$username".toUri()
fun getPostDeepLink(shortCode: String): Uri = "$domain://post/$shortCode".toUri()
fun getLocationDeepLink(locationId: Long): Uri = "$domain://location/$locationId".toUri()
fun getLocationDeepLink(locationId: String): Uri = "$domain://location/$locationId".toUri()
fun getHashtagDeepLink(hashtag: String): Uri = "$domain://hashtag/$hashtag".toUri()
fun getNotificationsDeepLink(type: String, targetId: Long = 0): Uri = "$domain://notifications/$type?targetId=$targetId".toUri()
fun getSearchDeepLink(): Uri = "$domain://search".toUri()

21
app/src/main/java/awais/instagrabber/utils/KeywordsFilterUtils.kt

@ -2,8 +2,8 @@ package awais.instagrabber.utils
import awais.instagrabber.repositories.responses.Media import awais.instagrabber.repositories.responses.Media
import java.util.* import java.util.*
import kotlin.collections.ArrayList
class KeywordsFilterUtils(private val keywords: ArrayList<String>) {
// fun filter(caption: String?): Boolean { // fun filter(caption: String?): Boolean {
// if (caption == null) return false // if (caption == null) return false
// if (keywords.isEmpty()) return false // if (keywords.isEmpty()) return false
@ -14,24 +14,17 @@ class KeywordsFilterUtils(private val keywords: ArrayList<String>) {
// return false // return false
// } // }
fun filter(media: Media?): Boolean {
if (media == null) return false
private fun containsAnyKeyword(keywords: List<String>, media: Media?): Boolean {
if (media == null || keywords.isEmpty()) return false
val (_, text) = media.caption ?: return false val (_, text) = media.caption ?: return false
if (keywords.isEmpty()) return false
val temp = text!!.lowercase(Locale.getDefault()) val temp = text!!.lowercase(Locale.getDefault())
for (s in keywords) {
if (temp.contains(s)) return true
}
return false
}
return keywords.any { temp.contains(it) }
}
fun filter(media: List<Media>?): List<Media>? {
fun filter(keywords: List<String>, media: List<Media>?): List<Media>? {
if (keywords.isEmpty()) return media if (keywords.isEmpty()) return media
if (media == null) return ArrayList() if (media == null) return ArrayList()
val result: MutableList<Media> = ArrayList() val result: MutableList<Media> = ArrayList()
for (m in media) {
if (!filter(m)) result.add(m)
}
media.filterNotTo(result) { containsAnyKeyword(keywords, it) }
return result return result
}
} }

30
app/src/main/java/awais/instagrabber/utils/NavigationHelper.kt

@ -28,7 +28,8 @@ private fun getTabs(
navRootIds: IntArray, navRootIds: IntArray,
isAnon: Boolean = false, isAnon: Boolean = false,
): Pair<List<Tab>, MutableList<Tab>> { ): Pair<List<Tab>, MutableList<Tab>> {
val navGraphNames = getResIdsForNavRootIds(navRootIds, ::geNavGraphNameForNavRootId)
val navGraphNames = getResIdsForNavRootIds(navRootIds, ::getNavGraphNameForNavRootId)
val navGraphResIds = getResIdsForNavRootIds(navRootIds, ::getNavGraphResIdForNavRootId)
val titleArray = getResIdsForNavRootIds(navRootIds, ::getTitleResIdForNavRootId) val titleArray = getResIdsForNavRootIds(navRootIds, ::getTitleResIdForNavRootId)
val iconIds = getResIdsForNavRootIds(navRootIds, ::getIconResIdForNavRootId) val iconIds = getResIdsForNavRootIds(navRootIds, ::getIconResIdForNavRootId)
val startDestFragIds = getResIdsForNavRootIds(navRootIds, ::getStartDestFragIdForNavRootId) val startDestFragIds = getResIdsForNavRootIds(navRootIds, ::getStartDestFragIdForNavRootId)
@ -37,15 +38,15 @@ private fun getTabs(
val otherTabs = mutableListOf<Tab>() // Will contain tabs not in current list val otherTabs = mutableListOf<Tab>() // Will contain tabs not in current list
for (i in navRootIds.indices) { for (i in navRootIds.indices) {
val navRootId = navRootIds[i] val navRootId = navRootIds[i]
val navGraphName = navGraphNames[i]
val tab = Tab( val tab = Tab(
iconIds[i], iconIds[i],
context.getString(titleArray[i]), context.getString(titleArray[i]),
if(isAnon) false else !NON_REMOVABLE_NAV_ROOT_IDS.contains(navRootId),
if (isAnon) false else !NON_REMOVABLE_NAV_ROOT_IDS.contains(navRootId),
navGraphResIds[i],
navRootId, navRootId,
startDestFragIds[i] startDestFragIds[i]
) )
if (!isAnon && !orderedGraphNames.contains(navGraphName)) {
if (!isAnon && !orderedGraphNames.contains(navGraphNames[i])) {
otherTabs.add(tab) otherTabs.add(tab)
continue continue
} }
@ -109,7 +110,7 @@ private fun getStartDestFragIdForNavRootId(id: Int): Int = when (id) {
else -> 0 else -> 0
} }
fun geNavGraphNameForNavRootId(id: Int): String = when (id) {
fun getNavGraphNameForNavRootId(id: Int): String = when (id) {
R.id.direct_messages_nav_graph -> "direct_messages_nav_graph" R.id.direct_messages_nav_graph -> "direct_messages_nav_graph"
R.id.feed_nav_graph -> "feed_nav_graph" R.id.feed_nav_graph -> "feed_nav_graph"
R.id.profile_nav_graph -> "profile_nav_graph" R.id.profile_nav_graph -> "profile_nav_graph"
@ -120,7 +121,18 @@ fun geNavGraphNameForNavRootId(id: Int): String = when (id) {
else -> "" else -> ""
} }
private fun geNavGraphNameForNavRootId(navGraphName: String): Int = when (navGraphName) {
fun getNavGraphResIdForNavRootId(id: Int): Int = when (id) {
R.id.direct_messages_nav_graph -> R.navigation.direct_messages_nav_graph
R.id.feed_nav_graph -> R.navigation.feed_nav_graph
R.id.profile_nav_graph -> R.navigation.profile_nav_graph
R.id.discover_nav_graph -> R.navigation.discover_nav_graph
R.id.more_nav_graph -> R.navigation.more_nav_graph
R.id.favorites_nav_graph -> R.navigation.favorites_nav_graph
R.id.notification_viewer_nav_graph -> R.navigation.notification_viewer_nav_graph
else -> 0
}
private fun getNavRootIdForGraphName(navGraphName: String): Int = when (navGraphName) {
"direct_messages_nav_graph" -> R.id.direct_messages_nav_graph "direct_messages_nav_graph" -> R.id.direct_messages_nav_graph
"feed_nav_graph" -> R.id.feed_nav_graph "feed_nav_graph" -> R.id.feed_nav_graph
"profile_nav_graph" -> R.id.profile_nav_graph "profile_nav_graph" -> R.id.profile_nav_graph
@ -139,9 +151,9 @@ private fun getOrderedNavRootIdsFromPref(navGraphNames: List<String>): Pair<List
val newOrderString = top5navGraphNames.joinToString(",") val newOrderString = top5navGraphNames.joinToString(",")
Utils.settingsHelper.putString(PreferenceKeys.PREF_TAB_ORDER, newOrderString) Utils.settingsHelper.putString(PreferenceKeys.PREF_TAB_ORDER, newOrderString)
tabOrderString = newOrderString tabOrderString = newOrderString
return top5navGraphNames to top5navGraphNames.map(::geNavGraphNameForNavRootId)
return top5navGraphNames to top5navGraphNames.map(::getNavRootIdForGraphName)
} }
val orderString = tabOrderString ?: return navGraphNames to navGraphNames.subList(0, 5).map(::geNavGraphNameForNavRootId)
val orderString = tabOrderString ?: return navGraphNames to navGraphNames.subList(0, 5).map(::getNavRootIdForGraphName)
// Make sure that the list from preference does not contain any invalid values // Make sure that the list from preference does not contain any invalid values
val orderGraphNames = orderString val orderGraphNames = orderString
.split(",") .split(",")
@ -153,7 +165,7 @@ private fun getOrderedNavRootIdsFromPref(navGraphNames: List<String>): Pair<List
// Use top 5 entries for default list // Use top 5 entries for default list
navGraphNames.subList(0, 5) navGraphNames.subList(0, 5)
} else orderGraphNames } else orderGraphNames
return graphNames to graphNames.map(::geNavGraphNameForNavRootId)
return graphNames to graphNames.map(::getNavRootIdForGraphName)
} }
fun isNavRootInCurrentTabs(navRootString: String?): Boolean { fun isNavRootInCurrentTabs(navRootString: String?): Boolean {

6
app/src/main/java/awais/instagrabber/viewmodels/FollowViewModel.kt

@ -1,10 +1,6 @@
package awais.instagrabber.viewmodels package awais.instagrabber.viewmodels
import androidx.lifecycle.LiveData
import androidx.lifecycle.MediatorLiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import androidx.lifecycle.*
import awais.instagrabber.models.Resource import awais.instagrabber.models.Resource
import awais.instagrabber.repositories.responses.User import awais.instagrabber.repositories.responses.User
import awais.instagrabber.webservices.FriendshipRepository import awais.instagrabber.webservices.FriendshipRepository

97
app/src/main/java/awais/instagrabber/viewmodels/MediaViewModel.java

@ -1,19 +1,108 @@
package awais.instagrabber.viewmodels; package awais.instagrabber.viewmodels;
import android.util.Log;
import androidx.annotation.NonNull;
import androidx.lifecycle.LiveData;
import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.MutableLiveData;
import androidx.lifecycle.ViewModel; import androidx.lifecycle.ViewModel;
import androidx.lifecycle.ViewModelProvider;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import awais.instagrabber.customviews.helpers.PostFetcher;
import awais.instagrabber.fragments.settings.PreferenceKeys;
import awais.instagrabber.interfaces.FetchListener;
import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.Media;
import awais.instagrabber.utils.KeywordsFilterUtilsKt;
import static awais.instagrabber.utils.Utils.settingsHelper;
public class MediaViewModel extends ViewModel { public class MediaViewModel extends ViewModel {
private MutableLiveData<List<Media>> list;
private static final String TAG = MediaViewModel.class.getSimpleName();
private boolean refresh = true;
private final PostFetcher postFetcher;
private final MutableLiveData<List<Media>> list = new MutableLiveData<>();
public MutableLiveData<List<Media>> getList() {
if (list == null) {
list = new MutableLiveData<>();
public MediaViewModel(@NonNull final PostFetcher.PostFetchService postFetchService) {
final FetchListener<List<Media>> fetchListener = new FetchListener<List<Media>>() {
@Override
public void onResult(final List<Media> result) {
if (refresh) {
list.postValue(filterResult(result, true));
refresh = false;
return;
}
list.postValue(filterResult(result, false));
}
@Override
public void onFailure(final Throwable t) {
Log.e(TAG, "onFailure: ", t);
}
};
postFetcher = new PostFetcher(postFetchService, fetchListener);
} }
@NonNull
private List<Media> filterResult(final List<Media> result, final boolean isRefresh) {
final List<Media> models = list.getValue();
final List<Media> modelsCopy = models == null || isRefresh ? new ArrayList<>() : new ArrayList<>(models);
if (settingsHelper.getBoolean(PreferenceKeys.TOGGLE_KEYWORD_FILTER)) {
final List<String> keywords = new ArrayList<>(settingsHelper.getStringSet(PreferenceKeys.KEYWORD_FILTERS));
final List<Media> filter = KeywordsFilterUtilsKt.filter(keywords, result);
if (filter != null) {
modelsCopy.addAll(filter);
}
return modelsCopy;
}
modelsCopy.addAll(result);
return modelsCopy;
}
public LiveData<List<Media>> getList() {
return list; return list;
} }
public boolean hasMore() {
return postFetcher.hasMore();
}
public void fetch() {
postFetcher.fetch();
}
public void reset() {
postFetcher.reset();
}
public boolean isFetching() {
return postFetcher.isFetching();
}
public void refresh() {
refresh = true;
reset();
fetch();
}
public static class ViewModelFactory implements ViewModelProvider.Factory {
@NonNull
private final PostFetcher.PostFetchService postFetchService;
public ViewModelFactory(@NonNull final PostFetcher.PostFetchService postFetchService) {
this.postFetchService = postFetchService;
}
@NonNull
@Override
public <T extends ViewModel> T create(@NonNull final Class<T> modelClass) {
//noinspection unchecked
return (T) new MediaViewModel(postFetchService);
}
}
} }

61
app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt

@ -16,12 +16,9 @@ import awais.instagrabber.repositories.responses.User
import awais.instagrabber.repositories.responses.UserProfileContextLink import awais.instagrabber.repositories.responses.UserProfileContextLink
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
import awais.instagrabber.repositories.responses.stories.Story import awais.instagrabber.repositories.responses.stories.Story
import awais.instagrabber.utils.Constants
import awais.instagrabber.utils.ControlledRunner import awais.instagrabber.utils.ControlledRunner
import awais.instagrabber.utils.Event import awais.instagrabber.utils.Event
import awais.instagrabber.utils.getCsrfTokenFromCookie
import awais.instagrabber.utils.SingleRunner import awais.instagrabber.utils.SingleRunner
import awais.instagrabber.utils.Utils
import awais.instagrabber.utils.extensions.TAG import awais.instagrabber.utils.extensions.TAG
import awais.instagrabber.utils.extensions.isReallyPrivate import awais.instagrabber.utils.extensions.isReallyPrivate
import awais.instagrabber.viewmodels.ProfileFragmentViewModel.ProfileAction.* import awais.instagrabber.viewmodels.ProfileFragmentViewModel.ProfileAction.*
@ -29,25 +26,24 @@ import awais.instagrabber.viewmodels.ProfileFragmentViewModel.ProfileEvent.*
import awais.instagrabber.webservices.* import awais.instagrabber.webservices.*
import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.CoroutineDispatcher
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.delay
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import java.time.LocalDateTime import java.time.LocalDateTime
class ProfileFragmentViewModel( class ProfileFragmentViewModel(
private val state: SavedStateHandle, private val state: SavedStateHandle,
private val favoriteRepository: FavoriteRepository?,
private val csrfToken: String?,
private val deviceUuid: String?,
private val userRepository: UserRepository,
private val friendshipRepository: FriendshipRepository,
private val storiesRepository: StoriesRepository,
private val mediaRepository: MediaRepository,
private val graphQLRepository: GraphQLRepository,
private val favoriteRepository: FavoriteRepository,
private val directMessagesRepository: DirectMessagesRepository,
private val messageManager: DirectMessagesManager?, private val messageManager: DirectMessagesManager?,
ioDispatcher: CoroutineDispatcher, ioDispatcher: CoroutineDispatcher,
) : ViewModel() { ) : ViewModel() {
private val cookie: String = Utils.settingsHelper.getString(Constants.COOKIE)
private val csrfToken: String? = getCsrfTokenFromCookie(cookie)
private val deviceUuid: String = Utils.settingsHelper.getString(Constants.DEVICE_UUID)
private val userRepository: UserRepository by lazy { UserRepository.getInstance() }
private val friendshipRepository: FriendshipRepository by lazy { FriendshipRepository.getInstance() }
private val storiesRepository: StoriesRepository by lazy { StoriesRepository.getInstance() }
private val mediaRepository: MediaRepository by lazy { MediaRepository.getInstance() }
private val graphQLRepository: GraphQLRepository by lazy { GraphQLRepository.getInstance() }
private val directMessagesRepository: DirectMessagesRepository by lazy { DirectMessagesRepository.getInstance() }
private val _currentUser = MutableLiveData<Resource<User?>>(Resource.loading(null)) private val _currentUser = MutableLiveData<Resource<User?>>(Resource.loading(null))
private val _isFavorite = MutableLiveData(false) private val _isFavorite = MutableLiveData(false)
private val profileAction = MutableLiveData(INIT) private val profileAction = MutableLiveData(INIT)
@ -247,7 +243,7 @@ class ProfileFragmentViewModel(
private suspend fun checkAndUpdateFavorite(fetchedUser: User) { private suspend fun checkAndUpdateFavorite(fetchedUser: User) {
try { try {
val favorite = favoriteRepository!!.getFavorite(fetchedUser.username, FavoriteType.USER)
val favorite = favoriteRepository.getFavorite(fetchedUser.username, FavoriteType.USER)
if (favorite == null) { if (favorite == null) {
_isFavorite.postValue(false) _isFavorite.postValue(false)
return return
@ -295,7 +291,7 @@ class ProfileFragmentViewModel(
viewModelScope.launch(Dispatchers.IO) { viewModelScope.launch(Dispatchers.IO) {
toggleFavoriteControlledRunner.afterPrevious { toggleFavoriteControlledRunner.afterPrevious {
try { try {
val favorite = favoriteRepository!!.getFavorite(username, FavoriteType.USER)
val favorite = favoriteRepository.getFavorite(username, FavoriteType.USER)
if (favorite == null) { if (favorite == null) {
// insert // insert
favoriteRepository.insertOrUpdateFavorite( favoriteRepository.insertOrUpdateFavorite(
@ -330,7 +326,7 @@ class ProfileFragmentViewModel(
val currentUserId = currentUser.value?.data?.pk ?: return@afterPrevious val currentUserId = currentUser.value?.data?.pk ?: return@afterPrevious
val targetUserId = profile.value?.data?.pk ?: return@afterPrevious val targetUserId = profile.value?.data?.pk ?: return@afterPrevious
val csrfToken = csrfToken ?: return@afterPrevious val csrfToken = csrfToken ?: return@afterPrevious
val deviceUuid = deviceUuid
val deviceUuid = deviceUuid ?: return@afterPrevious
if (following) { if (following) {
if (!confirmed) { if (!confirmed) {
_eventLiveData.postValue(Event(ShowConfirmUnfollowDialog)) _eventLiveData.postValue(Event(ShowConfirmUnfollowDialog))
@ -369,7 +365,7 @@ class ProfileFragmentViewModel(
val currentUserId = currentUser.value?.data?.pk ?: return@afterPrevious val currentUserId = currentUser.value?.data?.pk ?: return@afterPrevious
val targetUserId = profile.value?.data?.pk ?: return@afterPrevious val targetUserId = profile.value?.data?.pk ?: return@afterPrevious
val csrfToken = csrfToken ?: return@afterPrevious val csrfToken = csrfToken ?: return@afterPrevious
val deviceUuid = deviceUuid
val deviceUuid = deviceUuid ?: return@afterPrevious
val username = profile.value?.data?.username ?: return@afterPrevious val username = profile.value?.data?.username ?: return@afterPrevious
val thread = directMessagesRepository.createThread( val thread = directMessagesRepository.createThread(
csrfToken, csrfToken,
@ -385,6 +381,7 @@ class ProfileFragmentViewModel(
} }
val threadId = thread.threadId ?: return@afterPrevious val threadId = thread.threadId ?: return@afterPrevious
_eventLiveData.postValue(Event(NavigateToThread(threadId, username))) _eventLiveData.postValue(Event(NavigateToThread(threadId, username)))
delay(200) // Add delay so that the postValue in finally does not overwrite the NavigateToThread event
} catch (e: Exception) { } catch (e: Exception) {
Log.e(TAG, "sendDm: ", e) Log.e(TAG, "sendDm: ", e)
} finally { } finally {
@ -403,7 +400,7 @@ class ProfileFragmentViewModel(
val profile = profile.value?.data ?: return@afterPrevious val profile = profile.value?.data ?: return@afterPrevious
friendshipRepository.toggleRestrict( friendshipRepository.toggleRestrict(
csrfToken ?: return@afterPrevious, csrfToken ?: return@afterPrevious,
deviceUuid,
deviceUuid ?: return@afterPrevious,
profile.pk, profile.pk,
!(profile.friendshipStatus?.isRestricted ?: false), !(profile.friendshipStatus?.isRestricted ?: false),
) )
@ -425,7 +422,7 @@ class ProfileFragmentViewModel(
friendshipRepository.changeBlock( friendshipRepository.changeBlock(
csrfToken ?: return@afterPrevious, csrfToken ?: return@afterPrevious,
currentUser.value?.data?.pk ?: return@afterPrevious, currentUser.value?.data?.pk ?: return@afterPrevious,
deviceUuid,
deviceUuid ?: return@afterPrevious,
profile.friendshipStatus?.blocking ?: return@afterPrevious, profile.friendshipStatus?.blocking ?: return@afterPrevious,
profile.pk profile.pk
) )
@ -447,7 +444,7 @@ class ProfileFragmentViewModel(
friendshipRepository.changeMute( friendshipRepository.changeMute(
csrfToken ?: return@afterPrevious, csrfToken ?: return@afterPrevious,
currentUser.value?.data?.pk ?: return@afterPrevious, currentUser.value?.data?.pk ?: return@afterPrevious,
deviceUuid,
deviceUuid ?: return@afterPrevious,
profile.friendshipStatus?.isMutingReel ?: return@afterPrevious, profile.friendshipStatus?.isMutingReel ?: return@afterPrevious,
profile.pk, profile.pk,
true true
@ -470,7 +467,7 @@ class ProfileFragmentViewModel(
friendshipRepository.changeMute( friendshipRepository.changeMute(
csrfToken ?: return@afterPrevious, csrfToken ?: return@afterPrevious,
currentUser.value?.data?.pk ?: return@afterPrevious, currentUser.value?.data?.pk ?: return@afterPrevious,
deviceUuid,
deviceUuid ?: return@afterPrevious,
profile.friendshipStatus?.muting ?: return@afterPrevious, profile.friendshipStatus?.muting ?: return@afterPrevious,
profile.pk, profile.pk,
false false
@ -492,7 +489,7 @@ class ProfileFragmentViewModel(
friendshipRepository.removeFollower( friendshipRepository.removeFollower(
csrfToken ?: return@afterPrevious, csrfToken ?: return@afterPrevious,
currentUser.value?.data?.pk ?: return@afterPrevious, currentUser.value?.data?.pk ?: return@afterPrevious,
deviceUuid,
deviceUuid ?: return@afterPrevious,
profile.value?.data?.pk ?: return@afterPrevious profile.value?.data?.pk ?: return@afterPrevious
) )
profileAction.postValue(REFRESH_FRIENDSHIP) profileAction.postValue(REFRESH_FRIENDSHIP)
@ -601,7 +598,15 @@ class ProfileFragmentViewModel(
@Suppress("UNCHECKED_CAST") @Suppress("UNCHECKED_CAST")
class ProfileFragmentViewModelFactory( class ProfileFragmentViewModelFactory(
private val favoriteRepository: FavoriteRepository?,
private val csrfToken: String?,
private val deviceUuid: String?,
private val userRepository: UserRepository,
private val friendshipRepository: FriendshipRepository,
private val storiesRepository: StoriesRepository,
private val mediaRepository: MediaRepository,
private val graphQLRepository: GraphQLRepository,
private val favoriteRepository: FavoriteRepository,
private val directMessagesRepository: DirectMessagesRepository,
private val messageManager: DirectMessagesManager?, private val messageManager: DirectMessagesManager?,
owner: SavedStateRegistryOwner, owner: SavedStateRegistryOwner,
defaultArgs: Bundle? = null, defaultArgs: Bundle? = null,
@ -613,7 +618,15 @@ class ProfileFragmentViewModelFactory(
): T { ): T {
return ProfileFragmentViewModel( return ProfileFragmentViewModel(
handle, handle,
csrfToken,
deviceUuid,
userRepository,
friendshipRepository,
storiesRepository,
mediaRepository,
graphQLRepository,
favoriteRepository, favoriteRepository,
directMessagesRepository,
messageManager, messageManager,
Dispatchers.IO, Dispatchers.IO,
) as T ) as T

13
app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt

@ -7,19 +7,22 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import awais.instagrabber.R import awais.instagrabber.R
import awais.instagrabber.managers.DirectMessagesManager import awais.instagrabber.managers.DirectMessagesManager
import awais.instagrabber.models.enums.FavoriteType
import awais.instagrabber.models.enums.MediaItemType
import awais.instagrabber.models.enums.StoryPaginationType
import awais.instagrabber.models.Resource import awais.instagrabber.models.Resource
import awais.instagrabber.models.Resource.Companion.error import awais.instagrabber.models.Resource.Companion.error
import awais.instagrabber.models.Resource.Companion.loading import awais.instagrabber.models.Resource.Companion.loading
import awais.instagrabber.models.Resource.Companion.success import awais.instagrabber.models.Resource.Companion.success
import awais.instagrabber.models.enums.BroadcastItemType import awais.instagrabber.models.enums.BroadcastItemType
import awais.instagrabber.models.enums.FavoriteType
import awais.instagrabber.models.enums.MediaItemType
import awais.instagrabber.models.enums.StoryPaginationType
import awais.instagrabber.repositories.requests.StoryViewerOptions import awais.instagrabber.repositories.requests.StoryViewerOptions
import awais.instagrabber.repositories.responses.Media
import awais.instagrabber.repositories.responses.directmessages.RankedRecipient import awais.instagrabber.repositories.responses.directmessages.RankedRecipient
import awais.instagrabber.repositories.responses.stories.* import awais.instagrabber.repositories.responses.stories.*
import awais.instagrabber.repositories.responses.Media
import awais.instagrabber.utils.*
import awais.instagrabber.utils.Constants
import awais.instagrabber.utils.Utils
import awais.instagrabber.utils.getCsrfTokenFromCookie
import awais.instagrabber.utils.getUserIdFromCookie
import awais.instagrabber.webservices.MediaRepository import awais.instagrabber.webservices.MediaRepository
import awais.instagrabber.webservices.StoriesRepository import awais.instagrabber.webservices.StoriesRepository
import com.google.common.collect.ImmutableList import com.google.common.collect.ImmutableList

2
app/src/main/java/awais/instagrabber/viewmodels/UserSearchViewModel.java

@ -192,7 +192,7 @@ public class UserSearchViewModel extends ViewModel {
private void rankedRecipientSearch() { private void rankedRecipientSearch() {
directMessagesRepository.rankedRecipients( directMessagesRepository.rankedRecipients(
searchMode.name(),
searchMode.getMode(),
showGroups, showGroups,
currentQuery, currentQuery,
CoroutineUtilsKt.getContinuation((response, throwable) -> { CoroutineUtilsKt.getContinuation((response, throwable) -> {

1
app/src/main/java/thoughtbot/expandableadapter/ExpandableGroup.java

@ -1,6 +1,5 @@
package thoughtbot.expandableadapter; package thoughtbot.expandableadapter;
import java.util.ArrayList;
import java.util.List; import java.util.List;
import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.User;

3
app/src/main/res/layout/activity_main.xml

@ -73,5 +73,6 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom" android:layout_gravity="bottom"
app:labelVisibilityMode="auto" />
app:labelVisibilityMode="auto"
tools:menu="@menu/bottom_nav_menu" />
</awais.instagrabber.customviews.InsetsNotifyingCoordinatorLayout> </awais.instagrabber.customviews.InsetsNotifyingCoordinatorLayout>

1
app/src/main/res/layout/fragment_followers_viewer.xml

@ -1,6 +1,5 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<androidx.swiperefreshlayout.widget.SwipeRefreshLayout xmlns:android="http://schemas.android.com/apk/res/android" <androidx.swiperefreshlayout.widget.SwipeRefreshLayout 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" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/swipeRefreshLayout" android:id="@+id/swipeRefreshLayout"
android:layout_width="match_parent" android:layout_width="match_parent"

28
app/src/main/res/menu/bottom_nav_menu.xml

@ -0,0 +1,28 @@
<?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_message_24"
android:contentDescription="@string/title_dm"
android:title="@string/title_dm" />
<item
android:id="@id/feed_nav_graph"
android:icon="@drawable/ic_home_24"
android:contentDescription="@string/feed"
android:title="@string/feed" />
<item
android:id="@id/profile_nav_graph"
android:icon="@drawable/ic_person_24"
android:contentDescription="@string/profile"
android:title="@string/profile" />
<item
android:id="@id/discover_nav_graph"
android:icon="@drawable/ic_explore_24"
android:contentDescription="@string/title_discover"
android:title="@string/title_discover" />
<item
android:id="@id/more_nav_graph"
android:icon="@drawable/ic_more_horiz_24"
android:contentDescription="@string/more"
android:title="@string/more" />
</menu>

333
app/src/main/res/navigation/nav_graph.xml → app/src/main/res/navigation/direct_messages_nav_graph.xml

@ -2,70 +2,22 @@
<navigation xmlns:android="http://schemas.android.com/apk/res/android" <navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/nav_graph"
app:startDestination="@id/feedFragment">
<action
android:id="@+id/action_global_hashTag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_global_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_global_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_global_notifications"
app:destination="@id/notifications_viewer_non_top" />
<action
android:id="@+id/action_global_search"
app:destination="@id/searchFragment" />
<action
android:id="@+id/action_global_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_global_direct_thread"
app:destination="@id/directMessagesThreadFragment" />
android:id="@+id/direct_messages_nav_graph"
app:startDestination="@id/directMessagesInboxFragment">
<fragment <fragment
android:id="@+id/feedFragment"
android:name="awais.instagrabber.fragments.main.FeedFragment"
android:label="@string/feed"
tools:layout="@layout/fragment_feed">
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
android:id="@+id/directMessagesInboxFragment"
android:name="awais.instagrabber.fragments.directmessages.DirectMessageInboxFragment"
android:label="@string/action_dms"
tools:layout="@layout/fragment_direct_messages_inbox">
<action <action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
android:id="@+id/action_to_thread"
app:destination="@id/directMessagesThreadFragment" />
<action <action
android:id="@+id/action_to_story_list"
app:destination="@id/storyListViewerFragment" />
android:id="@+id/action_to_pending_inbox"
app:destination="@id/directPendingInboxFragment" />
</fragment> </fragment>
<fragment <fragment
@ -99,25 +51,6 @@
app:destination="@id/user_search" /> app:destination="@id/user_search" />
</fragment> </fragment>
<fragment
android:id="@+id/searchFragment"
android:name="awais.instagrabber.fragments.search.SearchFragment"
android:label="@string/search"
tools:layout="@layout/fragment_search">
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
</fragment>
<fragment <fragment
android:id="@+id/postViewFragment" android:id="@+id/postViewFragment"
android:name="awais.instagrabber.fragments.PostViewV2Fragment" android:name="awais.instagrabber.fragments.PostViewV2Fragment"
@ -266,63 +199,6 @@
app:destination="@id/profile_non_top" /> app:destination="@id/profile_non_top" />
</dialog> </dialog>
<fragment
android:id="@+id/profileFragment"
android:name="awais.instagrabber.fragments.main.ProfileFragment"
android:label="@string/profile"
tools:layout="@layout/fragment_profile">
<argument
android:name="username"
android:defaultValue=""
app:argType="string"
app:nullable="true" />
<action
android:id="@+id/action_to_saved"
app:destination="@id/savedViewerFragment" />
<action
android:id="@+id/action_to_saved_collections"
app:destination="@id/savedCollectionsFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_follow_viewer"
app:destination="@id/followViewerFragment" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
<action
android:id="@+id/action_to_notifications"
app:destination="@id/notifications_viewer_non_top" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<!-- Copy of profile fragment tag --> <!-- Copy of profile fragment tag -->
<!-- Required to get back arrow in action bar --> <!-- Required to get back arrow in action bar -->
<!-- See https://issuetracker.google.com/issues/192395936 --> <!-- See https://issuetracker.google.com/issues/192395936 -->
@ -470,126 +346,6 @@
app:destination="@id/profile_non_top" /> app:destination="@id/profile_non_top" />
</dialog> </dialog>
<fragment
android:id="@+id/discoverFragment"
android:name="awais.instagrabber.fragments.main.DiscoverFragment"
android:label="@string/title_discover"
tools:layout="@layout/fragment_discover">
<action
android:id="@+id/action_to_topic_posts"
app:destination="@id/topicPostsFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
</fragment>
<fragment
android:id="@+id/topicPostsFragment"
android:name="awais.instagrabber.fragments.TopicPostsFragment"
tools:layout="@layout/fragment_topic_posts">
<argument
android:name="topicCluster"
app:argType="awais.instagrabber.repositories.responses.discover.TopicCluster" />
<argument
android:name="titleColor"
app:argType="integer" />
<argument
android:name="backgroundColor"
app:argType="integer" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/favoritesFragment"
android:name="awais.instagrabber.fragments.FavoritesFragment"
android:label="@string/title_favorites"
tools:layout="@layout/fragment_favorites">
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
</fragment>
<!-- Copy of favorites fragment tag -->
<!-- Required to get back arrow in action bar -->
<!-- See https://issuetracker.google.com/issues/192395936 -->
<fragment
android:id="@+id/favorites_non_top"
android:name="awais.instagrabber.fragments.FavoritesFragment"
android:label="@string/title_favorites"
tools:layout="@layout/fragment_favorites">
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
</fragment>
<fragment
android:id="@+id/notificationsViewer"
android:name="awais.instagrabber.fragments.NotificationsViewerFragment"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications_viewer">
<argument
android:name="type"
android:defaultValue="notif"
app:argType="string"
app:nullable="false" />
<argument
android:name="targetId"
android:defaultValue="0L"
app:argType="long" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
</fragment>
<!-- Copy of notification viewer fragment tag --> <!-- Copy of notification viewer fragment tag -->
<!-- Required to get back arrow in action bar --> <!-- Required to get back arrow in action bar -->
<!-- See https://issuetracker.google.com/issues/192395936 --> <!-- See https://issuetracker.google.com/issues/192395936 -->
@ -677,41 +433,6 @@
app:destination="@id/profile_non_top" /> app:destination="@id/profile_non_top" />
</fragment> </fragment>
<fragment
android:id="@+id/storyListViewerFragment"
android:name="awais.instagrabber.fragments.StoryListViewerFragment"
android:label="Stories"
tools:layout="@layout/fragment_story_list_viewer">
<argument
android:name="type"
app:argType="string"
app:nullable="false" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/directMessagesInboxFragment"
android:name="awais.instagrabber.fragments.directmessages.DirectMessageInboxFragment"
android:label="@string/action_dms"
tools:layout="@layout/fragment_direct_messages_inbox">
<action
android:id="@+id/action_to_thread"
app:destination="@id/directMessagesThreadFragment" />
<action
android:id="@+id/action_to_pending_inbox"
app:destination="@id/directPendingInboxFragment" />
</fragment>
<fragment <fragment
android:id="@+id/directMessagesThreadFragment" android:id="@+id/directMessagesThreadFragment"
android:name="awais.instagrabber.fragments.directmessages.DirectMessageThreadFragment" android:name="awais.instagrabber.fragments.directmessages.DirectMessageThreadFragment"
@ -730,6 +451,8 @@
android:defaultValue="false" android:defaultValue="false"
app:argType="boolean" /> app:argType="boolean" />
<deepLink app:uri="barinsta://dm_thread/{threadId}/{title}?pending={pending}" />
<action <action
android:id="@+id/action_to_settings" android:id="@+id/action_to_settings"
app:destination="@id/directMessagesSettingsFragment" /> app:destination="@id/directMessagesSettingsFragment" />
@ -863,36 +586,4 @@
app:argType="string[]" app:argType="string[]"
app:nullable="true" /> app:nullable="true" />
</fragment> </fragment>
<include app:graph="@navigation/settings_nav_graph" />
<fragment
android:id="@+id/morePreferencesFragment"
android:name="awais.instagrabber.fragments.settings.MorePreferencesFragment"
android:label="@string/more">
<action
android:id="@+id/action_to_settings"
app:destination="@id/settings_nav_graph" />
<action
android:id="@+id/action_to_about"
app:destination="@id/aboutFragment" />
<action
android:id="@+id/action_to_favorites"
app:destination="@id/favorites_non_top" />
<action
android:id="@+id/action_to_backup"
app:destination="@id/backupPreferencesFragment" />
<action
android:id="@+id/action_to_notifications"
app:destination="@id/notifications_viewer_non_top" />
<action
android:id="@+id/action_to_story_list"
app:destination="@id/storyListViewerFragment" />
</fragment>
</navigation> </navigation>

518
app/src/main/res/navigation/discover_nav_graph.xml

@ -0,0 +1,518 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/discover_nav_graph"
app:startDestination="@id/discoverFragment">
<fragment
android:id="@+id/discoverFragment"
android:name="awais.instagrabber.fragments.main.DiscoverFragment"
android:label="@string/title_discover"
tools:layout="@layout/fragment_discover">
<action
android:id="@+id/action_to_topic_posts"
app:destination="@id/topicPostsFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
</fragment>
<fragment
android:id="@+id/storyViewerFragment"
android:name="awais.instagrabber.fragments.StoryViewerFragment"
android:label="StoryViewerFragment"
tools:layout="@layout/fragment_story_viewer">
<argument
android:name="options"
app:argType="awais.instagrabber.repositories.requests.StoryViewerOptions" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
</fragment>
<fragment
android:id="@+id/postViewFragment"
android:name="awais.instagrabber.fragments.PostViewV2Fragment"
android:label="@string/post"
tools:layout="@layout/dialog_post_view">
<argument
android:name="media"
app:argType="awais.instagrabber.repositories.responses.Media"
app:nullable="false" />
<argument
android:name="position"
app:argType="integer" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_likes"
app:destination="@id/likesViewerFragment" />
<action
android:id="@+id/action_to_saved_collections"
app:destination="@id/savedCollectionsFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/locationFragment"
android:name="awais.instagrabber.fragments.LocationFragment"
android:label=""
tools:layout="@layout/fragment_location">
<argument
android:name="locationId"
app:argType="long" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/hashTagFragment"
android:name="awais.instagrabber.fragments.HashTagFragment"
android:label=""
tools:layout="@layout/fragment_hashtag">
<argument
android:name="hashtag"
app:argType="string"
app:nullable="false" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<dialog
android:id="@+id/commentsViewerFragment"
android:name="awais.instagrabber.fragments.comments.CommentsViewerFragment"
android:label="Comments"
tools:layout="@layout/fragment_comments">
<argument
android:name="shortCode"
app:argType="string"
app:nullable="false" />
<argument
android:name="postId"
app:argType="string"
app:nullable="false" />
<argument
android:name="postUserId"
app:argType="long" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_likes"
app:destination="@id/likesViewerFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</dialog>
<!-- Copy of profile fragment tag -->
<!-- Required to get back arrow in action bar -->
<!-- See https://issuetracker.google.com/issues/192395936 -->
<fragment
android:id="@+id/profile_non_top"
android:name="awais.instagrabber.fragments.main.ProfileFragment"
android:label="@string/profile"
tools:layout="@layout/fragment_profile">
<argument
android:name="username"
android:defaultValue=""
app:argType="string"
app:nullable="true" />
<action
android:id="@+id/action_to_saved"
app:destination="@id/savedViewerFragment" />
<action
android:id="@+id/action_to_saved_collections"
app:destination="@id/savedCollectionsFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_follow_viewer"
app:destination="@id/followViewerFragment" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
<action
android:id="@+id/action_to_notifications"
app:destination="@id/notifications_viewer_non_top" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/savedViewerFragment"
android:name="awais.instagrabber.fragments.SavedViewerFragment"
android:label="Saved"
tools:layout="@layout/fragment_saved">
<argument
android:name="username"
app:argType="string"
app:nullable="false" />
<argument
android:name="profileId"
app:argType="long" />
<argument
android:name="type"
app:argType="awais.instagrabber.models.enums.PostItemType"
app:nullable="false" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/followViewerFragment"
android:name="awais.instagrabber.fragments.FollowViewerFragment"
android:label=""
tools:layout="@layout/fragment_followers_viewer">
<argument
android:name="profileId"
app:argType="long" />
<argument
android:name="isFollowersList"
app:argType="boolean"
app:nullable="false" />
<argument
android:name="username"
app:argType="string"
app:nullable="false" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<dialog
android:id="@+id/likesViewerFragment"
android:name="awais.instagrabber.fragments.LikesViewerFragment"
android:label="Comments"
tools:layout="@layout/fragment_likes">
<argument
android:name="postId"
app:argType="string"
app:nullable="false" />
<argument
android:name="isComment"
app:argType="boolean"
app:nullable="false" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</dialog>
<fragment
android:id="@+id/topicPostsFragment"
android:name="awais.instagrabber.fragments.TopicPostsFragment"
tools:layout="@layout/fragment_topic_posts">
<argument
android:name="topicCluster"
app:argType="awais.instagrabber.repositories.responses.discover.TopicCluster" />
<argument
android:name="titleColor"
app:argType="integer" />
<argument
android:name="backgroundColor"
app:argType="integer" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
</fragment>
<!-- Copy of notification viewer fragment tag -->
<!-- Required to get back arrow in action bar -->
<!-- See https://issuetracker.google.com/issues/192395936 -->
<fragment
android:id="@+id/notifications_viewer_non_top"
android:name="awais.instagrabber.fragments.NotificationsViewerFragment"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications_viewer">
<argument
android:name="type"
android:defaultValue="notif"
app:argType="string"
app:nullable="false" />
<argument
android:name="targetId"
android:defaultValue="0L"
app:argType="long" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
</fragment>
<fragment
android:id="@+id/savedCollectionsFragment"
android:name="awais.instagrabber.fragments.SavedCollectionsFragment"
android:label="@string/saved"
tools:layout="@layout/fragment_saved_collections">
<argument
android:name="isSaving"
android:defaultValue="false"
app:argType="boolean" />
<action
android:id="@+id/action_to_collection_posts"
app:destination="@id/collectionPostsFragment" />
</fragment>
<fragment
android:id="@+id/collectionPostsFragment"
android:name="awais.instagrabber.fragments.CollectionPostsFragment"
tools:layout="@layout/fragment_collection_posts">
<argument
android:name="savedCollection"
app:argType="awais.instagrabber.repositories.responses.saved.SavedCollection" />
<argument
android:name="titleColor"
app:argType="integer" />
<argument
android:name="backgroundColor"
app:argType="integer" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/user_search"
android:name="awais.instagrabber.fragments.UserSearchFragment"
android:label="@string/search"
tools:layout="@layout/fragment_user_search">
<argument
android:name="multiple"
android:defaultValue="false"
app:argType="boolean" />
<argument
android:name="title"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
<argument
android:name="action_label"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
<argument
android:name="show_groups"
android:defaultValue="false"
app:argType="boolean" />
<argument
android:name="search_mode"
android:defaultValue="USER_SEARCH"
app:argType="awais.instagrabber.fragments.UserSearchMode" />
<argument
android:name="hideUserIds"
android:defaultValue="@null"
app:argType="long[]"
app:nullable="true" />
<argument
android:name="hideThreadIds"
android:defaultValue="@null"
app:argType="string[]"
app:nullable="true" />
</fragment>
</navigation>

484
app/src/main/res/navigation/favorites_nav_graph.xml

@ -0,0 +1,484 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/favorites_nav_graph"
app:startDestination="@id/favoritesFragment">
<fragment
android:id="@+id/favoritesFragment"
android:name="awais.instagrabber.fragments.FavoritesFragment"
android:label="@string/title_favorites"
tools:layout="@layout/fragment_favorites">
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
</fragment>
<fragment
android:id="@+id/storyViewerFragment"
android:name="awais.instagrabber.fragments.StoryViewerFragment"
android:label="StoryViewerFragment"
tools:layout="@layout/fragment_story_viewer">
<argument
android:name="options"
app:argType="awais.instagrabber.repositories.requests.StoryViewerOptions" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
</fragment>
<fragment
android:id="@+id/postViewFragment"
android:name="awais.instagrabber.fragments.PostViewV2Fragment"
android:label="@string/post"
tools:layout="@layout/dialog_post_view">
<argument
android:name="media"
app:argType="awais.instagrabber.repositories.responses.Media"
app:nullable="false" />
<argument
android:name="position"
app:argType="integer" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_likes"
app:destination="@id/likesViewerFragment" />
<action
android:id="@+id/action_to_saved_collections"
app:destination="@id/savedCollectionsFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/locationFragment"
android:name="awais.instagrabber.fragments.LocationFragment"
android:label=""
tools:layout="@layout/fragment_location">
<argument
android:name="locationId"
app:argType="long" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/hashTagFragment"
android:name="awais.instagrabber.fragments.HashTagFragment"
android:label=""
tools:layout="@layout/fragment_hashtag">
<argument
android:name="hashtag"
app:argType="string"
app:nullable="false" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<dialog
android:id="@+id/commentsViewerFragment"
android:name="awais.instagrabber.fragments.comments.CommentsViewerFragment"
android:label="Comments"
tools:layout="@layout/fragment_comments">
<argument
android:name="shortCode"
app:argType="string"
app:nullable="false" />
<argument
android:name="postId"
app:argType="string"
app:nullable="false" />
<argument
android:name="postUserId"
app:argType="long" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_likes"
app:destination="@id/likesViewerFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</dialog>
<!-- Copy of profile fragment tag -->
<!-- Required to get back arrow in action bar -->
<!-- See https://issuetracker.google.com/issues/192395936 -->
<fragment
android:id="@+id/profile_non_top"
android:name="awais.instagrabber.fragments.main.ProfileFragment"
android:label="@string/profile"
tools:layout="@layout/fragment_profile">
<argument
android:name="username"
android:defaultValue=""
app:argType="string"
app:nullable="true" />
<action
android:id="@+id/action_to_saved"
app:destination="@id/savedViewerFragment" />
<action
android:id="@+id/action_to_saved_collections"
app:destination="@id/savedCollectionsFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_follow_viewer"
app:destination="@id/followViewerFragment" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
<action
android:id="@+id/action_to_notifications"
app:destination="@id/notifications_viewer_non_top" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/savedViewerFragment"
android:name="awais.instagrabber.fragments.SavedViewerFragment"
android:label="Saved"
tools:layout="@layout/fragment_saved">
<argument
android:name="username"
app:argType="string"
app:nullable="false" />
<argument
android:name="profileId"
app:argType="long" />
<argument
android:name="type"
app:argType="awais.instagrabber.models.enums.PostItemType"
app:nullable="false" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/followViewerFragment"
android:name="awais.instagrabber.fragments.FollowViewerFragment"
android:label=""
tools:layout="@layout/fragment_followers_viewer">
<argument
android:name="profileId"
app:argType="long" />
<argument
android:name="isFollowersList"
app:argType="boolean"
app:nullable="false" />
<argument
android:name="username"
app:argType="string"
app:nullable="false" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<dialog
android:id="@+id/likesViewerFragment"
android:name="awais.instagrabber.fragments.LikesViewerFragment"
android:label="Comments"
tools:layout="@layout/fragment_likes">
<argument
android:name="postId"
app:argType="string"
app:nullable="false" />
<argument
android:name="isComment"
app:argType="boolean"
app:nullable="false" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</dialog>
<fragment
android:id="@+id/savedCollectionsFragment"
android:name="awais.instagrabber.fragments.SavedCollectionsFragment"
android:label="@string/saved"
tools:layout="@layout/fragment_saved_collections">
<argument
android:name="isSaving"
android:defaultValue="false"
app:argType="boolean" />
<action
android:id="@+id/action_to_collection_posts"
app:destination="@id/collectionPostsFragment" />
</fragment>
<fragment
android:id="@+id/collectionPostsFragment"
android:name="awais.instagrabber.fragments.CollectionPostsFragment"
tools:layout="@layout/fragment_collection_posts">
<argument
android:name="savedCollection"
app:argType="awais.instagrabber.repositories.responses.saved.SavedCollection" />
<argument
android:name="titleColor"
app:argType="integer" />
<argument
android:name="backgroundColor"
app:argType="integer" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/user_search"
android:name="awais.instagrabber.fragments.UserSearchFragment"
android:label="@string/search"
tools:layout="@layout/fragment_user_search">
<argument
android:name="multiple"
android:defaultValue="false"
app:argType="boolean" />
<argument
android:name="title"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
<argument
android:name="action_label"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
<argument
android:name="show_groups"
android:defaultValue="false"
app:argType="boolean" />
<argument
android:name="search_mode"
android:defaultValue="USER_SEARCH"
app:argType="awais.instagrabber.fragments.UserSearchMode" />
<argument
android:name="hideUserIds"
android:defaultValue="@null"
app:argType="long[]"
app:nullable="true" />
<argument
android:name="hideThreadIds"
android:defaultValue="@null"
app:argType="string[]"
app:nullable="true" />
</fragment>
<!-- Copy of notification viewer fragment tag -->
<!-- Required to get back arrow in action bar -->
<!-- See https://issuetracker.google.com/issues/192395936 -->
<fragment
android:id="@+id/notifications_viewer_non_top"
android:name="awais.instagrabber.fragments.NotificationsViewerFragment"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications_viewer">
<argument
android:name="type"
android:defaultValue="notif"
app:argType="string"
app:nullable="false" />
<argument
android:name="targetId"
android:defaultValue="0L"
app:argType="long" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
</fragment>
</navigation>

520
app/src/main/res/navigation/feed_nav_graph.xml

@ -0,0 +1,520 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/feed_nav_graph"
app:startDestination="@id/feedFragment">
<fragment
android:id="@+id/feedFragment"
android:name="awais.instagrabber.fragments.main.FeedFragment"
android:label="@string/feed"
tools:layout="@layout/fragment_feed">
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_story_list"
app:destination="@id/storyListViewerFragment" />
</fragment>
<fragment
android:id="@+id/storyViewerFragment"
android:name="awais.instagrabber.fragments.StoryViewerFragment"
android:label="StoryViewerFragment"
tools:layout="@layout/fragment_story_viewer">
<argument
android:name="options"
app:argType="awais.instagrabber.repositories.requests.StoryViewerOptions" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
</fragment>
<fragment
android:id="@+id/postViewFragment"
android:name="awais.instagrabber.fragments.PostViewV2Fragment"
android:label="@string/post"
tools:layout="@layout/dialog_post_view">
<argument
android:name="media"
app:argType="awais.instagrabber.repositories.responses.Media"
app:nullable="false" />
<argument
android:name="position"
app:argType="integer" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_likes"
app:destination="@id/likesViewerFragment" />
<action
android:id="@+id/action_to_saved_collections"
app:destination="@id/savedCollectionsFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/locationFragment"
android:name="awais.instagrabber.fragments.LocationFragment"
android:label=""
tools:layout="@layout/fragment_location">
<argument
android:name="locationId"
app:argType="long" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/hashTagFragment"
android:name="awais.instagrabber.fragments.HashTagFragment"
android:label=""
tools:layout="@layout/fragment_hashtag">
<argument
android:name="hashtag"
app:argType="string"
app:nullable="false" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<dialog
android:id="@+id/commentsViewerFragment"
android:name="awais.instagrabber.fragments.comments.CommentsViewerFragment"
android:label="Comments"
tools:layout="@layout/fragment_comments">
<argument
android:name="shortCode"
app:argType="string"
app:nullable="false" />
<argument
android:name="postId"
app:argType="string"
app:nullable="false" />
<argument
android:name="postUserId"
app:argType="long" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_likes"
app:destination="@id/likesViewerFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</dialog>
<!-- Copy of profile fragment tag -->
<!-- Required to get back arrow in action bar -->
<!-- See https://issuetracker.google.com/issues/192395936 -->
<fragment
android:id="@+id/profile_non_top"
android:name="awais.instagrabber.fragments.main.ProfileFragment"
android:label="@string/profile"
tools:layout="@layout/fragment_profile">
<argument
android:name="username"
android:defaultValue=""
app:argType="string"
app:nullable="true" />
<action
android:id="@+id/action_to_saved"
app:destination="@id/savedViewerFragment" />
<action
android:id="@+id/action_to_saved_collections"
app:destination="@id/savedCollectionsFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_follow_viewer"
app:destination="@id/followViewerFragment" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
<action
android:id="@+id/action_to_notifications"
app:destination="@id/notifications_viewer_non_top" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/savedViewerFragment"
android:name="awais.instagrabber.fragments.SavedViewerFragment"
android:label="Saved"
tools:layout="@layout/fragment_saved">
<argument
android:name="username"
app:argType="string"
app:nullable="false" />
<argument
android:name="profileId"
app:argType="long" />
<argument
android:name="type"
app:argType="awais.instagrabber.models.enums.PostItemType"
app:nullable="false" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/followViewerFragment"
android:name="awais.instagrabber.fragments.FollowViewerFragment"
android:label=""
tools:layout="@layout/fragment_followers_viewer">
<argument
android:name="profileId"
app:argType="long" />
<argument
android:name="isFollowersList"
app:argType="boolean"
app:nullable="false" />
<argument
android:name="username"
app:argType="string"
app:nullable="false" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<dialog
android:id="@+id/likesViewerFragment"
android:name="awais.instagrabber.fragments.LikesViewerFragment"
android:label="Comments"
tools:layout="@layout/fragment_likes">
<argument
android:name="postId"
app:argType="string"
app:nullable="false" />
<argument
android:name="isComment"
app:argType="boolean"
app:nullable="false" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</dialog>
<!-- Copy of notification viewer fragment tag -->
<!-- Required to get back arrow in action bar -->
<!-- See https://issuetracker.google.com/issues/192395936 -->
<fragment
android:id="@+id/notifications_viewer_non_top"
android:name="awais.instagrabber.fragments.NotificationsViewerFragment"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications_viewer">
<argument
android:name="type"
android:defaultValue="notif"
app:argType="string"
app:nullable="false" />
<argument
android:name="targetId"
android:defaultValue="0L"
app:argType="long" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
</fragment>
<fragment
android:id="@+id/savedCollectionsFragment"
android:name="awais.instagrabber.fragments.SavedCollectionsFragment"
android:label="@string/saved"
tools:layout="@layout/fragment_saved_collections">
<argument
android:name="isSaving"
android:defaultValue="false"
app:argType="boolean" />
<action
android:id="@+id/action_to_collection_posts"
app:destination="@id/collectionPostsFragment" />
</fragment>
<fragment
android:id="@+id/collectionPostsFragment"
android:name="awais.instagrabber.fragments.CollectionPostsFragment"
tools:layout="@layout/fragment_collection_posts">
<argument
android:name="savedCollection"
app:argType="awais.instagrabber.repositories.responses.saved.SavedCollection" />
<argument
android:name="titleColor"
app:argType="integer" />
<argument
android:name="backgroundColor"
app:argType="integer" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/storyListViewerFragment"
android:name="awais.instagrabber.fragments.StoryListViewerFragment"
android:label="Stories"
tools:layout="@layout/fragment_story_list_viewer">
<argument
android:name="type"
app:argType="string"
app:nullable="false" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/user_search"
android:name="awais.instagrabber.fragments.UserSearchFragment"
android:label="@string/search"
tools:layout="@layout/fragment_user_search">
<argument
android:name="multiple"
android:defaultValue="false"
app:argType="boolean" />
<argument
android:name="title"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
<argument
android:name="action_label"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
<argument
android:name="show_groups"
android:defaultValue="false"
app:argType="boolean" />
<argument
android:name="search_mode"
android:defaultValue="USER_SEARCH"
app:argType="awais.instagrabber.fragments.UserSearchMode" />
<argument
android:name="hideUserIds"
android:defaultValue="@null"
app:argType="long[]"
app:nullable="true" />
<argument
android:name="hideThreadIds"
android:defaultValue="@null"
app:argType="string[]"
app:nullable="true" />
</fragment>
</navigation>

539
app/src/main/res/navigation/more_nav_graph.xml

@ -0,0 +1,539 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/more_nav_graph"
app:startDestination="@id/morePreferencesFragment">
<fragment
android:id="@+id/morePreferencesFragment"
android:name="awais.instagrabber.fragments.settings.MorePreferencesFragment"
android:label="@string/more">
<action
android:id="@+id/action_to_settings"
app:destination="@id/settings_nav_graph" />
<action
android:id="@+id/action_to_about"
app:destination="@id/aboutFragment" />
<action
android:id="@+id/action_to_favorites"
app:destination="@id/favorites_non_top" />
<action
android:id="@+id/action_to_backup"
app:destination="@id/backupPreferencesFragment" />
<action
android:id="@+id/action_to_notifications"
app:destination="@id/notifications_viewer_non_top" />
<action
android:id="@+id/action_to_story_list"
app:destination="@id/storyListViewerFragment" />
</fragment>
<fragment
android:id="@+id/storyViewerFragment"
android:name="awais.instagrabber.fragments.StoryViewerFragment"
android:label="StoryViewerFragment"
tools:layout="@layout/fragment_story_viewer">
<argument
android:name="options"
app:argType="awais.instagrabber.repositories.requests.StoryViewerOptions" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
</fragment>
<fragment
android:id="@+id/postViewFragment"
android:name="awais.instagrabber.fragments.PostViewV2Fragment"
android:label="@string/post"
tools:layout="@layout/dialog_post_view">
<argument
android:name="media"
app:argType="awais.instagrabber.repositories.responses.Media"
app:nullable="false" />
<argument
android:name="position"
app:argType="integer" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_likes"
app:destination="@id/likesViewerFragment" />
<action
android:id="@+id/action_to_saved_collections"
app:destination="@id/savedCollectionsFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/locationFragment"
android:name="awais.instagrabber.fragments.LocationFragment"
android:label=""
tools:layout="@layout/fragment_location">
<argument
android:name="locationId"
app:argType="long" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/hashTagFragment"
android:name="awais.instagrabber.fragments.HashTagFragment"
android:label=""
tools:layout="@layout/fragment_hashtag">
<argument
android:name="hashtag"
app:argType="string"
app:nullable="false" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<dialog
android:id="@+id/commentsViewerFragment"
android:name="awais.instagrabber.fragments.comments.CommentsViewerFragment"
android:label="Comments"
tools:layout="@layout/fragment_comments">
<argument
android:name="shortCode"
app:argType="string"
app:nullable="false" />
<argument
android:name="postId"
app:argType="string"
app:nullable="false" />
<argument
android:name="postUserId"
app:argType="long" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_likes"
app:destination="@id/likesViewerFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</dialog>
<!-- Copy of profile fragment tag -->
<!-- Required to get back arrow in action bar -->
<!-- See https://issuetracker.google.com/issues/192395936 -->
<fragment
android:id="@+id/profile_non_top"
android:name="awais.instagrabber.fragments.main.ProfileFragment"
android:label="@string/profile"
tools:layout="@layout/fragment_profile">
<argument
android:name="username"
android:defaultValue=""
app:argType="string"
app:nullable="true" />
<action
android:id="@+id/action_to_saved"
app:destination="@id/savedViewerFragment" />
<action
android:id="@+id/action_to_saved_collections"
app:destination="@id/savedCollectionsFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_follow_viewer"
app:destination="@id/followViewerFragment" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
<action
android:id="@+id/action_to_notifications"
app:destination="@id/notifications_viewer_non_top" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/savedViewerFragment"
android:name="awais.instagrabber.fragments.SavedViewerFragment"
android:label="Saved"
tools:layout="@layout/fragment_saved">
<argument
android:name="username"
app:argType="string"
app:nullable="false" />
<argument
android:name="profileId"
app:argType="long" />
<argument
android:name="type"
app:argType="awais.instagrabber.models.enums.PostItemType"
app:nullable="false" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/followViewerFragment"
android:name="awais.instagrabber.fragments.FollowViewerFragment"
android:label=""
tools:layout="@layout/fragment_followers_viewer">
<argument
android:name="profileId"
app:argType="long" />
<argument
android:name="isFollowersList"
app:argType="boolean"
app:nullable="false" />
<argument
android:name="username"
app:argType="string"
app:nullable="false" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<dialog
android:id="@+id/likesViewerFragment"
android:name="awais.instagrabber.fragments.LikesViewerFragment"
android:label="Comments"
tools:layout="@layout/fragment_likes">
<argument
android:name="postId"
app:argType="string"
app:nullable="false" />
<argument
android:name="isComment"
app:argType="boolean"
app:nullable="false" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</dialog>
<!-- Copy of favorites fragment tag -->
<!-- Required to get back arrow in action bar -->
<!-- See https://issuetracker.google.com/issues/192395936 -->
<fragment
android:id="@+id/favorites_non_top"
android:name="awais.instagrabber.fragments.FavoritesFragment"
android:label="@string/title_favorites"
tools:layout="@layout/fragment_favorites">
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
</fragment>
<!-- Copy of notification viewer fragment tag -->
<!-- Required to get back arrow in action bar -->
<!-- See https://issuetracker.google.com/issues/192395936 -->
<fragment
android:id="@+id/notifications_viewer_non_top"
android:name="awais.instagrabber.fragments.NotificationsViewerFragment"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications_viewer">
<argument
android:name="type"
android:defaultValue="notif"
app:argType="string"
app:nullable="false" />
<argument
android:name="targetId"
android:defaultValue="0L"
app:argType="long" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
</fragment>
<fragment
android:id="@+id/savedCollectionsFragment"
android:name="awais.instagrabber.fragments.SavedCollectionsFragment"
android:label="@string/saved"
tools:layout="@layout/fragment_saved_collections">
<argument
android:name="isSaving"
android:defaultValue="false"
app:argType="boolean" />
<action
android:id="@+id/action_to_collection_posts"
app:destination="@id/collectionPostsFragment" />
</fragment>
<fragment
android:id="@+id/collectionPostsFragment"
android:name="awais.instagrabber.fragments.CollectionPostsFragment"
tools:layout="@layout/fragment_collection_posts">
<argument
android:name="savedCollection"
app:argType="awais.instagrabber.repositories.responses.saved.SavedCollection" />
<argument
android:name="titleColor"
app:argType="integer" />
<argument
android:name="backgroundColor"
app:argType="integer" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/user_search"
android:name="awais.instagrabber.fragments.UserSearchFragment"
android:label="@string/search"
tools:layout="@layout/fragment_user_search">
<argument
android:name="multiple"
android:defaultValue="false"
app:argType="boolean" />
<argument
android:name="title"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
<argument
android:name="action_label"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
<argument
android:name="show_groups"
android:defaultValue="false"
app:argType="boolean" />
<argument
android:name="search_mode"
android:defaultValue="USER_SEARCH"
app:argType="awais.instagrabber.fragments.UserSearchMode" />
<argument
android:name="hideUserIds"
android:defaultValue="@null"
app:argType="long[]"
app:nullable="true" />
<argument
android:name="hideThreadIds"
android:defaultValue="@null"
app:argType="string[]"
app:nullable="true" />
</fragment>
<include app:graph="@navigation/settings_nav_graph" />
<fragment
android:id="@+id/storyListViewerFragment"
android:name="awais.instagrabber.fragments.StoryListViewerFragment"
android:label="Stories"
tools:layout="@layout/fragment_story_list_viewer">
<argument
android:name="type"
app:argType="string"
app:nullable="false" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
</navigation>

495
app/src/main/res/navigation/notification_viewer_nav_graph.xml

@ -0,0 +1,495 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/notification_viewer_nav_graph"
app:startDestination="@id/notificationsViewer">
<fragment
android:id="@+id/notificationsViewer"
android:name="awais.instagrabber.fragments.NotificationsViewerFragment"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications_viewer">
<argument
android:name="type"
android:defaultValue="notif"
app:argType="string"
app:nullable="false" />
<argument
android:name="targetId"
android:defaultValue="0L"
app:argType="long" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
</fragment>
<fragment
android:id="@+id/storyViewerFragment"
android:name="awais.instagrabber.fragments.StoryViewerFragment"
android:label="StoryViewerFragment"
tools:layout="@layout/fragment_story_viewer">
<argument
android:name="options"
app:argType="awais.instagrabber.repositories.requests.StoryViewerOptions" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
</fragment>
<fragment
android:id="@+id/postViewFragment"
android:name="awais.instagrabber.fragments.PostViewV2Fragment"
android:label="@string/post"
tools:layout="@layout/dialog_post_view">
<argument
android:name="media"
app:argType="awais.instagrabber.repositories.responses.Media"
app:nullable="false" />
<argument
android:name="position"
app:argType="integer" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_likes"
app:destination="@id/likesViewerFragment" />
<action
android:id="@+id/action_to_saved_collections"
app:destination="@id/savedCollectionsFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/locationFragment"
android:name="awais.instagrabber.fragments.LocationFragment"
android:label=""
tools:layout="@layout/fragment_location">
<argument
android:name="locationId"
app:argType="long" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/hashTagFragment"
android:name="awais.instagrabber.fragments.HashTagFragment"
android:label=""
tools:layout="@layout/fragment_hashtag">
<argument
android:name="hashtag"
app:argType="string"
app:nullable="false" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<dialog
android:id="@+id/commentsViewerFragment"
android:name="awais.instagrabber.fragments.comments.CommentsViewerFragment"
android:label="Comments"
tools:layout="@layout/fragment_comments">
<argument
android:name="shortCode"
app:argType="string"
app:nullable="false" />
<argument
android:name="postId"
app:argType="string"
app:nullable="false" />
<argument
android:name="postUserId"
app:argType="long" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_likes"
app:destination="@id/likesViewerFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</dialog>
<!-- Copy of profile fragment tag -->
<!-- Required to get back arrow in action bar -->
<!-- See https://issuetracker.google.com/issues/192395936 -->
<fragment
android:id="@+id/profile_non_top"
android:name="awais.instagrabber.fragments.main.ProfileFragment"
android:label="@string/profile"
tools:layout="@layout/fragment_profile">
<argument
android:name="username"
android:defaultValue=""
app:argType="string"
app:nullable="true" />
<action
android:id="@+id/action_to_saved"
app:destination="@id/savedViewerFragment" />
<action
android:id="@+id/action_to_saved_collections"
app:destination="@id/savedCollectionsFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_follow_viewer"
app:destination="@id/followViewerFragment" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
<action
android:id="@+id/action_to_notifications"
app:destination="@id/notifications_viewer_non_top" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/savedViewerFragment"
android:name="awais.instagrabber.fragments.SavedViewerFragment"
android:label="Saved"
tools:layout="@layout/fragment_saved">
<argument
android:name="username"
app:argType="string"
app:nullable="false" />
<argument
android:name="profileId"
app:argType="long" />
<argument
android:name="type"
app:argType="awais.instagrabber.models.enums.PostItemType"
app:nullable="false" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/followViewerFragment"
android:name="awais.instagrabber.fragments.FollowViewerFragment"
android:label=""
tools:layout="@layout/fragment_followers_viewer">
<argument
android:name="profileId"
app:argType="long" />
<argument
android:name="isFollowersList"
app:argType="boolean"
app:nullable="false" />
<argument
android:name="username"
app:argType="string"
app:nullable="false" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<dialog
android:id="@+id/likesViewerFragment"
android:name="awais.instagrabber.fragments.LikesViewerFragment"
android:label="Comments"
tools:layout="@layout/fragment_likes">
<argument
android:name="postId"
app:argType="string"
app:nullable="false" />
<argument
android:name="isComment"
app:argType="boolean"
app:nullable="false" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</dialog>
<!-- Copy of notification viewer fragment tag -->
<!-- Required to get back arrow in action bar -->
<!-- See https://issuetracker.google.com/issues/192395936 -->
<fragment
android:id="@+id/notifications_viewer_non_top"
android:name="awais.instagrabber.fragments.NotificationsViewerFragment"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications_viewer">
<argument
android:name="type"
android:defaultValue="notif"
app:argType="string"
app:nullable="false" />
<argument
android:name="targetId"
android:defaultValue="0L"
app:argType="long" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
</fragment>
<fragment
android:id="@+id/savedCollectionsFragment"
android:name="awais.instagrabber.fragments.SavedCollectionsFragment"
android:label="@string/saved"
tools:layout="@layout/fragment_saved_collections">
<argument
android:name="isSaving"
android:defaultValue="false"
app:argType="boolean" />
<action
android:id="@+id/action_to_collection_posts"
app:destination="@id/collectionPostsFragment" />
</fragment>
<fragment
android:id="@+id/collectionPostsFragment"
android:name="awais.instagrabber.fragments.CollectionPostsFragment"
tools:layout="@layout/fragment_collection_posts">
<argument
android:name="savedCollection"
app:argType="awais.instagrabber.repositories.responses.saved.SavedCollection" />
<argument
android:name="titleColor"
app:argType="integer" />
<argument
android:name="backgroundColor"
app:argType="integer" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/user_search"
android:name="awais.instagrabber.fragments.UserSearchFragment"
android:label="@string/search"
tools:layout="@layout/fragment_user_search">
<argument
android:name="multiple"
android:defaultValue="false"
app:argType="boolean" />
<argument
android:name="title"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
<argument
android:name="action_label"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
<argument
android:name="show_groups"
android:defaultValue="false"
app:argType="boolean" />
<argument
android:name="search_mode"
android:defaultValue="USER_SEARCH"
app:argType="awais.instagrabber.fragments.UserSearchMode" />
<argument
android:name="hideUserIds"
android:defaultValue="@null"
app:argType="long[]"
app:nullable="true" />
<argument
android:name="hideThreadIds"
android:defaultValue="@null"
app:argType="string[]"
app:nullable="true" />
</fragment>
</navigation>

567
app/src/main/res/navigation/profile_nav_graph.xml

@ -0,0 +1,567 @@
<?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"
xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/profile_nav_graph"
app:startDestination="@id/profileFragment">
<fragment
android:id="@+id/profileFragment"
android:name="awais.instagrabber.fragments.main.ProfileFragment"
android:label="@string/profile"
tools:layout="@layout/fragment_profile">
<argument
android:name="username"
android:defaultValue=""
app:argType="string"
app:nullable="true" />
<action
android:id="@+id/action_to_saved"
app:destination="@id/savedViewerFragment" />
<action
android:id="@+id/action_to_saved_collections"
app:destination="@id/savedCollectionsFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_follow_viewer"
app:destination="@id/followViewerFragment" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
<action
android:id="@+id/action_to_notifications"
app:destination="@id/notifications_viewer_non_top" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<dialog
android:id="@+id/post_loading_dialog"
android:name="awais.instagrabber.dialogs.PostLoadingDialogFragment"
android:label="@string/direct_download_loading">
<argument
android:name="shortCode"
app:argType="string" />
<deepLink app:uri="barinsta://post/{shortCode}" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
</dialog>
<fragment
android:id="@+id/storyViewerFragment"
android:name="awais.instagrabber.fragments.StoryViewerFragment"
android:label="StoryViewerFragment"
tools:layout="@layout/fragment_story_viewer">
<argument
android:name="options"
app:argType="awais.instagrabber.repositories.requests.StoryViewerOptions" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
</fragment>
<fragment
android:id="@+id/searchFragment"
android:name="awais.instagrabber.fragments.search.SearchFragment"
android:label="@string/search"
tools:layout="@layout/fragment_search">
<deepLink app:uri="barinsta://search" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
</fragment>
<fragment
android:id="@+id/postViewFragment"
android:name="awais.instagrabber.fragments.PostViewV2Fragment"
android:label="@string/post"
tools:layout="@layout/dialog_post_view">
<argument
android:name="media"
app:argType="awais.instagrabber.repositories.responses.Media"
app:nullable="false" />
<argument
android:name="position"
app:argType="integer" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_likes"
app:destination="@id/likesViewerFragment" />
<action
android:id="@+id/action_to_saved_collections"
app:destination="@id/savedCollectionsFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/locationFragment"
android:name="awais.instagrabber.fragments.LocationFragment"
android:label=""
tools:layout="@layout/fragment_location">
<argument
android:name="locationId"
app:argType="long" />
<deepLink app:uri="barinsta://location/{locationId}" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/hashTagFragment"
android:name="awais.instagrabber.fragments.HashTagFragment"
android:label=""
tools:layout="@layout/fragment_hashtag">
<argument
android:name="hashtag"
app:argType="string"
app:nullable="false" />
<deepLink app:uri="barinsta://hashtag/{hashtag}" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<dialog
android:id="@+id/commentsViewerFragment"
android:name="awais.instagrabber.fragments.comments.CommentsViewerFragment"
android:label="Comments"
tools:layout="@layout/fragment_comments">
<argument
android:name="shortCode"
app:argType="string"
app:nullable="false" />
<argument
android:name="postId"
app:argType="string"
app:nullable="false" />
<argument
android:name="postUserId"
app:argType="long" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_likes"
app:destination="@id/likesViewerFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</dialog>
<!-- Copy of profile fragment tag -->
<!-- Required to get back arrow in action bar -->
<!-- See https://issuetracker.google.com/issues/192395936 -->
<fragment
android:id="@+id/profile_non_top"
android:name="awais.instagrabber.fragments.main.ProfileFragment"
android:label="@string/profile"
tools:layout="@layout/fragment_profile">
<argument
android:name="username"
android:defaultValue=""
app:argType="string"
app:nullable="true" />
<deepLink app:uri="barinsta://profile/{username}" />
<action
android:id="@+id/action_to_saved"
app:destination="@id/savedViewerFragment" />
<action
android:id="@+id/action_to_saved_collections"
app:destination="@id/savedCollectionsFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_follow_viewer"
app:destination="@id/followViewerFragment" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_user_search"
app:destination="@id/user_search" />
<action
android:id="@+id/action_to_notifications"
app:destination="@id/notifications_viewer_non_top" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/savedViewerFragment"
android:name="awais.instagrabber.fragments.SavedViewerFragment"
android:label="Saved"
tools:layout="@layout/fragment_saved">
<argument
android:name="username"
app:argType="string"
app:nullable="false" />
<argument
android:name="profileId"
app:argType="long" />
<argument
android:name="type"
app:argType="awais.instagrabber.models.enums.PostItemType"
app:nullable="false" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/followViewerFragment"
android:name="awais.instagrabber.fragments.FollowViewerFragment"
android:label=""
tools:layout="@layout/fragment_followers_viewer">
<argument
android:name="profileId"
app:argType="long" />
<argument
android:name="isFollowersList"
app:argType="boolean"
app:nullable="false" />
<argument
android:name="username"
app:argType="string"
app:nullable="false" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<dialog
android:id="@+id/likesViewerFragment"
android:name="awais.instagrabber.fragments.LikesViewerFragment"
android:label="Comments"
tools:layout="@layout/fragment_likes">
<argument
android:name="postId"
app:argType="string"
app:nullable="false" />
<argument
android:name="isComment"
app:argType="boolean"
app:nullable="false" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</dialog>
<!-- Copy of notification viewer fragment tag -->
<!-- Required to get back arrow in action bar -->
<!-- See https://issuetracker.google.com/issues/192395936 -->
<fragment
android:id="@+id/notifications_viewer_non_top"
android:name="awais.instagrabber.fragments.NotificationsViewerFragment"
android:label="@string/title_notifications"
tools:layout="@layout/fragment_notifications_viewer">
<argument
android:name="type"
android:defaultValue="notif"
app:argType="string"
app:nullable="false" />
<argument
android:name="targetId"
android:defaultValue="0L"
app:argType="long" />
<deepLink app:uri="barinsta://notifications/{type}?targetId={targetId}" />
<action
android:id="@+id/action_to_story"
app:destination="@id/storyViewerFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
</fragment>
<fragment
android:id="@+id/savedCollectionsFragment"
android:name="awais.instagrabber.fragments.SavedCollectionsFragment"
android:label="@string/saved"
tools:layout="@layout/fragment_saved_collections">
<argument
android:name="isSaving"
android:defaultValue="false"
app:argType="boolean" />
<action
android:id="@+id/action_to_collection_posts"
app:destination="@id/collectionPostsFragment" />
</fragment>
<fragment
android:id="@+id/collectionPostsFragment"
android:name="awais.instagrabber.fragments.CollectionPostsFragment"
tools:layout="@layout/fragment_collection_posts">
<argument
android:name="savedCollection"
app:argType="awais.instagrabber.repositories.responses.saved.SavedCollection" />
<argument
android:name="titleColor"
app:argType="integer" />
<argument
android:name="backgroundColor"
app:argType="integer" />
<action
android:id="@+id/action_to_comments"
app:destination="@id/commentsViewerFragment" />
<action
android:id="@+id/action_to_hashtag"
app:destination="@id/hashTagFragment" />
<action
android:id="@+id/action_to_location"
app:destination="@id/locationFragment" />
<action
android:id="@+id/action_to_post"
app:destination="@id/postViewFragment" />
<action
android:id="@+id/action_to_profile"
app:destination="@id/profile_non_top" />
</fragment>
<fragment
android:id="@+id/user_search"
android:name="awais.instagrabber.fragments.UserSearchFragment"
android:label="@string/search"
tools:layout="@layout/fragment_user_search">
<argument
android:name="multiple"
android:defaultValue="false"
app:argType="boolean" />
<argument
android:name="title"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
<argument
android:name="action_label"
android:defaultValue="@null"
app:argType="string"
app:nullable="true" />
<argument
android:name="show_groups"
android:defaultValue="false"
app:argType="boolean" />
<argument
android:name="search_mode"
android:defaultValue="USER_SEARCH"
app:argType="awais.instagrabber.fragments.UserSearchMode" />
<argument
android:name="hideUserIds"
android:defaultValue="@null"
app:argType="long[]"
app:nullable="true" />
<argument
android:name="hideThreadIds"
android:defaultValue="@null"
app:argType="string[]"
app:nullable="true" />
</fragment>
</navigation>

12
app/src/main/res/navigation/root_nav_graph.xml

@ -0,0 +1,12 @@
<?xml version="1.0" encoding="utf-8"?>
<navigation>
<!--android:id="@+id/root"-->
<!--app:startDestination="@id/profile_nav_graph"-->
<!--<include app:graph="@navigation/dm_nav_graph" />-->
<!--<include app:graph="@navigation/feed_nav_graph" />-->
<!--<include app:graph="@navigation/profile_nav_graph" />-->
<!--<include app:graph="@navigation/discover_nav_graph" />-->
<!--<include app:graph="@navigation/more_nav_graph" />-->
</navigation>

108
app/src/main/res/navigation/settings_nav_graph.xml

@ -4,110 +4,6 @@
android:id="@+id/settings_nav_graph" android:id="@+id/settings_nav_graph"
app:startDestination="@id/settingsPreferencesFragment"> app:startDestination="@id/settingsPreferencesFragment">
<!--<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" />-->
<!--<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_commentsViewerFragment"-->
<!-- app:destination="@id/comments_nav_graph">-->
<!-- <argument-->
<!-- android:name="shortCode"-->
<!-- app:argType="string"-->
<!-- app:nullable="false" />-->
<!-- <argument-->
<!-- android:name="postId"-->
<!-- app:argType="string"-->
<!-- app:nullable="false" />-->
<!-- <argument-->
<!-- android:name="postUserId"-->
<!-- app:argType="long" />-->
<!--</action>-->
<!--<action-->
<!-- android:id="@+id/action_global_likesViewerFragment"-->
<!-- app:destination="@id/likes_nav_graph">-->
<!-- <argument-->
<!-- android:name="postId"-->
<!-- app:argType="string"-->
<!-- app:nullable="false" />-->
<!-- <argument-->
<!-- android:name="isComment"-->
<!-- app:argType="boolean"-->
<!-- app:nullable="false" />-->
<!--</action>-->
<!--<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>-->
<!--<action-->
<!-- android:id="@+id/action_global_storyListViewerFragment"-->
<!-- app:destination="@id/story_list_nav_graph">-->
<!-- <argument-->
<!-- android:name="type"-->
<!-- app:argType="string"-->
<!-- app:nullable="false" />-->
<!--</action>-->
<!--<action-->
<!-- android:id="@+id/action_global_notificationsViewerFragment"-->
<!-- app:destination="@id/notification_viewer_nav_graph">-->
<!-- <argument-->
<!-- android:name="type"-->
<!-- app:argType="string"-->
<!-- app:nullable="false" />-->
<!-- <argument-->
<!-- android:name="targetId"-->
<!-- android:defaultValue="0L"-->
<!-- app:argType="long" />-->
<!--</action>-->
<!--<action-->
<!-- android:id="@+id/action_global_post_view"-->
<!-- app:destination="@id/postViewFragment">-->
<!-- <argument-->
<!-- android:name="media"-->
<!-- app:argType="awais.instagrabber.repositories.responses.Media"-->
<!-- app:nullable="false" />-->
<!-- <argument-->
<!-- android:name="position"-->
<!-- app:argType="integer" />-->
<!--</action>-->
<!--<include app:graph="@navigation/user_search_nav_graph" />-->
<!--<action-->
<!-- android:id="@+id/action_global_user_search"-->
<!-- app:destination="@id/user_search_nav_graph" />-->
<fragment <fragment
android:id="@+id/settingsPreferencesFragment" android:id="@+id/settingsPreferencesFragment"
android:name="awais.instagrabber.fragments.settings.SettingsPreferencesFragment" android:name="awais.instagrabber.fragments.settings.SettingsPreferencesFragment"
@ -181,8 +77,4 @@
android:id="@+id/postPreferencesFragment" android:id="@+id/postPreferencesFragment"
android:name="awais.instagrabber.fragments.settings.PostPreferencesFragment" android:name="awais.instagrabber.fragments.settings.PostPreferencesFragment"
android:label="@string/pref_category_post" /> android:label="@string/pref_category_post" />
<!--<fragment-->
<!-- android:id="@+id/postViewFragment"-->
<!-- android:name="awais.instagrabber.fragments.PostViewV2Fragment"-->
<!-- android:label="@string/post" />-->
</navigation> </navigation>

9
app/src/main/res/values/ids.xml

@ -11,15 +11,6 @@
<item name="root_nav_graph" type="id" /> <item name="root_nav_graph" type="id" />
<!-- Navigation top level root ids -->
<item name="feed_nav_graph" type="id" />
<item name="profile_nav_graph" type="id" />
<item name="direct_messages_nav_graph" type="id" />
<item name="notification_viewer_nav_graph" type="id" />
<item name="discover_nav_graph" type="id" />
<item name="favorites_nav_graph" type="id" />
<item name="more_nav_graph" type="id" />
<!-- story stickers --> <!-- story stickers -->
<item name="mentions" type="id" /> <item name="mentions" type="id" />
<item name="spotify" type="id" /> <item name="spotify" type="id" />

18
app/src/test/java/awais/instagrabber/common/Adapters.kt

@ -8,9 +8,7 @@ import awais.instagrabber.models.enums.FavoriteType
import awais.instagrabber.repositories.* import awais.instagrabber.repositories.*
import awais.instagrabber.repositories.responses.* import awais.instagrabber.repositories.responses.*
import awais.instagrabber.repositories.responses.directmessages.* import awais.instagrabber.repositories.responses.directmessages.*
import awais.instagrabber.repositories.responses.stories.ArchiveResponse
import awais.instagrabber.repositories.responses.stories.ReelsTrayResponse
import awais.instagrabber.repositories.responses.stories.StoryStickerResponse
import awais.instagrabber.repositories.responses.stories.*
open class UserServiceAdapter : UserService { open class UserServiceAdapter : UserService {
override suspend fun getUserInfo(uid: Long): WrappedUser { override suspend fun getUserInfo(uid: Long): WrappedUser {
@ -47,7 +45,7 @@ open class FriendshipServiceAdapter : FriendshipService {
} }
open class StoriesServiceAdapter : StoriesService { open class StoriesServiceAdapter : StoriesService {
override suspend fun fetch(mediaId: Long): String {
override suspend fun fetch(mediaId: Long): StoryMediaResponse {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
@ -63,11 +61,19 @@ open class StoriesServiceAdapter : StoriesService {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun getUserStory(url: String): String {
override suspend fun getReelsMedia(id: String): ReelsMediaResponse {
TODO("Not yet implemented") TODO("Not yet implemented")
} }
override suspend fun respondToSticker(storyId: String, stickerId: String, action: String, form: Map<String, String>): StoryStickerResponse {
override suspend fun getStories(type: String, id: String): ReelsResponse {
TODO("Not yet implemented")
}
override suspend fun getUserStories(id: Long): ReelsResponse {
TODO("Not yet implemented")
}
override suspend fun respondToSticker(storyId: Long, stickerId: Long, action: String, form: Map<String, String>): StoryStickerResponse {
TODO("Not yet implemented") TODO("Not yet implemented")
} }

5
app/src/test/java/awais/instagrabber/viewmodels/ProfileFragmentViewModelTest.kt

@ -15,7 +15,6 @@ import awais.instagrabber.repositories.requests.StoryViewerOptions
import awais.instagrabber.repositories.responses.FriendshipStatus import awais.instagrabber.repositories.responses.FriendshipStatus
import awais.instagrabber.repositories.responses.User import awais.instagrabber.repositories.responses.User
import awais.instagrabber.repositories.responses.stories.Story import awais.instagrabber.repositories.responses.stories.Story
import awais.instagrabber.repositories.responses.stories.StoryMedia
import awais.instagrabber.webservices.* import awais.instagrabber.webservices.*
import kotlinx.coroutines.ExperimentalCoroutinesApi import kotlinx.coroutines.ExperimentalCoroutinesApi
import org.json.JSONException import org.json.JSONException
@ -320,13 +319,13 @@ internal class ProfileFragmentViewModelTest {
"username" to testPublicUser.username "username" to testPublicUser.username
) )
) )
val testUserStories = listOf(StoryMedia())
val testUserStories = Story()
val testUserHighlights = listOf(Story()) val testUserHighlights = listOf(Story())
val userRepository = object : UserRepository(UserServiceAdapter()) { val userRepository = object : UserRepository(UserServiceAdapter()) {
override suspend fun getUsernameInfo(username: String): User = testPublicUser override suspend fun getUsernameInfo(username: String): User = testPublicUser
} }
val storiesRepository = object : StoriesRepository(StoriesServiceAdapter()) { val storiesRepository = object : StoriesRepository(StoriesServiceAdapter()) {
override suspend fun getStories(options: StoryViewerOptions): List<StoryMedia> = testUserStories
override suspend fun getStories(options: StoryViewerOptions): Story = testUserStories
override suspend fun fetchHighlights(profileId: Long): List<Story> = testUserHighlights override suspend fun fetchHighlights(profileId: Long): List<Story> = testUserHighlights
} }
val viewModel = ProfileFragmentViewModel( val viewModel = ProfileFragmentViewModel(

Loading…
Cancel
Save