diff --git a/app/build.gradle b/app/build.gradle index 49dafc60..e08a11a6 100755 --- a/app/build.gradle +++ b/app/build.gradle @@ -163,7 +163,6 @@ configurations.all { dependencies { coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5' - def nav_version = '2.3.5' def exoplayer_version = '2.14.1' implementation 'com.google.android.material:material:1.4.0' @@ -175,8 +174,6 @@ dependencies { implementation "androidx.recyclerview:recyclerview:1.2.1" implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation "androidx.viewpager2:viewpager2:1.0.0" - implementation "androidx.navigation:navigation-fragment:$nav_version" - implementation "androidx.navigation:navigation-ui:$nav_version" implementation "androidx.constraintlayout:constraintlayout:2.0.4" implementation "androidx.preference:preference:1.1.1" implementation 'androidx.palette:palette:1.0.0' @@ -195,6 +192,10 @@ dependencies { implementation "androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.1" implementation "androidx.lifecycle:lifecycle-livedata-ktx:2.3.1" + // Navigation + implementation "androidx.navigation:navigation-fragment-ktx:$rootProject.nav_version" + implementation "androidx.navigation:navigation-ui-ktx:$rootProject.nav_version" + // Room def room_version = "2.3.0" implementation "androidx.room:room-runtime:$room_version" diff --git a/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java b/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java index 681e92df..d5c316c1 100644 --- a/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java +++ b/app/src/main/java/awais/instagrabber/activities/DirectorySelectActivity.java @@ -1,5 +1,7 @@ package awais.instagrabber.activities; +import android.annotation.SuppressLint; +import android.content.ActivityNotFoundException; import android.content.Intent; import android.net.Uri; import android.os.Build; @@ -66,9 +68,17 @@ public class DirectorySelectActivity extends BaseLanguageActivity { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && initialUri != null) { intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialUri); } - startActivityForResult(intent, SELECT_DIR_REQUEST_CODE); + try { + startActivityForResult(intent, SELECT_DIR_REQUEST_CODE); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "openDirectoryChooser: ", e); + showErrorDialog(getString(R.string.no_directory_picker_activity)); + } catch (Exception e) { + Log.e(TAG, "openDirectoryChooser: ", e); + } } + @SuppressLint("StringFormatInvalid") @Override protected void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { super.onActivityResult(requestCode, resultCode, data); diff --git a/app/src/main/java/awais/instagrabber/activities/MainActivity.kt b/app/src/main/java/awais/instagrabber/activities/MainActivity.kt index fc7b4a2c..fbf89442 100644 --- a/app/src/main/java/awais/instagrabber/activities/MainActivity.kt +++ b/app/src/main/java/awais/instagrabber/activities/MainActivity.kt @@ -1,7 +1,6 @@ package awais.instagrabber.activities import android.animation.LayoutTransition -import android.annotation.SuppressLint import android.app.NotificationChannel import android.app.NotificationManager import android.content.ComponentName @@ -15,9 +14,6 @@ import android.view.Menu import android.view.MenuItem import android.view.View import android.view.WindowManager -import android.widget.Toast -import androidx.annotation.IdRes -import androidx.appcompat.app.AlertDialog import androidx.appcompat.widget.Toolbar import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.core.app.NotificationManagerCompat @@ -28,23 +24,20 @@ import androidx.core.view.WindowInsetsCompat import androidx.emoji.text.EmojiCompat import androidx.emoji.text.EmojiCompat.InitCallback import androidx.emoji.text.FontRequestEmojiCompatConfig -import androidx.fragment.app.FragmentManager -import androidx.lifecycle.LiveData -import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider -import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController -import androidx.navigation.NavController.OnDestinationChangedListener import androidx.navigation.NavDestination -import androidx.navigation.ui.NavigationUI +import androidx.navigation.NavGraph +import androidx.navigation.NavGraphNavigator +import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.ui.* import awais.instagrabber.BuildConfig import awais.instagrabber.R import awais.instagrabber.customviews.emoji.EmojiVariantManager import awais.instagrabber.customviews.helpers.RootViewDeferringInsetsCallback import awais.instagrabber.customviews.helpers.TextWatcherAdapter import awais.instagrabber.databinding.ActivityMainBinding -import awais.instagrabber.fragments.PostViewV2Fragment -import awais.instagrabber.fragments.directmessages.DirectMessageInboxFragmentDirections +import awais.instagrabber.fragments.main.FeedFragment import awais.instagrabber.fragments.settings.PreferenceKeys import awais.instagrabber.models.IntentModel import awais.instagrabber.models.Resource @@ -56,35 +49,28 @@ import awais.instagrabber.utils.* import awais.instagrabber.utils.AppExecutors.tasksThread import awais.instagrabber.utils.DownloadUtils.ReselectDocumentTreeException import awais.instagrabber.utils.TextUtils.isEmpty -import awais.instagrabber.utils.TextUtils.shortcodeToId import awais.instagrabber.utils.emoji.EmojiParser import awais.instagrabber.viewmodels.AppStateViewModel 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.ScrollingViewBehavior import com.google.android.material.appbar.CollapsingToolbarLayout import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.textfield.TextInputLayout import com.google.common.collect.ImmutableList -import com.google.common.collect.Iterators -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.launch -import kotlinx.coroutines.withContext import java.util.* -import java.util.stream.Collectors -class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedListener { +class MainActivity : BaseLanguageActivity() { private lateinit var binding: ActivityMainBinding + private lateinit var navController: NavController + private lateinit var appBarConfiguration: AppBarConfiguration - private var currentNavControllerLiveData: LiveData? = null private var searchMenuItem: MenuItem? = null - private var firstFragmentGraphIndex = 0 + private var startNavRootId: Int = 0 + private var lastSelectedNavMenuId = 0 private var isActivityCheckerServiceBound = false - private var isBackStackEmpty = false private var isLoggedIn = false private var deviceUuid: String? = null private var csrfToken: String? = null @@ -106,8 +92,6 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL isActivityCheckerServiceBound = false } } - private val mediaRepository: MediaRepository by lazy { MediaRepository.getInstance() } - private val graphQLRepository: GraphQLRepository by lazy { GraphQLRepository.getInstance() } override fun onCreate(savedInstanceState: Bundle?) { try { @@ -141,8 +125,10 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL // } catch (e: Exception) { // Log.e(TAG, "onCreate: ", e) // } + val navHostFragment = supportFragmentManager.findFragmentById(R.id.main_nav_host) as NavHostFragment + navController = navHostFragment.navController if (savedInstanceState == null) { - setupBottomNavigationBar(true) + setupNavigation(true) } if (!BuildConfig.isPre) { val checkUpdates = Utils.settingsHelper.getBoolean(PreferenceKeys.CHECK_UPDATES) @@ -154,7 +140,6 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL if (isLoggedIn && Utils.settingsHelper.getBoolean(PreferenceKeys.CHECK_ACTIVITY)) { bindActivityCheckerService() } - supportFragmentManager.addOnBackStackChangedListener(this) // Initialise the internal map tasksThread.execute { EmojiParser.getInstance(this) @@ -233,42 +218,18 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL override fun onCreateOptionsMenu(menu: Menu): Boolean { menuInflater.inflate(R.menu.main_menu, menu) searchMenuItem = menu.findItem(R.id.search) - val navController = currentNavControllerLiveData?.value - if (navController != null) { - val currentDestination = navController.currentDestination - if (currentDestination != null) { - @SuppressLint("RestrictedApi") val backStack = navController.backStack - setupMenu(backStack.size, currentDestination.id) - } + val currentDestination = navController.currentDestination + if (currentDestination != null) { + val backStack = navController.backQueue + setupMenu(backStack.size, currentDestination.id) } - // if (binding.searchInputLayout.getVisibility() == View.VISIBLE) { - // searchMenuItem.setVisible(false).setEnabled(false); - // return true; - // } - // searchMenuItem.setVisible(true).setEnabled(true); - // if (showSearch && currentNavControllerLiveData != null) { - // final NavController navController = currentNavControllerLiveData.getValue(); - // if (navController != null) { - // final NavDestination currentDestination = navController.getCurrentDestination(); - // if (currentDestination != null) { - // final int destinationId = currentDestination.getId(); - // showSearch = destinationId == R.id.profileFragment; - // } - // } - // } - // if (!showSearch) { - // searchMenuItem.setVisible(false); - // return true; - // } - // return setupSearchView(); return true } override fun onOptionsItemSelected(item: MenuItem): Boolean { if (item.itemId == R.id.search) { - val navController = currentNavControllerLiveData?.value ?: return false try { - navController.navigate(R.id.action_global_search) + navController.navigate(getSearchDeepLink()) return true } catch (e: Exception) { Log.e(TAG, "onOptionsItemSelected: ", e) @@ -279,20 +240,13 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL } override fun onSaveInstanceState(outState: Bundle) { - outState.putString(FIRST_FRAGMENT_GRAPH_INDEX_KEY, firstFragmentGraphIndex.toString()) + // outState.putString(FIRST_FRAGMENT_GRAPH_INDEX_KEY, firstFragmentGraphIndex.toString()) outState.putString(LAST_SELECT_NAV_MENU_ID, binding.bottomNavView.selectedItemId.toString()) super.onSaveInstanceState(outState) } override fun onRestoreInstanceState(savedInstanceState: Bundle) { super.onRestoreInstanceState(savedInstanceState) - val key = savedInstanceState[FIRST_FRAGMENT_GRAPH_INDEX_KEY] as String? - if (key != null) { - try { - firstFragmentGraphIndex = key.toInt() - } catch (ignored: NumberFormatException) { - } - } val lastSelected = savedInstanceState[LAST_SELECT_NAV_MENU_ID] as String? if (lastSelected != null) { try { @@ -300,13 +254,11 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL } catch (ignored: NumberFormatException) { } } - setupBottomNavigationBar(false) + setupNavigation(false) } override fun onSupportNavigateUp(): Boolean { - if (currentNavControllerLiveData == null) return false - val navController = currentNavControllerLiveData?.value ?: return false - return navController.navigateUp() + return navController.navigateUp(appBarConfiguration) } override fun onNewIntent(intent: Intent) { @@ -330,33 +282,24 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL instance = null } - override fun onBackPressed() { - var currentNavControllerBackStack = 2 - currentNavControllerLiveData?.let { - val navController = it.value - if (navController != null) { - @SuppressLint("RestrictedApi") val backStack = navController.backStack - 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() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return @@ -391,117 +334,34 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL notificationManager.createNotificationChannel(silentNotificationChannel) } - private fun setupBottomNavigationBar(setDefaultTabFromSettings: Boolean) { - currentTabs = if (!isLoggedIn) setupAnonBottomNav() else setupMainBottomNav() - val mainNavList = currentTabs.stream() - .map(Tab::navigationResId) - .collect(Collectors.toList()) + private fun setupNavigation(setDefaultTabFromSettings: Boolean) { + currentTabs = if (isLoggedIn) setupMainBottomNav() else setupAnonBottomNav() showBottomViewDestinations = currentTabs.asSequence().map { it.startDestinationFragmentId }.toMutableList().apply { add(R.id.postViewFragment) add(R.id.favoritesFragment) + add(R.id.profile_non_top) } if (setDefaultTabFromSettings) { setSelectedTab(currentTabs) } else { binding.bottomNavView.selectedItemId = lastSelectedNavMenuId } - val navControllerLiveData = NavigationExtensions.setupWithNavController( - binding.bottomNavView, - mainNavList, - supportFragmentManager, - R.id.main_nav_host, - intent, - firstFragmentGraphIndex - ) - navControllerLiveData.observe(this, { navController: NavController? -> setupNavigation(binding.toolbar, navController) }) - currentNavControllerLiveData = navControllerLiveData - } - - private fun setSelectedTab(tabs: List) { - val defaultTabResNameString = Utils.settingsHelper.getString(Constants.DEFAULT_TAB) - try { - var navId = 0 - if (!isEmpty(defaultTabResNameString)) { - navId = resources.getIdentifier(defaultTabResNameString, "navigation", packageName) - } - val navGraph = if (isLoggedIn) R.navigation.feed_nav_graph else R.navigation.profile_nav_graph - val defaultNavId = if (navId <= 0) navGraph else navId - var index = Iterators.indexOf(tabs.iterator()) { tab: Tab? -> - if (tab == null) return@indexOf false - tab.navigationResId == defaultNavId - } - if (index < 0 || index >= tabs.size) index = 0 - firstFragmentGraphIndex = index - setBottomNavSelectedTab(tabs[index]) - } catch (e: Exception) { - Log.e(TAG, "Error parsing id", e) - } - } - - private fun setupAnonBottomNav(): List { - val selectedItemId = binding.bottomNavView.selectedItemId - val favoriteTab = Tab( - R.drawable.ic_star_24, - getString(R.string.title_favorites), - false, - "favorites_nav_graph", - R.navigation.favorites_nav_graph, - R.id.favorites_nav_graph, - R.id.favoritesFragment - ) - val profileTab = Tab( - R.drawable.ic_person_24, - getString(R.string.profile), - false, - "profile_nav_graph", - R.navigation.profile_nav_graph, - R.id.profile_nav_graph, - R.id.profileFragment - ) - val moreTab = Tab( - R.drawable.ic_more_horiz_24, - getString(R.string.more), - false, - "more_nav_graph", - R.navigation.more_nav_graph, - R.id.more_nav_graph, - R.id.morePreferencesFragment - ) - val menu = binding.bottomNavView.menu - menu.clear() - menu.add(0, favoriteTab.navigationRootId, 0, favoriteTab.title).setIcon(favoriteTab.iconResId) - menu.add(0, profileTab.navigationRootId, 0, profileTab.title).setIcon(profileTab.iconResId) - menu.add(0, moreTab.navigationRootId, 0, moreTab.title).setIcon(moreTab.iconResId) - if (selectedItemId != R.id.profile_nav_graph && selectedItemId != R.id.more_nav_graph && selectedItemId != R.id.favorites_nav_graph) { - setBottomNavSelectedTab(profileTab) - } - return ImmutableList.of(favoriteTab, profileTab, moreTab) - } - - private fun setupMainBottomNav(): List { - val menu = binding.bottomNavView.menu - menu.clear() - val navTabList = Utils.getNavTabList(this).first - for ((iconResId, title, _, _, _, navigationRootId) in navTabList) { - menu.add(0, navigationRootId, 0, title).setIcon(iconResId) - } - return navTabList - } - - private fun setBottomNavSelectedTab(tab: Tab) { - binding.bottomNavView.selectedItemId = tab.navigationRootId - } - - private fun setBottomNavSelectedTab(@IdRes navGraphRootId: Int) { - binding.bottomNavView.selectedItemId = navGraphRootId - } - - private fun setupNavigation(toolbar: Toolbar, navController: NavController?) { - if (navController == null) return - NavigationUI.setupWithNavController(toolbar, navController) - navController.addOnDestinationChangedListener(OnDestinationChangedListener { _: NavController?, destination: NavDestination, arguments: Bundle? -> + val navigatorProvider = navController.navigatorProvider + val navigator = navigatorProvider.getNavigator("navigation") + val rootNavGraph = NavGraph(navigator) + val navInflater = navController.navInflater + val topLevelDestinations = currentTabs.map { navInflater.inflate(it.navigationResId) } + rootNavGraph.id = R.id.root_nav_graph + rootNavGraph.label = "root_nav_graph" + rootNavGraph.addDestinations(topLevelDestinations) + rootNavGraph.setStartDestination(if (startNavRootId != 0) startNavRootId else R.id.profile_nav_graph) + navController.graph = rootNavGraph + binding.bottomNavView.setupWithNavController(navController) + appBarConfiguration = AppBarConfiguration(currentTabs.map { it.startDestinationFragmentId }.toSet()) + setupActionBarWithNavController(navController, appBarConfiguration) + navController.addOnDestinationChangedListener { _: NavController?, destination: NavDestination, arguments: Bundle? -> if (destination.id == R.id.directMessagesThreadFragment && arguments != null) { // Set the thread title earlier for better ux val title = arguments.getString("title") @@ -519,7 +379,7 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL // below is a hack to check if we are at the end of the current stack, to setup the search view binding.appBarLayout.setExpanded(true, true) val destinationId = destination.id - @SuppressLint("RestrictedApi") val backStack = navController.backStack + val backStack = navController.backQueue setupMenu(backStack.size, destinationId) val contains = showBottomViewDestinations.contains(destinationId) binding.root.post { @@ -531,7 +391,65 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL // explicitly hide keyboard when we navigate val view = currentFocus Utils.hideKeyboard(view) - }) + } + setupReselection() + } + + private fun setupReselection() { + binding.bottomNavView.setOnItemReselectedListener { + val navHostFragment = (supportFragmentManager.primaryNavigationFragment ?: return@setOnItemReselectedListener) as NavHostFragment + val currentFragment = navHostFragment.childFragmentManager.fragments.firstOrNull() ?: return@setOnItemReselectedListener + if (currentFragment is FeedFragment) { + currentFragment.scrollToTop() + return@setOnItemReselectedListener + } + val currentDestination = navController.currentDestination ?: return@setOnItemReselectedListener + val currentTabStartDestId = (navController.getBackStackEntry(it.itemId).destination as NavGraph).startDestinationId + if (currentDestination.id == currentTabStartDestId) return@setOnItemReselectedListener + navController.popBackStack(currentTabStartDestId, false) + } + } + + private fun setSelectedTab(tabs: List) { + val defaultTabResNameString = Utils.settingsHelper.getString(Constants.DEFAULT_TAB) + try { + var navId = 0 + if (defaultTabResNameString.isNotBlank()) { + navId = resources.getIdentifier(defaultTabResNameString, "id", packageName) + } + val startFragmentNavResId = if (navId <= 0) R.id.profile_nav_graph else navId + val tab = tabs.firstOrNull { it.navigationRootId == startFragmentNavResId } + // if (index < 0 || index >= tabs.size) index = 0 + val firstTab = tab ?: tabs[0] + startNavRootId = firstTab.navigationRootId + binding.bottomNavView.selectedItemId = firstTab.navigationRootId + } catch (e: Exception) { + Log.e(TAG, "Error parsing id", e) + } + } + + private fun setupAnonBottomNav(): List { + val selectedItemId = binding.bottomNavView.selectedItemId + val anonNavTabs = getAnonNavTabs(this) + val menu = binding.bottomNavView.menu + menu.clear() + for (tab in anonNavTabs) { + menu.add(0, tab.navigationRootId, 0, tab.title).setIcon(tab.iconResId) + } + if (selectedItemId != R.id.profile_nav_graph && selectedItemId != R.id.more_nav_graph && selectedItemId != R.id.favorites_nav_graph) { + binding.bottomNavView.selectedItemId = R.id.profile_nav_graph + } + return anonNavTabs + } + + private fun setupMainBottomNav(): List { + val menu = binding.bottomNavView.menu + menu.clear() + val navTabList = getLoggedInNavTabs(this).first + for (tab in navTabList) { + menu.add(0, tab.navigationRootId, 0, tab.title).setIcon(tab.iconResId) + } + return navTabList } private fun setupMenu(backStackSize: Int, destinationId: Int) { @@ -589,47 +507,10 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL fun navigateToThread(threadId: String?, threadTitle: String?) { if (threadId == null || threadTitle == null) return - currentNavControllerLiveData?.observe(this, object : Observer { - override fun onChanged(navController: NavController?) { - if (navController == null) return - if (navController.graph.id != R.id.direct_messages_nav_graph) return - try { - val currentDestination = navController.currentDestination - if (currentDestination != null && currentDestination.id == R.id.directMessagesInboxFragment) { - // if we are already on the inbox page, navigate to the thread - // need handler.post() to wait for the fragment manager to be ready to navigate - Handler(Looper.getMainLooper()).post { - val action = DirectMessageInboxFragmentDirections - .actionInboxToThread(threadId, threadTitle) - navController.navigate(action) - } - return - } - // add a destination change listener to navigate to thread once we are on the inbox page - navController.addOnDestinationChangedListener(object : OnDestinationChangedListener { - override fun onDestinationChanged( - controller: NavController, - destination: NavDestination, - arguments: Bundle?, - ) { - if (destination.id == R.id.directMessagesInboxFragment) { - val action = DirectMessageInboxFragmentDirections - .actionInboxToThread(threadId, threadTitle) - controller.navigate(action) - controller.removeOnDestinationChangedListener(this) - } - } - }) - // pop back stack until we reach the inbox page - navController.popBackStack(R.id.directMessagesInboxFragment, false) - } finally { - currentNavControllerLiveData?.removeObserver(this) - } - } - }) - val selectedItemId = binding.bottomNavView.selectedItemId - if (selectedItemId != R.navigation.direct_messages_nav_graph) { - setBottomNavSelectedTab(R.id.direct_messages_nav_graph) + try { + navController.navigate(getDirectThreadDeepLink(threadId, threadTitle)) + } catch (e: Exception) { + Log.e(TAG, "navigateToThread: ", e) } } @@ -652,14 +533,9 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL } private fun showProfileView(intentModel: IntentModel) { - val username = intentModel.text - // Log.d(TAG, "username: " + username); - val currentNavControllerLiveData = currentNavControllerLiveData ?: return - val navController = currentNavControllerLiveData.value - val bundle = Bundle() - bundle.putString("username", "@$username") try { - navController?.navigate(R.id.action_global_profileFragment, bundle) + val username = intentModel.text + navController.navigate(getProfileDeepLink(username)) } catch (e: Exception) { Log.e(TAG, "showProfileView: ", e) } @@ -668,65 +544,39 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL private fun showPostView(intentModel: IntentModel) { val shortCode = intentModel.text // 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 - } - val currentNavControllerLiveData = currentNavControllerLiveData ?: return@withContext - val navController = currentNavControllerLiveData.value - val bundle = Bundle() - bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, media) - try { - navController?.navigate(R.id.action_global_post_view, bundle) - } catch (e: Exception) { - Log.e(TAG, "showPostView: ", e) - } - } - } catch (e: Exception) { - Log.e(TAG, "showPostView: ", e) - } finally { - withContext(Dispatchers.Main) { - alertDialog.dismiss() - } - } + try { + navController.navigate(getPostDeepLink(shortCode)) + } catch (e: Exception) { + Log.e(TAG, "showPostView: ", e) } } private fun showLocationView(intentModel: IntentModel) { val locationId = intentModel.text // Log.d(TAG, "locationId: " + locationId); - val currentNavControllerLiveData = currentNavControllerLiveData ?: return - val navController = currentNavControllerLiveData.value - val bundle = Bundle() - bundle.putLong("locationId", locationId.toLong()) - navController?.navigate(R.id.action_global_locationFragment, bundle) + try { + navController.navigate(getLocationDeepLink(locationId)) + } catch (e: Exception) { + Log.e(TAG, "showLocationView: ", e) + } } private fun showHashtagView(intentModel: IntentModel) { val hashtag = intentModel.text // Log.d(TAG, "hashtag: " + hashtag); - val currentNavControllerLiveData = currentNavControllerLiveData ?: return - val navController = currentNavControllerLiveData.value - val bundle = Bundle() - bundle.putString("hashtag", hashtag) - navController?.navigate(R.id.action_global_hashTagFragment, bundle) + try { + navController.navigate(getHashtagDeepLink(hashtag)) + } catch (e: Exception) { + Log.e(TAG, "showHashtagView: ", e) + } } private fun showActivityView() { - val currentNavControllerLiveData = currentNavControllerLiveData ?: return - val navController = currentNavControllerLiveData.value - val bundle = Bundle() - bundle.putString("type", "notif") - navController?.navigate(R.id.action_global_notificationsViewerFragment, bundle) + try { + navController.navigate(getNotificationsDeepLink("notif")) + } catch (e: Exception) { + Log.e(TAG, "showActivityView: ", e) + } } private fun bindActivityCheckerService() { @@ -743,28 +593,27 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL val bottomNavView: BottomNavigationView 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() { binding.appBarLayout.visibility = View.VISIBLE setScrollingBehaviour() setSupportActionBar(binding.toolbar) - val currentNavControllerLiveData = currentNavControllerLiveData ?: return - setupNavigation(binding.toolbar, currentNavControllerLiveData.value) + setupActionBarWithNavController(navController, appBarConfiguration) } val collapsingToolbarView: CollapsingToolbarLayout @@ -808,8 +657,7 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL binding.appBarLayout.visibility = View.GONE removeScrollingBehaviour() setSupportActionBar(toolbar) - if (currentNavControllerLiveData == null) return - setupNavigation(toolbar, currentNavControllerLiveData?.value) + NavigationUI.setupWithNavController(toolbar, navController, appBarConfiguration) } val rootView: View get() = binding.root @@ -839,8 +687,8 @@ class MainActivity : BaseLanguageActivity(), FragmentManager.OnBackStackChangedL companion object { private const val TAG = "MainActivity" - private const val FIRST_FRAGMENT_GRAPH_INDEX_KEY = "firstFragmentGraphIndex" private const val LAST_SELECT_NAV_MENU_ID = "lastSelectedNavMenuId" + private val SEARCH_VISIBLE_DESTINATIONS: List = ImmutableList.of( R.id.feedFragment, R.id.profileFragment, diff --git a/app/src/main/java/awais/instagrabber/customviews/BarinstaFragmentNavigator.kt b/app/src/main/java/awais/instagrabber/customviews/BarinstaFragmentNavigator.kt new file mode 100644 index 00000000..1220c0d6 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/customviews/BarinstaFragmentNavigator.kt @@ -0,0 +1,90 @@ +package awais.instagrabber.customviews + +import android.content.Context +import androidx.fragment.app.FragmentManager +import androidx.navigation.NavBackStackEntry +import androidx.navigation.NavOptions +import androidx.navigation.Navigator +import androidx.navigation.fragment.FragmentNavigator +import androidx.navigation.navOptions +import awais.instagrabber.R +import awais.instagrabber.fragments.settings.PreferenceKeys +import awais.instagrabber.utils.Utils + +private val defaultNavOptions = navOptions { + anim { + enter = R.anim.slide_in_right + exit = R.anim.slide_out_left + popEnter = android.R.anim.slide_in_left + popExit = android.R.anim.slide_out_right + } +} + +private val emptyNavOptions = navOptions {} + +/** + * Needs to replace FragmentNavigator and replacing is done with name in annotation. + * Navigation method will use defaults for fragments transitions animations. + */ +@Navigator.Name("fragment") +class BarinstaFragmentNavigator( + context: Context, + fragmentManager: FragmentManager, + containerId: Int +) : FragmentNavigator(context, fragmentManager, containerId) { + + override fun navigate( + entries: List, + navOptions: NavOptions?, + navigatorExtras: Navigator.Extras? + ) { + val disableTransitions = Utils.settingsHelper.getBoolean(PreferenceKeys.PREF_DISABLE_SCREEN_TRANSITIONS) + if (disableTransitions) { + super.navigate(entries, navOptions, navigatorExtras) + return + } + // this will try to fill in empty animations with defaults when no shared element transitions + // https://developer.android.com/guide/navigation/navigation-animate-transitions#shared-element + val hasSharedElements = navigatorExtras != null && navigatorExtras is Extras + val navOptions1 = if (hasSharedElements) navOptions else navOptions.fillEmptyAnimationsWithDefaults() + super.navigate(entries, navOptions1, navigatorExtras) + } + + private fun NavOptions?.fillEmptyAnimationsWithDefaults(): NavOptions = + this?.copyNavOptionsWithDefaultAnimations() ?: defaultNavOptions + + private fun NavOptions.copyNavOptionsWithDefaultAnimations(): NavOptions = let { originalNavOptions -> + navOptions { + launchSingleTop = originalNavOptions.shouldLaunchSingleTop() + popUpTo(originalNavOptions.popUpToId) { + inclusive = originalNavOptions.isPopUpToInclusive() + saveState = originalNavOptions.shouldPopUpToSaveState() + } + originalNavOptions.popUpToRoute?.let { + popUpTo(it) { + inclusive = originalNavOptions.isPopUpToInclusive() + saveState = originalNavOptions.shouldPopUpToSaveState() + } + } + restoreState = originalNavOptions.shouldRestoreState() + anim { + enter = + if (originalNavOptions.enterAnim == emptyNavOptions.enterAnim) defaultNavOptions.enterAnim + else originalNavOptions.enterAnim + exit = + if (originalNavOptions.exitAnim == emptyNavOptions.exitAnim) defaultNavOptions.exitAnim + else originalNavOptions.exitAnim + popEnter = + if (originalNavOptions.popEnterAnim == emptyNavOptions.popEnterAnim) defaultNavOptions.popEnterAnim + else originalNavOptions.popEnterAnim + popExit = + if (originalNavOptions.popExitAnim == emptyNavOptions.popExitAnim) defaultNavOptions.popExitAnim + else originalNavOptions.popExitAnim + } + } + } + + private companion object { + private const val TAG = "FragmentNavigator" + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/customviews/BarinstaNavHostFragment.kt b/app/src/main/java/awais/instagrabber/customviews/BarinstaNavHostFragment.kt new file mode 100644 index 00000000..24979a95 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/customviews/BarinstaNavHostFragment.kt @@ -0,0 +1,14 @@ +package awais.instagrabber.customviews + +import androidx.navigation.NavHostController +import androidx.navigation.fragment.NavHostFragment + +class BarinstaNavHostFragment : NavHostFragment() { + override fun onCreateNavHostController(navHostController: NavHostController) { + super.onCreateNavHostController(navHostController) + navHostController.navigatorProvider.addNavigator( + // this replaces FragmentNavigator + BarinstaFragmentNavigator(requireContext(), childFragmentManager, id) + ) + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/customviews/ChatMessageLayout.java b/app/src/main/java/awais/instagrabber/customviews/ChatMessageLayout.java index 7d09d929..7b50a068 100644 --- a/app/src/main/java/awais/instagrabber/customviews/ChatMessageLayout.java +++ b/app/src/main/java/awais/instagrabber/customviews/ChatMessageLayout.java @@ -9,7 +9,6 @@ import android.widget.TextView; import androidx.annotation.NonNull; import androidx.annotation.Nullable; -import androidx.constraintlayout.widget.ConstraintLayout; import awais.instagrabber.R; diff --git a/app/src/main/java/awais/instagrabber/customviews/FragmentNavigatorWithDefaultAnimations.java b/app/src/main/java/awais/instagrabber/customviews/FragmentNavigatorWithDefaultAnimations.java deleted file mode 100644 index 358e34d8..00000000 --- a/app/src/main/java/awais/instagrabber/customviews/FragmentNavigatorWithDefaultAnimations.java +++ /dev/null @@ -1,75 +0,0 @@ -package awais.instagrabber.customviews; - -import android.content.Context; -import android.os.Bundle; - -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.fragment.app.FragmentManager; -import androidx.navigation.NavDestination; -import androidx.navigation.NavOptions; -import androidx.navigation.Navigator; -import androidx.navigation.fragment.FragmentNavigator; - -import awais.instagrabber.R; - -@Navigator.Name("fragment") -public class FragmentNavigatorWithDefaultAnimations extends FragmentNavigator { - - private final NavOptions emptyNavOptions = new NavOptions.Builder().build(); - // private final NavOptions defaultNavOptions = new NavOptions.Builder() - // .setEnterAnim(R.animator.nav_default_enter_anim) - // .setExitAnim(R.animator.nav_default_exit_anim) - // .setPopEnterAnim(R.animator.nav_default_pop_enter_anim) - // .setPopExitAnim(R.animator.nav_default_pop_exit_anim) - // .build(); - - private final NavOptions defaultNavOptions = new NavOptions.Builder() - .setEnterAnim(R.anim.slide_in_right) - .setExitAnim(R.anim.slide_out_left) - .setPopEnterAnim(android.R.anim.slide_in_left) - .setPopExitAnim(android.R.anim.slide_out_right) - .build(); - - public FragmentNavigatorWithDefaultAnimations(@NonNull final Context context, - @NonNull final FragmentManager manager, - final int containerId) { - super(context, manager, containerId); - } - - @Nullable - @Override - public NavDestination navigate(@NonNull final Destination destination, - @Nullable final Bundle args, - @Nullable final NavOptions navOptions, - @Nullable final Navigator.Extras navigatorExtras) { - // this will try to fill in empty animations with defaults when no shared element transitions are set - // https://developer.android.com/guide/navigation/navigation-animate-transitions#shared-element - final boolean shouldUseTransitionsInstead = navigatorExtras != null; - final NavOptions navOptions1 = shouldUseTransitionsInstead ? navOptions : fillEmptyAnimationsWithDefaults(navOptions); - return super.navigate(destination, args, navOptions1, navigatorExtras); - } - - private NavOptions fillEmptyAnimationsWithDefaults(@Nullable final NavOptions navOptions) { - if (navOptions == null) { - return defaultNavOptions; - } - return copyNavOptionsWithDefaultAnimations(navOptions); - } - - @NonNull - private NavOptions copyNavOptionsWithDefaultAnimations(@NonNull final NavOptions navOptions) { - return new NavOptions.Builder() - .setLaunchSingleTop(navOptions.shouldLaunchSingleTop()) - .setPopUpTo(navOptions.getPopUpTo(), navOptions.isPopUpToInclusive()) - .setEnterAnim(navOptions.getEnterAnim() == emptyNavOptions.getEnterAnim() - ? defaultNavOptions.getEnterAnim() : navOptions.getEnterAnim()) - .setExitAnim(navOptions.getExitAnim() == emptyNavOptions.getExitAnim() - ? defaultNavOptions.getExitAnim() : navOptions.getExitAnim()) - .setPopEnterAnim(navOptions.getPopEnterAnim() == emptyNavOptions.getPopEnterAnim() - ? defaultNavOptions.getPopEnterAnim() : navOptions.getPopEnterAnim()) - .setPopExitAnim(navOptions.getPopExitAnim() == emptyNavOptions.getPopExitAnim() - ? defaultNavOptions.getPopExitAnim() : navOptions.getPopExitAnim()) - .build(); - } -} diff --git a/app/src/main/java/awais/instagrabber/customviews/NavHostFragmentWithDefaultAnimations.java b/app/src/main/java/awais/instagrabber/customviews/NavHostFragmentWithDefaultAnimations.java deleted file mode 100644 index 11621a6c..00000000 --- a/app/src/main/java/awais/instagrabber/customviews/NavHostFragmentWithDefaultAnimations.java +++ /dev/null @@ -1,60 +0,0 @@ -package awais.instagrabber.customviews; - -import android.os.Bundle; - -import androidx.annotation.NavigationRes; -import androidx.annotation.NonNull; -import androidx.annotation.Nullable; -import androidx.navigation.NavController; -import androidx.navigation.Navigator; -import androidx.navigation.fragment.FragmentNavigator; -import androidx.navigation.fragment.NavHostFragment; - -public class NavHostFragmentWithDefaultAnimations extends NavHostFragment { - private static final String KEY_GRAPH_ID = "android-support-nav:fragment:graphId"; - private static final String KEY_START_DESTINATION_ARGS = - "android-support-nav:fragment:startDestinationArgs"; - private static final String KEY_NAV_CONTROLLER_STATE = - "android-support-nav:fragment:navControllerState"; - private static final String KEY_DEFAULT_NAV_HOST = "android-support-nav:fragment:defaultHost"; - - @NonNull - public static NavHostFragment create(@NavigationRes int graphResId) { - return create(graphResId, null); - } - - @NonNull - public static NavHostFragment create(@NavigationRes int graphResId, - @Nullable Bundle startDestinationArgs) { - Bundle b = null; - if (graphResId != 0) { - b = new Bundle(); - b.putInt(KEY_GRAPH_ID, graphResId); - } - if (startDestinationArgs != null) { - if (b == null) { - b = new Bundle(); - } - b.putBundle(KEY_START_DESTINATION_ARGS, startDestinationArgs); - } - - final NavHostFragmentWithDefaultAnimations result = new NavHostFragmentWithDefaultAnimations(); - if (b != null) { - result.setArguments(b); - } - return result; - } - - @NonNull - @Override - protected Navigator createFragmentNavigator() { - return new FragmentNavigatorWithDefaultAnimations(requireContext(), getChildFragmentManager(), getId()); - } - - @Override - protected void onCreateNavController(@NonNull final NavController navController) { - super.onCreateNavController(navController); - navController.getNavigatorProvider() - .addNavigator(new FragmentNavigatorWithDefaultAnimations(requireContext(), getChildFragmentManager(), getId())); - } -} diff --git a/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java b/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java index 6b05d2fe..698cbb94 100644 --- a/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java +++ b/app/src/main/java/awais/instagrabber/customviews/PostsRecyclerView.java @@ -3,12 +3,11 @@ package awais.instagrabber.customviews; import android.content.Context; import android.util.AttributeSet; import android.util.Log; -import android.view.View; -import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.lifecycle.LifecycleOwner; +import androidx.lifecycle.LiveData; import androidx.lifecycle.ViewModelProvider; import androidx.lifecycle.ViewModelStoreOwner; import androidx.recyclerview.widget.LinearSmoothScroller; @@ -27,24 +26,18 @@ import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; import java.util.List; -import java.util.function.Function; import awais.instagrabber.adapters.FeedAdapterV2; import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration; import awais.instagrabber.customviews.helpers.PostFetcher; 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.repositories.responses.Media; -import awais.instagrabber.utils.KeywordsFilterUtils; import awais.instagrabber.utils.ResponseBodyUtils; import awais.instagrabber.utils.Utils; import awais.instagrabber.viewmodels.MediaViewModel; import awais.instagrabber.workers.DownloadWorker; -import static awais.instagrabber.utils.Utils.settingsHelper; - public class PostsRecyclerView extends RecyclerView { private static final String TAG = "PostsRecyclerView"; @@ -52,7 +45,6 @@ public class PostsRecyclerView extends RecyclerView { private PostsLayoutPreferences layoutPreferences; private PostFetcher.PostFetchService postFetchService; private Transition transition; - private PostFetcher postFetcher; private ViewModelStoreOwner viewModelStoreOwner; private FeedAdapterV2 feedAdapter; private LifecycleOwner lifeCycleOwner; @@ -63,40 +55,9 @@ public class PostsRecyclerView extends RecyclerView { private FeedAdapterV2.FeedItemCallback feedItemCallback; private boolean shouldScrollToTop; private FeedAdapterV2.SelectionModeCallback selectionModeCallback; - private Function headerViewCreator; - private Function headerBinder; - private boolean refresh = true; private final List fetchStatusChangeListeners = new ArrayList<>(); - private final FetchListener> fetchListener = new FetchListener>() { - @Override - public void onResult(final List result) { - if (refresh) { - refresh = false; - mediaViewModel.getList().postValue(result); - shouldScrollToTop = true; - dispatchFetchStatus(); - return; - } - final List models = mediaViewModel.getList().getValue(); - final List modelsCopy = models == null ? new ArrayList<>() : new ArrayList<>(models); - if (settingsHelper.getBoolean(PreferenceKeys.TOGGLE_KEYWORD_FILTER)) { - final ArrayList 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()) { @Override protected int getVerticalSnapPreference() { @@ -199,18 +160,22 @@ public class PostsRecyclerView extends RecyclerView { private void initSelf() { try { - mediaViewModel = new ViewModelProvider(viewModelStoreOwner).get(MediaViewModel.class); + mediaViewModel = new ViewModelProvider( + viewModelStoreOwner, + new MediaViewModel.ViewModelFactory(postFetchService) + ).get(MediaViewModel.class); } catch (Exception e) { Log.e(TAG, "initSelf: ", e); } if (mediaViewModel == null) return; - mediaViewModel.getList().observe(lifeCycleOwner, list -> feedAdapter.submitList(list, () -> { + final LiveData> mediaListLiveData = mediaViewModel.getList(); + mediaListLiveData.observe(lifeCycleOwner, list -> feedAdapter.submitList(list, () -> { + dispatchFetchStatus(); // postDelayed(this::fetchMoreIfPossible, 1000); if (!shouldScrollToTop) return; shouldScrollToTop = false; post(() -> smoothScrollToPosition(0)); })); - postFetcher = new PostFetcher(postFetchService, fetchListener); if (layoutPreferences.getHasGap()) { addItemDecoration(gridSpacingItemDecoration); } @@ -218,18 +183,20 @@ public class PostsRecyclerView extends RecyclerView { setNestedScrollingEnabled(true); setItemAnimator(null); lazyLoader = new RecyclerLazyLoaderAtEdge(layoutManager, (page) -> { - if (postFetcher.hasMore()) { - postFetcher.fetch(); + if (mediaViewModel.hasMore()) { + mediaViewModel.fetch(); dispatchFetchStatus(); } }); addOnScrollListener(lazyLoader); - postFetcher.fetch(); - dispatchFetchStatus(); + if (mediaListLiveData.getValue() == null || mediaListLiveData.getValue().isEmpty()) { + mediaViewModel.fetch(); + dispatchFetchStatus(); + } } private void fetchMoreIfPossible() { - if (!postFetcher.hasMore()) return; + if (!mediaViewModel.hasMore()) return; if (feedAdapter.getItemCount() == 0) return; final LayoutManager layoutManager = getLayoutManager(); if (!(layoutManager instanceof StaggeredGridLayoutManager)) return; @@ -238,7 +205,7 @@ public class PostsRecyclerView extends RecyclerView { if (allNoPosition) return; final boolean match = Arrays.stream(itemPositions).anyMatch(position -> position == feedAdapter.getItemCount() - 1); if (!match) return; - postFetcher.fetch(); + mediaViewModel.fetch(); dispatchFetchStatus(); } @@ -268,6 +235,7 @@ public class PostsRecyclerView extends RecyclerView { private List getDisplayUrl(final Media feedModel) { List urls = Collections.emptyList(); + if (feedModel == null || feedModel.getType() == null) return urls; switch (feedModel.getType()) { case MEDIA_TYPE_IMAGE: case MEDIA_TYPE_VIDEO: @@ -320,20 +288,18 @@ public class PostsRecyclerView extends RecyclerView { } public void refresh() { - refresh = true; + shouldScrollToTop = true; if (lazyLoader != null) { lazyLoader.resetState(); } - if (postFetcher != null) { - // mediaViewModel.getList().postValue(Collections.emptyList()); - postFetcher.reset(); - postFetcher.fetch(); + if (mediaViewModel != null) { + mediaViewModel.refresh(); } dispatchFetchStatus(); } public boolean isFetching() { - return postFetcher != null && postFetcher.isFetching(); + return mediaViewModel != null && mediaViewModel.isFetching(); } public PostsRecyclerView addFetchStatusChangeListener(final FetchStatusChangeListener fetchStatusChangeListener) { @@ -369,6 +335,7 @@ public class PostsRecyclerView extends RecyclerView { protected void onDetachedFromWindow() { super.onDetachedFromWindow(); lifeCycleOwner = null; + initCalled = false; } @Override diff --git a/app/src/main/java/awais/instagrabber/customviews/helpers/PostFetcher.java b/app/src/main/java/awais/instagrabber/customviews/helpers/PostFetcher.java index 367b6544..a017ebaa 100644 --- a/app/src/main/java/awais/instagrabber/customviews/helpers/PostFetcher.java +++ b/app/src/main/java/awais/instagrabber/customviews/helpers/PostFetcher.java @@ -21,21 +21,20 @@ public class PostFetcher { } public void fetch() { - if (!fetching) { - fetching = true; - postFetchService.fetch(new FetchListener>() { - @Override - public void onResult(final List result) { - fetching = false; - fetchListener.onResult(result); - } + if (fetching) return; + fetching = true; + postFetchService.fetch(new FetchListener>() { + @Override + public void onResult(final List result) { + fetching = false; + fetchListener.onResult(result); + } - @Override - public void onFailure(final Throwable t) { - Log.e(TAG, "onFailure: ", t); - } - }); - } + @Override + public void onFailure(final Throwable t) { + Log.e(TAG, "onFailure: ", t); + } + }); } public void reset() { diff --git a/app/src/main/java/awais/instagrabber/dialogs/PostLoadingDialogFragment.kt b/app/src/main/java/awais/instagrabber/dialogs/PostLoadingDialogFragment.kt new file mode 100644 index 00000000..66cea86a --- /dev/null +++ b/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() + } + } + } + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/dialogs/PostsLayoutPreferencesDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/PostsLayoutPreferencesDialogFragment.java index 9fc1fa6c..9e35ae54 100644 --- a/app/src/main/java/awais/instagrabber/dialogs/PostsLayoutPreferencesDialogFragment.java +++ b/app/src/main/java/awais/instagrabber/dialogs/PostsLayoutPreferencesDialogFragment.java @@ -80,7 +80,6 @@ public class PostsLayoutPreferencesDialogFragment extends DialogFragment { initAvatarsToggle(); initCornersToggle(); initGapToggle(); - initAnimationDisableToggle(); } private void initLayoutToggle() { @@ -170,11 +169,6 @@ public class PostsLayoutPreferencesDialogFragment extends DialogFragment { binding.showGapToggle.setOnCheckedChangeListener((buttonView, isChecked) -> preferencesBuilder.setHasGap(isChecked)); } - private void initAnimationDisableToggle() { - binding.disableAnimationToggle.setChecked(preferencesBuilder.isAnimationDisabled()); - binding.disableAnimationToggle.setOnCheckedChangeListener((buttonView, isChecked) -> preferencesBuilder.setAnimationDisabled(isChecked)); - } - private int getSelectedLayoutId() { switch (preferencesBuilder.getType()) { case STAGGERED_GRID: diff --git a/app/src/main/java/awais/instagrabber/dialogs/TabOrderPreferenceDialogFragment.java b/app/src/main/java/awais/instagrabber/dialogs/TabOrderPreferenceDialogFragment.java index 0ac2dd23..ba6fbab0 100644 --- a/app/src/main/java/awais/instagrabber/dialogs/TabOrderPreferenceDialogFragment.java +++ b/app/src/main/java/awais/instagrabber/dialogs/TabOrderPreferenceDialogFragment.java @@ -4,7 +4,6 @@ import android.app.Dialog; import android.content.Context; import android.graphics.Canvas; import android.os.Bundle; -import android.util.Pair; import android.view.View; import androidx.annotation.NonNull; @@ -28,7 +27,9 @@ import awais.instagrabber.adapters.TabsAdapter; import awais.instagrabber.adapters.viewholder.TabViewHolder; import awais.instagrabber.fragments.settings.PreferenceKeys; import awais.instagrabber.models.Tab; +import awais.instagrabber.utils.NavigationHelperKt; import awais.instagrabber.utils.Utils; +import kotlin.Pair; import static androidx.recyclerview.widget.ItemTouchHelper.ACTION_STATE_DRAG; import static androidx.recyclerview.widget.ItemTouchHelper.DOWN; @@ -235,9 +236,10 @@ public class TabOrderPreferenceDialogFragment extends DialogFragment { } private void saveNewOrder() { - final String newOrderString = newOrderTabs.stream() - .map(Tab::getGraphName) - .collect(Collectors.joining(",")); + final String newOrderString = newOrderTabs + .stream() + .map(tab -> NavigationHelperKt.getNavGraphNameForNavRootId(tab.getNavigationRootId())) + .collect(Collectors.joining(",")); Utils.settingsHelper.putString(PreferenceKeys.PREF_TAB_ORDER, newOrderString); } @@ -258,12 +260,12 @@ public class TabOrderPreferenceDialogFragment extends DialogFragment { itemTouchHelper.attachToRecyclerView(list); adapter = new TabsAdapter(tabAdapterCallback); list.setAdapter(adapter); - final Pair, List> navTabListPair = Utils.getNavTabList(context); - tabsInPref = navTabListPair.first; + final Pair, List> navTabListPair = NavigationHelperKt.getLoggedInNavTabs(context); + tabsInPref = navTabListPair.getFirst(); // initially set newOrderTabs and newOtherTabs same as current tabs - newOrderTabs = navTabListPair.first; - newOtherTabs = navTabListPair.second; - adapter.submitList(navTabListPair.first, navTabListPair.second); + newOrderTabs = navTabListPair.getFirst(); + newOtherTabs = navTabListPair.getSecond(); + adapter.submitList(navTabListPair.getFirst(), navTabListPair.getSecond()); return list; } diff --git a/app/src/main/java/awais/instagrabber/dialogs/TimeSettingsDialog.java b/app/src/main/java/awais/instagrabber/dialogs/TimeSettingsDialog.java index 7ad34fe1..c7238bb1 100755 --- a/app/src/main/java/awais/instagrabber/dialogs/TimeSettingsDialog.java +++ b/app/src/main/java/awais/instagrabber/dialogs/TimeSettingsDialog.java @@ -16,18 +16,20 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.fragment.app.DialogFragment; -import java.time.format.DateTimeFormatter; import java.time.Instant; import java.time.LocalDateTime; import java.time.ZoneId; +import java.time.format.DateTimeFormatter; +import awais.instagrabber.R; import awais.instagrabber.databinding.DialogTimeSettingsBinding; +import awais.instagrabber.utils.DateUtils; import awais.instagrabber.utils.LocaleUtils; import awais.instagrabber.utils.TextUtils; public final class TimeSettingsDialog extends DialogFragment implements AdapterView.OnItemSelectedListener, CompoundButton.OnCheckedChangeListener, View.OnClickListener, TextWatcher { - private DialogTimeSettingsBinding timeSettingsBinding; + private DialogTimeSettingsBinding binding; private final LocalDateTime magicDate; private DateTimeFormatter currentFormat; private String selectedFormat; @@ -55,57 +57,67 @@ public final class TimeSettingsDialog extends DialogFragment implements AdapterV @Override public View onCreateView(@NonNull final LayoutInflater inflater, @Nullable final ViewGroup container, @Nullable final Bundle savedInstanceState) { - timeSettingsBinding = DialogTimeSettingsBinding.inflate(inflater, container, false); + binding = DialogTimeSettingsBinding.inflate(inflater, container, false); - timeSettingsBinding.cbCustomFormat.setOnCheckedChangeListener(this); - timeSettingsBinding.cbCustomFormat.setChecked(customDateTimeFormatEnabled); - timeSettingsBinding.cbSwapTimeDate.setChecked(swapDateTimeEnabled); - timeSettingsBinding.etCustomFormat.setText(customDateTimeFormat); + binding.cbCustomFormat.setOnCheckedChangeListener(this); + binding.cbCustomFormat.setChecked(customDateTimeFormatEnabled); + binding.cbSwapTimeDate.setChecked(swapDateTimeEnabled); + binding.customFormatEditText.setText(customDateTimeFormat); final String[] dateTimeFormat = dateTimeSelection.split(";"); // output = time;separator;date - timeSettingsBinding.spTimeFormat.setSelection(Integer.parseInt(dateTimeFormat[0])); - timeSettingsBinding.spSeparator.setSelection(Integer.parseInt(dateTimeFormat[1])); - timeSettingsBinding.spDateFormat.setSelection(Integer.parseInt(dateTimeFormat[2])); + binding.spTimeFormat.setSelection(Integer.parseInt(dateTimeFormat[0])); + binding.spSeparator.setSelection(Integer.parseInt(dateTimeFormat[1])); + binding.spDateFormat.setSelection(Integer.parseInt(dateTimeFormat[2])); - timeSettingsBinding.cbSwapTimeDate.setOnCheckedChangeListener(this); + binding.cbSwapTimeDate.setOnCheckedChangeListener(this); refreshTimeFormat(); - timeSettingsBinding.spTimeFormat.setOnItemSelectedListener(this); - timeSettingsBinding.spDateFormat.setOnItemSelectedListener(this); - timeSettingsBinding.spSeparator.setOnItemSelectedListener(this); + binding.spTimeFormat.setOnItemSelectedListener(this); + binding.spDateFormat.setOnItemSelectedListener(this); + binding.spSeparator.setOnItemSelectedListener(this); - timeSettingsBinding.etCustomFormat.addTextChangedListener(this); - timeSettingsBinding.btnConfirm.setOnClickListener(this); - timeSettingsBinding.btnInfo.setOnClickListener(this); + binding.customFormatEditText.addTextChangedListener(this); + binding.btnConfirm.setOnClickListener(this); + binding.customFormatField.setEndIconOnClickListener(this); - return timeSettingsBinding.getRoot(); + return binding.getRoot(); } private void refreshTimeFormat() { - if (timeSettingsBinding.cbCustomFormat.isChecked()) - selectedFormat = timeSettingsBinding.etCustomFormat.getText().toString(); - else { - final String sepStr = String.valueOf(timeSettingsBinding.spSeparator.getSelectedItem()); - final String timeStr = String.valueOf(timeSettingsBinding.spTimeFormat.getSelectedItem()); - final String dateStr = String.valueOf(timeSettingsBinding.spDateFormat.getSelectedItem()); + final boolean isCustom = binding.cbCustomFormat.isChecked(); + if (isCustom) { + final Editable text = binding.customFormatEditText.getText(); + if (text != null) { + selectedFormat = text.toString(); + } + } else { + final String sepStr = String.valueOf(binding.spSeparator.getSelectedItem()); + final String timeStr = String.valueOf(binding.spTimeFormat.getSelectedItem()); + final String dateStr = String.valueOf(binding.spDateFormat.getSelectedItem()); - final boolean isSwapTime = timeSettingsBinding.cbSwapTimeDate.isChecked(); - final boolean isBlankSeparator = timeSettingsBinding.spSeparator.getSelectedItemPosition() <= 0; + final boolean isSwapTime = binding.cbSwapTimeDate.isChecked(); + final boolean isBlankSeparator = binding.spSeparator.getSelectedItemPosition() <= 0; selectedFormat = (isSwapTime ? dateStr : timeStr) + (isBlankSeparator ? " " : " '" + sepStr + "' ") + (isSwapTime ? timeStr : dateStr); } - timeSettingsBinding.btnConfirm.setEnabled(true); + binding.btnConfirm.setEnabled(true); try { currentFormat = DateTimeFormatter.ofPattern(selectedFormat, LocaleUtils.getCurrentLocale()); - timeSettingsBinding.timePreview.setText(magicDate.format(currentFormat)); - } - catch (Exception e) { - timeSettingsBinding.btnConfirm.setEnabled(false); - timeSettingsBinding.timePreview.setText(null); + if (isCustom) { + final boolean valid = !TextUtils.isEmpty(selectedFormat) && DateUtils.checkFormatterValid(currentFormat); + binding.customFormatField.setError(valid ? null :getString(R.string.invalid_format)); + if (!valid) { + binding.btnConfirm.setEnabled(false); + } + } + binding.timePreview.setText(magicDate.format(currentFormat)); + } catch (Exception e) { + binding.btnConfirm.setEnabled(false); + binding.timePreview.setText(null); } } @@ -116,16 +128,14 @@ public final class TimeSettingsDialog extends DialogFragment implements AdapterV @Override public void onCheckedChanged(final CompoundButton buttonView, final boolean isChecked) { - if (buttonView == timeSettingsBinding.cbCustomFormat) { - final View parent = (View) timeSettingsBinding.etCustomFormat.getParent(); - parent.setVisibility(isChecked ? View.VISIBLE : View.GONE); - timeSettingsBinding.etCustomFormat.setEnabled(isChecked); - timeSettingsBinding.btnInfo.setEnabled(isChecked); + if (buttonView == binding.cbCustomFormat) { + binding.customFormatField.setVisibility(isChecked ? View.VISIBLE : View.GONE); + binding.customFormatField.setEnabled(isChecked); - timeSettingsBinding.spTimeFormat.setEnabled(!isChecked); - timeSettingsBinding.spDateFormat.setEnabled(!isChecked); - timeSettingsBinding.spSeparator.setEnabled(!isChecked); - timeSettingsBinding.cbSwapTimeDate.setEnabled(!isChecked); + binding.spTimeFormat.setEnabled(!isChecked); + binding.spDateFormat.setEnabled(!isChecked); + binding.spSeparator.setEnabled(!isChecked); + binding.cbSwapTimeDate.setEnabled(!isChecked); } refreshTimeFormat(); } @@ -137,20 +147,21 @@ public final class TimeSettingsDialog extends DialogFragment implements AdapterV @Override public void onClick(final View v) { - if (v == timeSettingsBinding.btnConfirm) { + if (v == binding.btnConfirm) { if (onConfirmListener != null) { onConfirmListener.onConfirm( - timeSettingsBinding.cbCustomFormat.isChecked(), - timeSettingsBinding.spTimeFormat.getSelectedItemPosition(), - timeSettingsBinding.spSeparator.getSelectedItemPosition(), - timeSettingsBinding.spDateFormat.getSelectedItemPosition(), + binding.cbCustomFormat.isChecked(), + binding.spTimeFormat.getSelectedItemPosition(), + binding.spSeparator.getSelectedItemPosition(), + binding.spDateFormat.getSelectedItemPosition(), selectedFormat, - timeSettingsBinding.cbSwapTimeDate.isChecked()); + binding.cbSwapTimeDate.isChecked()); } dismiss(); - } else if (v == timeSettingsBinding.btnInfo) { - timeSettingsBinding.customPanel.setVisibility(timeSettingsBinding.customPanel - .getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE); + } else if (v == binding.customFormatField.findViewById(R.id.text_input_end_icon)) { + binding.customPanel.setVisibility( + binding.customPanel.getVisibility() == View.VISIBLE ? View.GONE : View.VISIBLE + ); } } diff --git a/app/src/main/java/awais/instagrabber/fragments/CollectionPostsFragment.java b/app/src/main/java/awais/instagrabber/fragments/CollectionPostsFragment.java index fc3991fb..450ca951 100644 --- a/app/src/main/java/awais/instagrabber/fragments/CollectionPostsFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/CollectionPostsFragment.java @@ -28,7 +28,6 @@ import androidx.appcompat.app.AlertDialog; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.graphics.ColorUtils; import androidx.fragment.app.Fragment; -import androidx.navigation.NavController; import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; @@ -53,7 +52,9 @@ import awais.instagrabber.databinding.FragmentCollectionPostsBinding; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.enums.PostItemType; +import awais.instagrabber.repositories.responses.Location; import awais.instagrabber.repositories.responses.Media; +import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.saved.SavedCollection; import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.Constants; @@ -76,7 +77,6 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay private Set selectedFeedModels; private CollectionService collectionService; private PostsLayoutPreferences layoutPreferences = Utils.getPostsLayoutPreferences(Constants.PREF_SAVED_POSTS_LAYOUT); - private MenuItem deleteMenu, editMenu; private final OnBackPressedCallback onBackPressedCallback = new OnBackPressedCallback(false) { @Override @@ -117,12 +117,18 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay @Override public void onCommentsClick(final Media feedModel) { - final NavDirections commentsAction = CollectionPostsFragmentDirections.actionGlobalCommentsViewerFragment( - feedModel.getCode(), - feedModel.getPk(), - feedModel.getUser().getPk() - ); - NavHostFragment.findNavController(CollectionPostsFragment.this).navigate(commentsAction); + final User user = feedModel.getUser(); + if (user == null) return; + try { + final NavDirections commentsAction = CollectionPostsFragmentDirections.actionToComments( + feedModel.getCode(), + feedModel.getPk(), + user.getPk() + ); + NavHostFragment.findNavController(CollectionPostsFragment.this).navigate(commentsAction); + } catch (Exception e) { + Log.e(TAG, "onCommentsClick: ", e); + } } @Override @@ -134,14 +140,24 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay @Override public void onHashtagClick(final String hashtag) { - final NavDirections action = CollectionPostsFragmentDirections.actionGlobalHashTagFragment(hashtag); - NavHostFragment.findNavController(CollectionPostsFragment.this).navigate(action); + try { + final NavDirections action = CollectionPostsFragmentDirections.actionToHashtag(hashtag); + NavHostFragment.findNavController(CollectionPostsFragment.this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "onHashtagClick: ", e); + } } @Override public void onLocationClick(final Media feedModel) { - final NavDirections action = CollectionPostsFragmentDirections.actionGlobalLocationFragment(feedModel.getLocation().getPk()); - NavHostFragment.findNavController(CollectionPostsFragment.this).navigate(action); + final Location location = feedModel.getLocation(); + if (location == null) return; + try { + final NavDirections action = CollectionPostsFragmentDirections.actionToLocation(location.getPk()); + NavHostFragment.findNavController(CollectionPostsFragment.this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "onLocationClick: ", e); + } } @Override @@ -151,12 +167,16 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay @Override public void onNameClick(final Media feedModel) { - navigateToProfile("@" + feedModel.getUser().getUsername()); + final User user = feedModel.getUser(); + if (user == null) return; + navigateToProfile("@" + user.getUsername()); } @Override public void onProfilePicClick(final Media feedModel) { - navigateToProfile("@" + feedModel.getUser().getUsername()); + final User user = feedModel.getUser(); + if (user == null) return; + navigateToProfile("@" + user.getUsername()); } @Override @@ -170,12 +190,9 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay } private void openPostDialog(final Media feedModel, final int position) { - final NavController navController = NavHostFragment.findNavController(CollectionPostsFragment.this); - final Bundle bundle = new Bundle(); - bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel); - bundle.putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, position); try { - navController.navigate(R.id.action_global_post_view, bundle); + final NavDirections action = CollectionPostsFragmentDirections.actionToPost(feedModel, position); + NavHostFragment.findNavController(CollectionPostsFragment.this).navigate(action); } catch (Exception e) { Log.e(TAG, "openPostDialog: ", e); } @@ -262,10 +279,10 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay @Override public void onCreateOptionsMenu(@NonNull final Menu menu, @NonNull final MenuInflater inflater) { inflater.inflate(R.menu.collection_posts_menu, menu); - deleteMenu = menu.findItem(R.id.delete); + final MenuItem deleteMenu = menu.findItem(R.id.delete); if (deleteMenu != null) deleteMenu.setVisible(savedCollection.getCollectionType().equals("MEDIA")); - editMenu = menu.findItem(R.id.edit); + final MenuItem editMenu = menu.findItem(R.id.edit); if (editMenu != null) editMenu.setVisible(savedCollection.getCollectionType().equals("MEDIA")); } @@ -408,9 +425,7 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay } private void setupCover() { - final String coverUrl = ResponseBodyUtils.getImageUrl(savedCollection.getCoverMediaList() == null - ? savedCollection.getCoverMedia() - : savedCollection.getCoverMediaList().get(0)); + final String coverUrl = ResponseBodyUtils.getImageUrl(savedCollection.getCoverMediaList().get(0)); final DraweeController controller = Fresco .newDraweeControllerBuilder() .setOldController(binding.cover.getController()) @@ -443,7 +458,6 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay .setFeedItemCallback(feedItemCallback) .setSelectionModeCallback(selectionModeCallback) .init(); - binding.swipeRefreshLayout.setRefreshing(true); } private void updateSwipeRefreshState() { @@ -453,10 +467,12 @@ public class CollectionPostsFragment extends Fragment implements SwipeRefreshLay } private void navigateToProfile(final String username) { - final NavController navController = NavHostFragment.findNavController(this); - final Bundle bundle = new Bundle(); - bundle.putString("username", username); - navController.navigate(R.id.action_global_profileFragment, bundle); + try { + final NavDirections action = CollectionPostsFragmentDirections.actionToProfile().setUsername(username); + NavHostFragment.findNavController(this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "navigateToProfile: ", e); + } } private void showPostsLayoutPreferences() { diff --git a/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.kt b/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.kt index cbd587a2..6bb75bdb 100644 --- a/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.kt +++ b/app/src/main/java/awais/instagrabber/fragments/FavoritesFragment.kt @@ -8,7 +8,7 @@ import android.view.View import android.view.ViewGroup import androidx.fragment.app.Fragment import androidx.fragment.app.viewModels -import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import awais.instagrabber.R @@ -48,17 +48,17 @@ class FavoritesFragment : Fragment() { override fun onPause() { super.onPause() - adapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT) + adapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.PREVENT } override fun onResume() { super.onResume() if (!this::adapter.isInitialized) return // refresh list every time in onViewStateRestored since it is cheaper than implementing pull down to refresh - favoritesViewModel.list.observe(viewLifecycleOwner, { - list: List? -> adapter.submitList(list, Runnable { - adapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.ALLOW) - }) + favoritesViewModel.list.observe(viewLifecycleOwner, { list: List? -> + adapter.submitList(list) { + adapter.stateRestorationPolicy = RecyclerView.Adapter.StateRestorationPolicy.ALLOW + } }) } @@ -66,32 +66,31 @@ class FavoritesFragment : Fragment() { adapter = FavoritesAdapter({ model: Favorite -> when (model.type) { FavoriteType.USER -> { - val username = model.query - // Log.d(TAG, "username: " + username); - val navController = NavHostFragment.findNavController(this) - val bundle = Bundle() - bundle.putString("username", "@$username") - navController.navigate(R.id.action_global_profileFragment, bundle) + try { + val username = model.query ?: return@FavoritesAdapter + val actionToProfile = FavoritesFragmentDirections.actionToProfile().apply { this.username = username } + findNavController().navigate(actionToProfile) + } catch (e: Exception) { + Log.e(TAG, "init: ", e) + } } FavoriteType.LOCATION -> { - val locationId = model.query ?: return@FavoritesAdapter - // Log.d(TAG, "locationId: " + locationId); - val navController = NavHostFragment.findNavController(this) - val bundle = Bundle() try { - bundle.putLong("locationId", locationId.toLong()) - navController.navigate(R.id.action_global_locationFragment, bundle) + val locationId = model.query ?: return@FavoritesAdapter + val actionToLocation = FavoritesFragmentDirections.actionToLocation(locationId.toLong()) + findNavController().navigate(actionToLocation) } catch (e: Exception) { Log.e(TAG, "init: ", e) } } FavoriteType.HASHTAG -> { - val hashtag = model.query - // Log.d(TAG, "hashtag: " + hashtag); - val navController = NavHostFragment.findNavController(this) - val bundle = Bundle() - bundle.putString("hashtag", "#$hashtag") - navController.navigate(R.id.action_global_hashTagFragment, bundle) + try { + val hashtag = model.query ?: return@FavoritesAdapter + val actionToHashtag = FavoritesFragmentDirections.actionToHashtag(hashtag) + findNavController().navigate(actionToHashtag) + } catch (e: Exception) { + Log.e(TAG, "init: ", e) + } } else -> { } diff --git a/app/src/main/java/awais/instagrabber/fragments/FollowViewerFragment.kt b/app/src/main/java/awais/instagrabber/fragments/FollowViewerFragment.kt index 41cf7f36..bc080171 100644 --- a/app/src/main/java/awais/instagrabber/fragments/FollowViewerFragment.kt +++ b/app/src/main/java/awais/instagrabber/fragments/FollowViewerFragment.kt @@ -1,51 +1,30 @@ package awais.instagrabber.fragments -import android.content.Context -import android.content.res.Resources 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.AppCompatActivity import androidx.appcompat.widget.SearchView import androidx.fragment.app.Fragment import androidx.lifecycle.ViewModelProvider -import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import androidx.swiperefreshlayout.widget.SwipeRefreshLayout - -import java.util.ArrayList - import awais.instagrabber.R import awais.instagrabber.adapters.FollowAdapter import awais.instagrabber.customviews.helpers.RecyclerLazyLoader import awais.instagrabber.databinding.FragmentFollowersViewerBinding import awais.instagrabber.models.Resource import awais.instagrabber.repositories.responses.User -import awais.instagrabber.utils.AppExecutors import awais.instagrabber.viewmodels.FollowViewModel import thoughtbot.expandableadapter.ExpandableGroup +import java.util.* class FollowViewerFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener { - private val followModels: ArrayList = ArrayList() - private val followingModels: ArrayList = ArrayList() - private val followersModels: ArrayList = ArrayList() - private val allFollowing: ArrayList = ArrayList() - private val moreAvailable = true private var isFollowersList = false private var isCompare = false private var shouldRefresh = true private var searching = false - private var profileId: Long = 0 private var username: String? = null private var namePost: String? = null private var type = 0 @@ -125,15 +104,15 @@ class FollowViewerFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener { type = if (isFollowersList) R.string.followers_type_followers else R.string.followers_type_following setSubtitle(type) val layoutManager = LinearLayoutManager(context) - lazyLoader = RecyclerLazyLoader(layoutManager, { _, totalItemsCount -> + lazyLoader = RecyclerLazyLoader(layoutManager) { _, totalItemsCount -> binding.swipeRefreshLayout.isRefreshing = true val liveData = if (searching) viewModel.search(isFollowersList) - else viewModel.fetch(isFollowersList, null) + else viewModel.fetch(isFollowersList, null) liveData.observe(viewLifecycleOwner) { binding.swipeRefreshLayout.isRefreshing = it.status != Resource.Status.SUCCESS layoutManager.scrollToPosition(totalItemsCount) } - }) + } binding.rvFollow.addOnScrollListener(lazyLoader) binding.rvFollow.layoutManager = layoutManager viewModel.getList(isFollowersList).observe(viewLifecycleOwner) { @@ -167,7 +146,7 @@ class FollowViewerFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener { } override fun onQueryTextChange(query: String): Boolean { - if (query.isNullOrEmpty()) { + if (query.isEmpty()) { if (!isCompare && searching) { viewModel.setQuery(null, isFollowersList) viewModel.getSearch().removeObservers(viewLifecycleOwner) @@ -216,7 +195,7 @@ class FollowViewerFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener { ) { val groups: ArrayList = ArrayList(1) if (isCompare && followingModels != null && followersModels != null && allFollowing != null) { - if (followingModels.size > 0) groups.add( + if (followingModels.isNotEmpty()) groups.add( ExpandableGroup( getString( R.string.followers_not_following, @@ -224,7 +203,7 @@ class FollowViewerFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener { ), followingModels ) ) - if (followersModels.size > 0) groups.add( + if (followersModels.isNotEmpty()) groups.add( ExpandableGroup( getString( R.string.followers_not_follower, @@ -232,7 +211,7 @@ class FollowViewerFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener { ), followersModels ) ) - if (allFollowing.size > 0) groups.add( + if (allFollowing.isNotEmpty()) groups.add( ExpandableGroup( getString(R.string.followers_both_following), allFollowing @@ -244,17 +223,11 @@ class FollowViewerFragment : Fragment(), SwipeRefreshLayout.OnRefreshListener { adapter = FollowAdapter({ v -> val tag = v.tag 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!! - } - - companion object { - private const val TAG = "FollowViewerFragment" + }, groups).also { + it.toggleGroup(0) + binding.rvFollow.adapter = it + } } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java index 2967c7fe..a8198406 100644 --- a/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/HashTagFragment.java @@ -26,7 +26,6 @@ import androidx.appcompat.app.ActionBar; import androidx.constraintlayout.motion.widget.MotionLayout; import androidx.constraintlayout.motion.widget.MotionScene; import androidx.fragment.app.Fragment; -import androidx.navigation.NavController; import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.RecyclerView; @@ -75,13 +74,10 @@ import static awais.instagrabber.utils.Utils.settingsHelper; public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "HashTagFragment"; - public static final String ARG_HASHTAG = "hashtag"; - private MainActivity fragmentActivity; private FragmentHashtagBinding binding; private MotionLayout root; private boolean shouldRefresh = true; -// private boolean hasStories = false; private boolean opening = false; private String hashtag; private Hashtag hashtagModel = null; @@ -135,12 +131,18 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe @Override public void onCommentsClick(final Media feedModel) { - final NavDirections commentsAction = HashTagFragmentDirections.actionGlobalCommentsViewerFragment( - feedModel.getCode(), - feedModel.getCode(), - feedModel.getUser().getPk() - ); - NavHostFragment.findNavController(HashTagFragment.this).navigate(commentsAction); + final User user = feedModel.getUser(); + if (user == null) return; + try { + final NavDirections commentsAction = HashTagFragmentDirections.actionToComments( + feedModel.getCode(), + feedModel.getCode(), + user.getPk() + ); + NavHostFragment.findNavController(HashTagFragment.this).navigate(commentsAction); + } catch (Exception e) { + Log.e(TAG, "onCommentsClick: ", e); + } } @Override @@ -152,16 +154,24 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe @Override public void onHashtagClick(final String hashtag) { - final NavDirections action = HashTagFragmentDirections.actionGlobalHashTagFragment(hashtag); - NavHostFragment.findNavController(HashTagFragment.this).navigate(action); + try { + final NavDirections action = HashTagFragmentDirections.actionToHashtag(hashtag); + NavHostFragment.findNavController(HashTagFragment.this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "onHashtagClick: ", e); + } } @Override public void onLocationClick(final Media media) { final Location location = media.getLocation(); if (location == null) return; - final NavDirections action = HashTagFragmentDirections.actionGlobalLocationFragment(location.getPk()); - NavHostFragment.findNavController(HashTagFragment.this).navigate(action); + try { + final NavDirections action = HashTagFragmentDirections.actionToLocation(location.getPk()); + NavHostFragment.findNavController(HashTagFragment.this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "onLocationClick: ", e); + } } @Override @@ -171,12 +181,16 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe @Override public void onNameClick(final Media feedModel) { - navigateToProfile("@" + feedModel.getUser().getUsername()); + final User user = feedModel.getUser(); + if (user == null) return; + navigateToProfile("@" + user.getUsername()); } @Override public void onProfilePicClick(final Media feedModel) { - navigateToProfile("@" + feedModel.getUser().getUsername()); + final User user = feedModel.getUser(); + if (user == null) return; + navigateToProfile("@" + user.getUsername()); } @Override @@ -196,7 +210,9 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe if (TextUtils.isEmpty(user.getUsername())) { // this only happens for anons opening = true; - graphQLRepository.fetchPost(feedModel.getCode(), CoroutineUtilsKt.getContinuation((media, throwable) -> { + final String code = feedModel.getCode(); + if (code == null) return; + graphQLRepository.fetchPost(code, CoroutineUtilsKt.getContinuation((media, throwable) -> { opening = false; if (throwable != null) { Log.e(TAG, "Error", throwable); @@ -208,12 +224,9 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe return; } opening = true; - final NavController navController = NavHostFragment.findNavController(HashTagFragment.this); - final Bundle bundle = new Bundle(); - bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel); - bundle.putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, position); try { - navController.navigate(R.id.action_global_post_view, bundle); + final NavDirections action = HashTagFragmentDirections.actionToPost(feedModel, position); + NavHostFragment.findNavController(HashTagFragment.this).navigate(action); } catch (Exception e) { Log.e(TAG, "openPostDialog: ", e); } @@ -305,7 +318,7 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe @Override public void onRefresh() { binding.posts.refresh(); -// fetchStories(); + // fetchStories(); } @Override @@ -357,7 +370,6 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe .setFeedItemCallback(feedItemCallback) .setSelectionModeCallback(selectionModeCallback) .init(); - binding.swipeRefreshLayout.setRefreshing(true); binding.posts.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) { @@ -381,7 +393,6 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe } setTitle(); setupPosts(); -// fetchStories(); if (isLoggedIn) { hashtagDetailsBinding.btnFollowTag.setVisibility(View.VISIBLE); hashtagDetailsBinding.btnFollowTag.setText(hashtagModel.getFollowing() == FollowingType.FOLLOWING @@ -515,22 +526,23 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe ); hashtagDetailsBinding.mainHashtagImage.setImageURI("res:/" + R.drawable.ic_hashtag); final String postCount = String.valueOf(hashtagModel.getMediaCount()); - final SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString(R.plurals.main_posts_count_inline, - hashtagModel.getMediaCount() > 2000000000L - ? 2000000000 - : Long.valueOf(hashtagModel.getMediaCount()).intValue(), - postCount)); + final SpannableStringBuilder span = new SpannableStringBuilder(getResources().getQuantityString( + R.plurals.main_posts_count_inline, + hashtagModel.getMediaCount() > 2000000000L ? 2000000000 + : Long.valueOf(hashtagModel.getMediaCount()).intValue(), + postCount) + ); span.setSpan(new RelativeSizeSpan(1.2f), 0, postCount.length(), 0); span.setSpan(new StyleSpan(Typeface.BOLD), 0, postCount.length(), 0); hashtagDetailsBinding.mainTagPostCount.setText(span); hashtagDetailsBinding.mainTagPostCount.setVisibility(View.VISIBLE); -// hashtagDetailsBinding.mainHashtagImage.setOnClickListener(v -> { -// if (!hasStories) return; -// // show stories -// final NavDirections action = HashTagFragmentDirections -// .actionHashtagFragmentToStoryViewerFragment(StoryViewerOptions.forHashtag(hashtagModel.getName())); -// NavHostFragment.findNavController(this).navigate(action); -// }); + // hashtagDetailsBinding.mainHashtagImage.setOnClickListener(v -> { + // if (!hasStories) return; + // // show stories + // final NavDirections action = HashTagFragmentDirections + // .actionHashtagFragmentToStoryViewerFragment(StoryViewerOptions.forHashtag(hashtagModel.getName())); + // NavHostFragment.findNavController(this).navigate(action); + // }); } private void showSnackbar(final String message) { @@ -541,35 +553,10 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe .show(); } -// private void fetchStories() { -// if (!isLoggedIn) return; -// storiesFetching = true; -// storiesRepository.getStories( -// StoryViewerOptions.forHashtag(hashtagModel.getName()), -// CoroutineUtilsKt.getContinuation((storyModels, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { -// if (throwable != null) { -// Log.e(TAG, "Error", throwable); -// storiesFetching = false; -// return; -// } -// if (storyModels != null && !storyModels.isEmpty()) { -// hashtagDetailsBinding.mainHashtagImage.setStoriesBorder(1); -// hasStories = true; -// } else { -// hasStories = false; -// } -// storiesFetching = false; -// }), Dispatchers.getIO()) -// ); -// } - private void setTitle() { final ActionBar actionBar = fragmentActivity.getSupportActionBar(); if (actionBar != null) { - // Log.d(TAG, "setting title: " + hashtag); actionBar.setTitle('#' + hashtag); - // final Handler handler = new Handler(); - // handler.postDelayed(() -> , 1000); } } @@ -580,10 +567,12 @@ public class HashTagFragment extends Fragment implements SwipeRefreshLayout.OnRe } private void navigateToProfile(final String username) { - final NavController navController = NavHostFragment.findNavController(this); - final Bundle bundle = new Bundle(); - bundle.putString("username", username); - navController.navigate(R.id.action_global_profileFragment, bundle); + try { + final NavDirections action = HashTagFragmentDirections.actionToProfile().setUsername(username); + NavHostFragment.findNavController(this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "navigateToProfile: ", e); + } } private void showPostsLayoutPreferences() { diff --git a/app/src/main/java/awais/instagrabber/fragments/LikesViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/LikesViewerFragment.java index 5b3ca53d..bbf97bd6 100644 --- a/app/src/main/java/awais/instagrabber/fragments/LikesViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/LikesViewerFragment.java @@ -10,6 +10,7 @@ import android.widget.Toast; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.DividerItemDecoration; import androidx.recyclerview.widget.LinearLayoutManager; @@ -19,7 +20,6 @@ import com.google.android.material.bottomsheet.BottomSheetDialogFragment; import java.util.List; -import awais.instagrabber.R; import awais.instagrabber.adapters.LikesAdapter; import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; import awais.instagrabber.databinding.FragmentLikesBinding; @@ -55,9 +55,12 @@ public final class LikesViewerFragment extends BottomSheetDialogFragment impleme final Object tag = v.getTag(); if (tag instanceof User) { User model = (User) tag; - final Bundle bundle = new Bundle(); - bundle.putString("username", "@" + model.getUsername()); - NavHostFragment.findNavController(LikesViewerFragment.this).navigate(R.id.action_global_profileFragment, bundle); + try { + final NavDirections action = LikesViewerFragmentDirections.actionToProfile().setUsername(model.getUsername()); + NavHostFragment.findNavController(LikesViewerFragment.this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "onSuccess: ", e); + } } }); binding.rvLikes.setAdapter(likesAdapter); @@ -84,9 +87,12 @@ public final class LikesViewerFragment extends BottomSheetDialogFragment impleme final Object tag = v.getTag(); if (tag instanceof User) { User model = (User) tag; - final Bundle bundle = new Bundle(); - bundle.putString("username", "@" + model.getUsername()); - NavHostFragment.findNavController(LikesViewerFragment.this).navigate(R.id.action_global_profileFragment, bundle); + try { + final NavDirections action = LikesViewerFragmentDirections.actionToProfile().setUsername(model.getUsername()); + NavHostFragment.findNavController(LikesViewerFragment.this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "onSuccess: ", e); + } } }); binding.rvLikes.setAdapter(likesAdapter); diff --git a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java index 93c3e13f..b7e9960f 100644 --- a/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/LocationFragment.java @@ -24,7 +24,6 @@ import androidx.appcompat.app.ActionBar; import androidx.constraintlayout.motion.widget.MotionLayout; import androidx.constraintlayout.motion.widget.MotionScene; import androidx.fragment.app.Fragment; -import androidx.navigation.NavController; import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.RecyclerView; @@ -63,7 +62,6 @@ import awais.instagrabber.utils.Utils; import awais.instagrabber.webservices.GraphQLRepository; import awais.instagrabber.webservices.LocationService; import awais.instagrabber.webservices.ServiceCallback; -//import awais.instagrabber.webservices.StoriesRepository; import kotlinx.coroutines.Dispatchers; import static awais.instagrabber.utils.Utils.settingsHelper; @@ -75,7 +73,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR private FragmentLocationBinding binding; private MotionLayout root; private boolean shouldRefresh = true; -// private boolean hasStories = false; + // private boolean hasStories = false; private boolean opening = false; private long locationId; private Location locationModel; @@ -129,12 +127,18 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR @Override public void onCommentsClick(final Media feedModel) { - final NavDirections commentsAction = LocationFragmentDirections.actionGlobalCommentsViewerFragment( - feedModel.getCode(), - feedModel.getPk(), - feedModel.getUser().getPk() - ); - NavHostFragment.findNavController(LocationFragment.this).navigate(commentsAction); + final User user = feedModel.getUser(); + if (user == null) return; + try { + final NavDirections commentsAction = LocationFragmentDirections.actionToComments( + feedModel.getCode(), + feedModel.getPk(), + user.getPk() + ); + NavHostFragment.findNavController(LocationFragment.this).navigate(commentsAction); + } catch (Exception e) { + Log.e(TAG, "onCommentsClick: ", e); + } } @Override @@ -146,13 +150,19 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR @Override public void onHashtagClick(final String hashtag) { - final NavDirections action = LocationFragmentDirections.actionGlobalHashTagFragment(hashtag); - NavHostFragment.findNavController(LocationFragment.this).navigate(action); + try { + final NavDirections action = LocationFragmentDirections.actionToHashtag(hashtag); + NavHostFragment.findNavController(LocationFragment.this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "onHashtagClick: ", e); + } } @Override public void onLocationClick(final Media feedModel) { - final NavDirections action = LocationFragmentDirections.actionGlobalLocationFragment(feedModel.getLocation().getPk()); + final Location location = feedModel.getLocation(); + if (location == null) return; + final NavDirections action = LocationFragmentDirections.actionToLocation(location.getPk()); NavHostFragment.findNavController(LocationFragment.this).navigate(action); } @@ -202,12 +212,9 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR return; } opening = true; - final NavController navController = NavHostFragment.findNavController(LocationFragment.this); - final Bundle bundle = new Bundle(); - bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel); - bundle.putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, position); try { - navController.navigate(R.id.action_global_post_view, bundle); + final NavDirections action = LocationFragmentDirections.actionToPost(feedModel, position); + NavHostFragment.findNavController(LocationFragment.this).navigate(action); } catch (Exception e) { Log.e(TAG, "openPostDialog: ", e); } @@ -301,7 +308,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR @Override public void onRefresh() { binding.posts.refresh(); -// fetchStories(); + // fetchStories(); } @Override @@ -343,7 +350,6 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR .setFeedItemCallback(feedItemCallback) .setSelectionModeCallback(selectionModeCallback) .init(); - binding.swipeRefreshLayout.setRefreshing(true); binding.posts.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) { @@ -382,7 +388,7 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR } setTitle(); setupPosts(); -// fetchStories(); + // fetchStories(); final long locationId = locationModel.getPk(); // binding.swipeRefreshLayout.setRefreshing(true); locationDetailsBinding.mainLocationImage.setImageURI("res:/" + R.drawable.ic_location); @@ -527,14 +533,14 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR ); }), Dispatchers.getIO()) )); -// locationDetailsBinding.mainLocationImage.setOnClickListener(v -> { -// if (hasStories) { -// // show stories -// final NavDirections action = LocationFragmentDirections -// .actionLocationFragmentToStoryViewerFragment(StoryViewerOptions.forLocation(locationId, locationModel.getName())); -// NavHostFragment.findNavController(this).navigate(action); -// } -// }); + // locationDetailsBinding.mainLocationImage.setOnClickListener(v -> { + // if (hasStories) { + // // show stories + // final NavDirections action = LocationFragmentDirections + // .actionLocationFragmentToStoryViewerFragment(StoryViewerOptions.forLocation(locationId, locationModel.getName())); + // NavHostFragment.findNavController(this).navigate(action); + // } + // }); } private void showSnackbar(final String message) { @@ -545,26 +551,26 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR .show(); } -// private void fetchStories() { -// if (isLoggedIn) { -// storiesFetching = true; -// storiesRepository.getStories( -// StoryViewerOptions.forLocation(locationId, locationModel.getName()), -// CoroutineUtilsKt.getContinuation((storyModels, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { -// if (throwable != null) { -// Log.e(TAG, "Error", throwable); -// storiesFetching = false; -// return; -// } -// if (storyModels != null && !storyModels.isEmpty()) { -// locationDetailsBinding.mainLocationImage.setStoriesBorder(1); -// hasStories = true; -// } -// storiesFetching = false; -// }), Dispatchers.getIO()) -// ); -// } -// } + // private void fetchStories() { + // if (isLoggedIn) { + // storiesFetching = true; + // storiesRepository.getStories( + // StoryViewerOptions.forLocation(locationId, locationModel.getName()), + // CoroutineUtilsKt.getContinuation((storyModels, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { + // if (throwable != null) { + // Log.e(TAG, "Error", throwable); + // storiesFetching = false; + // return; + // } + // if (storyModels != null && !storyModels.isEmpty()) { + // locationDetailsBinding.mainLocationImage.setStoriesBorder(1); + // hasStories = true; + // } + // storiesFetching = false; + // }), Dispatchers.getIO()) + // ); + // } + // } private void setTitle() { final ActionBar actionBar = fragmentActivity.getSupportActionBar(); @@ -580,10 +586,12 @@ public class LocationFragment extends Fragment implements SwipeRefreshLayout.OnR } private void navigateToProfile(final String username) { - final NavController navController = NavHostFragment.findNavController(this); - final Bundle bundle = new Bundle(); - bundle.putString("username", username); - navController.navigate(R.id.action_global_profileFragment, bundle); + try { + final NavDirections action = LocationFragmentDirections.actionToProfile().setUsername(username); + NavHostFragment.findNavController(this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "navigateToProfile: ", e); + } } private void showPostsLayoutPreferences() { diff --git a/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java index 4ce968b7..a997bc65 100644 --- a/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/NotificationsViewerFragment.java @@ -20,7 +20,6 @@ import androidx.appcompat.app.AppCompatActivity; import androidx.core.app.NotificationManagerCompat; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; -import androidx.navigation.NavController; import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.LinearLayoutManager; @@ -50,8 +49,6 @@ import awais.instagrabber.webservices.NewsService; import awais.instagrabber.webservices.ServiceCallback; import kotlinx.coroutines.Dispatchers; -import static awais.instagrabber.utils.Utils.settingsHelper; - public final class NotificationsViewerFragment extends Fragment implements SwipeRefreshLayout.OnRefreshListener { private static final String TAG = "NotificationsViewer"; @@ -98,10 +95,14 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe if (model.getType() == NotificationType.RESPONDED_STORY) { final StoryViewerOptions options = StoryViewerOptions.forStory( mediaId, - model.getArgs().getUsername()); - final Bundle bundle = new Bundle(); - bundle.putSerializable("options", options); - NavHostFragment.findNavController(NotificationsViewerFragment.this).navigate(R.id.action_notifications_to_story, bundle); + model.getArgs().getUsername() + ); + try { + final NavDirections action = NotificationsViewerFragmentDirections.actionToStory(options); + NavHostFragment.findNavController(NotificationsViewerFragment.this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "onPreviewClick: ", e); + } } else { final AlertDialog alertDialog = new AlertDialog.Builder(context) .setCancelable(false) @@ -116,14 +117,13 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); return; } - final NavController navController = NavHostFragment.findNavController(NotificationsViewerFragment.this); - final Bundle bundle = new Bundle(); - bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, media); try { - navController.navigate(R.id.action_global_post_view, bundle); - alertDialog.dismiss(); + final NavDirections action = NotificationsViewerFragmentDirections.actionToPost(media, 0); + NavHostFragment.findNavController(NotificationsViewerFragment.this).navigate(action); } catch (Exception e) { Log.e(TAG, "onSuccess: ", e); + } finally { + alertDialog.dismiss(); } }), Dispatchers.getIO()) ); @@ -257,7 +257,7 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe type = fragmentArgs.getType(); targetId = fragmentArgs.getTargetId(); final Context context = getContext(); - CookieUtils.setupCookies(settingsHelper.getString(Constants.COOKIE)); + CookieUtils.setupCookies(Utils.settingsHelper.getString(Constants.COOKIE)); binding.swipeRefreshLayout.setOnRefreshListener(this); notificationViewModel = new ViewModelProvider(this).get(NotificationViewModel.class); final NotificationsAdapter adapter = new NotificationsAdapter(clickListener); @@ -288,7 +288,11 @@ public final class NotificationsViewerFragment extends Fragment implements Swipe } private void openProfile(final String username) { - final NavDirections action = NotificationsViewerFragmentDirections.actionGlobalProfileFragment("@" + username); - NavHostFragment.findNavController(this).navigate(action); + try { + final NavDirections action = NotificationsViewerFragmentDirections.actionToProfile().setUsername(username); + NavHostFragment.findNavController(this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "openProfile: ", e); + } } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java b/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java index 8e8f62e5..7964e683 100644 --- a/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/PostViewV2Fragment.java @@ -40,6 +40,7 @@ import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; import androidx.navigation.NavBackStackEntry; import androidx.navigation.NavController; +import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; import androidx.recyclerview.widget.RecyclerView; import androidx.transition.TransitionManager; @@ -70,7 +71,6 @@ import java.util.List; import java.util.Set; import awais.instagrabber.R; -import awais.instagrabber.UserSearchNavGraphDirections; import awais.instagrabber.activities.MainActivity; import awais.instagrabber.adapters.SliderCallbackAdapter; import awais.instagrabber.adapters.SliderItemsAdapter; @@ -103,9 +103,7 @@ import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; import awais.instagrabber.viewmodels.PostViewV2ViewModel; -import static awais.instagrabber.fragments.HashTagFragment.ARG_HASHTAG; import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_SHOWN_COUNT_TOOLTIP; -import static awais.instagrabber.utils.Utils.settingsHelper; public class PostViewV2Fragment extends Fragment implements EditTextDialogFragment.EditTextDialogFragmentCallback { private static final String TAG = "PostViewV2Fragment"; @@ -217,7 +215,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme public void onPause() { super.onPause(); // wasPaused = true; - if (settingsHelper.getBoolean(PreferenceKeys.PLAY_IN_BACKGROUND)) return; + if (Utils.settingsHelper.getBoolean(PreferenceKeys.PLAY_IN_BACKGROUND)) return; final Media media = viewModel.getMedia(); if (media.getType() == null) return; switch (media.getType()) { @@ -333,7 +331,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme viewModel.getLikeCount().observe(getViewLifecycleOwner(), count -> { bottom.likesCount.setNumber(getSafeCount(count)); binding.getRoot().postDelayed(() -> bottom.likesCount.setAnimateChanges(true), 1000); - if (count > 1000 && !settingsHelper.getBoolean(PREF_SHOWN_COUNT_TOOLTIP)) { + if (count > 1000 && !Utils.settingsHelper.getBoolean(PREF_SHOWN_COUNT_TOOLTIP)) { binding.getRoot().postDelayed(this::showCountTooltip, 1000); } }); @@ -392,7 +390,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme .setDismissWhenOverlayClicked(false) .build(); balloon.showAlignBottom(bottom.likesCount); - settingsHelper.putBoolean(PREF_SHOWN_COUNT_TOOLTIP, true); + Utils.settingsHelper.putBoolean(PREF_SHOWN_COUNT_TOOLTIP, true); balloon.setOnBalloonOutsideTouchListener((view, motionEvent) -> { if (rect.contains((int) motionEvent.getRawX(), (int) motionEvent.getRawY())) { bottom.likesCount.setShowAbbreviation(false); @@ -431,12 +429,9 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme if (user == null) return; final NavController navController = getNavController(); if (navController == null) return; - final Bundle bundle = new Bundle(); - bundle.putString("shortCode", media.getCode()); - bundle.putString("postId", media.getPk()); - bundle.putLong("postUserId", user.getPk()); try { - navController.navigate(R.id.action_global_commentsViewerFragment, bundle); + final NavDirections action = PostViewV2FragmentDirections.actionToComments(media.getCode(), media.getPk(), user.getPk()); + navController.navigate(action); } catch (Exception e) { Log.e(TAG, "setupComment: ", e); } @@ -445,9 +440,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme } private void setupDownload() { - bottom.download.setOnClickListener(v -> { - DownloadUtils.showDownloadDialog(context, viewModel.getMedia(), sliderPosition, bottom.download); - }); + bottom.download.setOnClickListener(v -> DownloadUtils.showDownloadDialog(context, viewModel.getMedia(), sliderPosition, bottom.download)); TooltipCompat.setTooltipText(bottom.download, getString(R.string.action_download)); } @@ -470,10 +463,12 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme bottom.like.setOnLongClickListener(v -> { final NavController navController = getNavController(); if (navController != null && viewModel.isLoggedIn()) { - final Bundle bundle = new Bundle(); - bundle.putString("postId", viewModel.getMedia().getPk()); - bundle.putBoolean("isComment", false); - navController.navigate(R.id.action_global_likesViewerFragment, bundle); + try { + final NavDirections action = PostViewV2FragmentDirections.actionToLikes(viewModel.getMedia().getPk(), false); + navController.navigate(action); + } catch (Exception e) { + Log.e(TAG, "setupLike: ", e); + } return true; } return true; @@ -543,11 +538,14 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme handleSaveUnsaveResourceLiveData(viewModel.toggleSave()); }); bottom.save.setOnLongClickListener(v -> { - final NavController navController = NavHostFragment.findNavController(this); - final Bundle bundle = new Bundle(); - bundle.putBoolean("isSaving", true); - navController.navigate(R.id.action_global_savedCollectionsFragment, bundle); - return true; + try { + final NavDirections action = PostViewV2FragmentDirections.actionToSavedCollections().setIsSaving(true); + NavHostFragment.findNavController(this).navigate(action); + return true; + } catch (Exception e) { + Log.e(TAG, "setupSave: ", e); + } + return false; }); } @@ -668,11 +666,13 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme } final String postCaption = caption.getText(); bottom.caption.addOnHashtagListener(autoLinkItem -> { - final NavController navController = NavHostFragment.findNavController(this); - final Bundle bundle = new Bundle(); - final String originalText = autoLinkItem.getOriginalText().trim(); - bundle.putString(ARG_HASHTAG, originalText); - navController.navigate(R.id.action_global_hashTagFragment, bundle); + try { + final String originalText = autoLinkItem.getOriginalText().trim(); + final NavDirections action = PostViewV2FragmentDirections.actionToHashtag(originalText); + NavHostFragment.findNavController(this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "setupCaption: ", e); + } }); bottom.caption.addOnMentionClickListener(autoLinkItem -> { final String originalText = autoLinkItem.getOriginalText().trim(); @@ -701,7 +701,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme case ERROR: bottom.translate.setEnabled(true); String message = resource.message; - if (TextUtils.isEmpty(resource.message)) { + if (TextUtils.isEmpty(message)) { message = getString(R.string.downloader_unknown_error); } final Snackbar snackbar = Snackbar.make(binding.getRoot(), message, Snackbar.LENGTH_INDEFINITE); @@ -725,11 +725,14 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme binding.location.setText(locationName); binding.location.setVisibility(View.VISIBLE); binding.location.setOnClickListener(v -> { - final NavController navController = getNavController(); - if (navController == null) return; - final Bundle bundle = new Bundle(); - bundle.putLong("locationId", location.getPk()); - navController.navigate(R.id.action_global_locationFragment, bundle); + try { + final NavController navController = getNavController(); + if (navController == null) return; + final NavDirections action = PostViewV2FragmentDirections.actionToLocation(location.getPk()); + navController.navigate(action); + } catch (Exception e) { + Log.e(TAG, "setupLocation: ", e); + } }); } @@ -755,15 +758,14 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme popupMenu.setOnMenuItemClickListener(item -> { final int itemId = item.getItemId(); if (itemId == R.id.share_dm) { - if (profileModel.isPrivate()) - Toast.makeText(context, R.string.share_private_post, Toast.LENGTH_SHORT).show(); - final UserSearchNavGraphDirections.ActionGlobalUserSearch actionGlobalUserSearch = UserSearchFragmentDirections - .actionGlobalUserSearch() + if (profileModel.isPrivate()) Toast.makeText(context, R.string.share_private_post, Toast.LENGTH_SHORT).show(); + final PostViewV2FragmentDirections.ActionToUserSearch actionGlobalUserSearch = PostViewV2FragmentDirections + .actionToUserSearch() .setTitle(getString(R.string.share)) .setActionLabel(getString(R.string.send)) .setShowGroups(true) .setMultiple(true) - .setSearchMode(UserSearchFragment.SearchMode.RAVEN); + .setSearchMode(UserSearchMode.RAVEN); final NavController navController = NavHostFragment.findNavController(PostViewV2Fragment.this); try { navController.navigate(actionGlobalUserSearch); @@ -1072,7 +1074,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme // gestureDetector.onTouchEvent(event); // return true; // }); - final float vol = settingsHelper.getBoolean(PreferenceKeys.MUTED_VIDEOS) ? 0f : 1f; + final float vol = Utils.settingsHelper.getBoolean(PreferenceKeys.MUTED_VIDEOS) ? 0f : 1f; final VideoPlayerViewHelper.VideoPlayerCallback videoPlayerCallback = new VideoPlayerCallbackAdapter() { @Override public void onThumbnailLoaded() { @@ -1409,9 +1411,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme final CollapsingToolbarLayout appbarLayout = activity.getCollapsingToolbarView(); appbarLayout.setVisibility(View.GONE); final Toolbar toolbar = activity.getToolbar(); - if (toolbar != null) { - toolbar.setVisibility(View.GONE); - } + toolbar.setVisibility(View.GONE); binding.getRoot().setPadding(binding.getRoot().getPaddingLeft(), binding.getRoot().getPaddingTop(), binding.getRoot().getPaddingRight(), @@ -1434,9 +1434,7 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme final CollapsingToolbarLayout appbarLayout = activity.getCollapsingToolbarView(); appbarLayout.setVisibility(View.VISIBLE); final Toolbar toolbar = activity.getToolbar(); - if (toolbar != null) { - toolbar.setVisibility(View.VISIBLE); - } + toolbar.setVisibility(View.VISIBLE); final Context context = getContext(); if (context == null) return; binding.getRoot().setPadding(binding.getRoot().getPaddingLeft(), @@ -1451,9 +1449,8 @@ public class PostViewV2Fragment extends Fragment implements EditTextDialogFragme private void navigateToProfile(final String username) { final NavController navController = getNavController(); if (navController == null) return; - final Bundle bundle = new Bundle(); - bundle.putString("username", username); - navController.navigate(R.id.action_global_profileFragment, bundle); + final NavDirections actionToProfile = PostViewV2FragmentDirections.actionToProfile().setUsername(username); + navController.navigate(actionToProfile); } @Nullable diff --git a/app/src/main/java/awais/instagrabber/fragments/SavedCollectionsFragment.java b/app/src/main/java/awais/instagrabber/fragments/SavedCollectionsFragment.java index a995d989..af756f56 100644 --- a/app/src/main/java/awais/instagrabber/fragments/SavedCollectionsFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/SavedCollectionsFragment.java @@ -21,6 +21,7 @@ import androidx.lifecycle.SavedStateHandle; import androidx.lifecycle.ViewModelProvider; import androidx.navigation.NavBackStackEntry; import androidx.navigation.NavController; +import androidx.navigation.NavDirections; import androidx.navigation.fragment.FragmentNavigator; import androidx.navigation.fragment.NavHostFragment; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; @@ -153,8 +154,8 @@ public class SavedCollectionsFragment extends Fragment implements SwipeRefreshLa try { final FragmentNavigator.Extras.Builder builder = new FragmentNavigator.Extras.Builder() .addSharedElement(cover, "collection-" + topicCluster.getCollectionId()); - final SavedCollectionsFragmentDirections.ActionSavedCollectionsFragmentToCollectionPostsFragment action = SavedCollectionsFragmentDirections - .actionSavedCollectionsFragmentToCollectionPostsFragment(topicCluster, titleColor, backgroundColor); + final NavDirections action = SavedCollectionsFragmentDirections + .actionToCollectionPosts(topicCluster, titleColor, backgroundColor); navController.navigate(action, builder.build()); } catch (Exception e) { Log.e(TAG, "setupTopics: ", e); diff --git a/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java index 6325794e..a8477634 100644 --- a/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/SavedViewerFragment.java @@ -19,7 +19,6 @@ import androidx.annotation.Nullable; import androidx.appcompat.app.ActionBar; import androidx.appcompat.app.AppCompatActivity; import androidx.fragment.app.Fragment; -import androidx.navigation.NavController; import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; @@ -37,7 +36,9 @@ import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; import awais.instagrabber.fragments.main.ProfileFragmentDirections; import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.models.enums.PostItemType; +import awais.instagrabber.repositories.responses.Location; import awais.instagrabber.repositories.responses.Media; +import awais.instagrabber.repositories.responses.User; import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; @@ -100,12 +101,18 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL @Override public void onCommentsClick(final Media feedModel) { - final NavDirections commentsAction = ProfileFragmentDirections.actionGlobalCommentsViewerFragment( - feedModel.getCode(), - feedModel.getPk(), - feedModel.getUser().getPk() - ); - NavHostFragment.findNavController(SavedViewerFragment.this).navigate(commentsAction); + final User user = feedModel.getUser(); + if (user == null) return; + try { + final NavDirections commentsAction = ProfileFragmentDirections.actionToComments( + feedModel.getCode(), + feedModel.getPk(), + user.getPk() + ); + NavHostFragment.findNavController(SavedViewerFragment.this).navigate(commentsAction); + } catch (Exception e) { + Log.e(TAG, "onCommentsClick: ", e); + } } @Override @@ -117,14 +124,24 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL @Override public void onHashtagClick(final String hashtag) { - final NavDirections action = ProfileFragmentDirections.actionGlobalHashTagFragment(hashtag); - NavHostFragment.findNavController(SavedViewerFragment.this).navigate(action); + try { + final NavDirections action = ProfileFragmentDirections.actionToHashtag(hashtag); + NavHostFragment.findNavController(SavedViewerFragment.this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "onHashtagClick: ", e); + } } @Override public void onLocationClick(final Media feedModel) { - final NavDirections action = ProfileFragmentDirections.actionGlobalLocationFragment(feedModel.getLocation().getPk()); - NavHostFragment.findNavController(SavedViewerFragment.this).navigate(action); + final Location location = feedModel.getLocation(); + if (location == null) return; + try { + final NavDirections action = ProfileFragmentDirections.actionToLocation(location.getPk()); + NavHostFragment.findNavController(SavedViewerFragment.this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "onLocationClick: ", e); + } } @Override @@ -139,7 +156,9 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL @Override public void onProfilePicClick(final Media feedModel) { - navigateToProfile("@" + feedModel.getUser().getUsername()); + final User user = feedModel.getUser(); + if (user == null) return; + navigateToProfile("@" + user.getUsername()); } @Override @@ -153,12 +172,9 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL } private void openPostDialog(final Media feedModel, final int position) { - final NavController navController = NavHostFragment.findNavController(SavedViewerFragment.this); - final Bundle bundle = new Bundle(); - bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel); - bundle.putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, position); try { - navController.navigate(R.id.action_global_post_view, bundle); + final NavDirections action = SavedViewerFragmentDirections.actionToPost(feedModel, position); + NavHostFragment.findNavController(SavedViewerFragment.this).navigate(action); } catch (Exception e) { Log.e(TAG, "openPostDialog: ", e); } @@ -316,10 +332,12 @@ public final class SavedViewerFragment extends Fragment implements SwipeRefreshL } private void navigateToProfile(final String username) { - final NavController navController = NavHostFragment.findNavController(this); - final Bundle bundle = new Bundle(); - bundle.putString("username", username); - navController.navigate(R.id.action_global_profileFragment, bundle); + try { + final NavDirections action = SavedViewerFragmentDirections.actionToProfile().setUsername(username); + NavHostFragment.findNavController(this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "navigateToProfile: ", e); + } } private void showPostsLayoutPreferences() { diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java index 6f0516a2..92dd23e4 100644 --- a/app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/StoryListViewerFragment.java @@ -37,7 +37,6 @@ import awais.instagrabber.adapters.HighlightStoriesListAdapter; import awais.instagrabber.adapters.HighlightStoriesListAdapter.OnHighlightStoryClickListener; import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; import awais.instagrabber.databinding.FragmentStoryListViewerBinding; -import awais.instagrabber.fragments.settings.MorePreferencesFragmentDirections; import awais.instagrabber.repositories.requests.StoryViewerOptions; import awais.instagrabber.repositories.responses.stories.ArchiveResponse; import awais.instagrabber.repositories.responses.stories.Story; @@ -74,9 +73,12 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr if (feedStoryModels == null) return; final int position = Iterables.indexOf(feedStoryModels, feedStoryModel -> feedStoryModel != null && Objects.equals(feedStoryModel.getId(), model.getId())); - final NavDirections action = StoryListViewerFragmentDirections - .actionStoryListFragmentToStoryViewerFragment(StoryViewerOptions.forFeedStoryPosition(position)); - NavHostFragment.findNavController(StoryListViewerFragment.this).navigate(action); + try { + final NavDirections action = StoryListViewerFragmentDirections.actionToStory(StoryViewerOptions.forFeedStoryPosition(position)); + NavHostFragment.findNavController(StoryListViewerFragment.this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "onFeedStoryClick: ", e); + } } @Override @@ -89,9 +91,12 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr @Override public void onHighlightClick(final Story model, final int position) { if (model == null) return; - final NavDirections action = StoryListViewerFragmentDirections - .actionStoryListFragmentToStoryViewerFragment(StoryViewerOptions.forStoryArchive(position)); - NavHostFragment.findNavController(StoryListViewerFragment.this).navigate(action); + try { + final NavDirections action = StoryListViewerFragmentDirections.actionToStory(StoryViewerOptions.forStoryArchive(position)); + NavHostFragment.findNavController(StoryListViewerFragment.this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "onHighlightClick: ", e); + } } @Override @@ -271,8 +276,11 @@ public final class StoryListViewerFragment extends Fragment implements SwipeRefr } private void openProfile(final String username) { - final NavDirections action = MorePreferencesFragmentDirections - .actionGlobalProfileFragment("@" + username); - NavHostFragment.findNavController(this).navigate(action); + try { + final NavDirections action = StoryListViewerFragmentDirections.actionToProfile().setUsername(username); + NavHostFragment.findNavController(this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "openProfile: ", e); + } } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt b/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt index 34d789a4..d9e20b4a 100644 --- a/app/src/main/java/awais/instagrabber/fragments/StoryViewerFragment.kt +++ b/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.core.view.GestureDetectorCompat import androidx.fragment.app.Fragment -import androidx.lifecycle.LiveData import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer import androidx.lifecycle.ViewModel @@ -639,20 +638,19 @@ class StoryViewerFragment : Fragment() { actionBar.title = null actionBar.subtitle = null } - when (data.second) { + val action = when (data.second) { FavoriteType.USER -> { - bundle.putString("username", data.first) - navController.navigate(R.id.action_global_profileFragment, bundle) + StoryViewerFragmentDirections.actionToProfile().apply { this.username = data.first!! } } FavoriteType.HASHTAG -> { - bundle.putString("hashtag", data.first) - navController.navigate(R.id.action_global_hashTagFragment, bundle) + StoryViewerFragmentDirections.actionToHashtag(data.first!!) } FavoriteType.LOCATION -> { - bundle.putLong("locationId", data.first!!.toLong()) - navController.navigate(R.id.action_global_locationFragment, bundle) + StoryViewerFragmentDirections.actionToLocation(data.first!!.toLong()) } + else -> null } + navController.navigate(action!!) } private fun releasePlayer() { @@ -804,12 +802,12 @@ class StoryViewerFragment : Fragment() { } val actionBar = fragmentActivity.supportActionBar if (actionBar != null) actionBar.subtitle = null - val actionGlobalUserSearch = UserSearchFragmentDirections.actionGlobalUserSearch().apply { + val actionGlobalUserSearch = StoryViewerFragmentDirections.actionToUserSearch().apply { title = getString(R.string.share) - setActionLabel(getString(R.string.send)) + actionLabel = getString(R.string.send) showGroups = true multiple = true - setSearchMode(UserSearchFragment.SearchMode.RAVEN) + searchMode = UserSearchMode.RAVEN } try { val navController = NavHostFragment.findNavController(this@StoryViewerFragment) @@ -876,7 +874,7 @@ class StoryViewerFragment : Fragment() { val bundle = Bundle() bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, it.data) try { - navController.navigate(R.id.action_global_post_view, bundle) + navController.navigate(StoryViewerFragmentDirections.actionToPost(it.data, 0)) } catch (e: Exception) { Log.e(TAG, "openPostDialog: ", e) } diff --git a/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java b/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java index 39c4e6a4..693ffee9 100644 --- a/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/TopicPostsFragment.java @@ -25,7 +25,6 @@ import androidx.annotation.Nullable; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.core.graphics.ColorUtils; import androidx.fragment.app.Fragment; -import androidx.navigation.NavController; import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; @@ -48,9 +47,10 @@ import awais.instagrabber.asyncs.DiscoverPostFetchService; import awais.instagrabber.customviews.PrimaryActionModeCallback; import awais.instagrabber.databinding.FragmentTopicPostsBinding; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; -import awais.instagrabber.fragments.main.DiscoverFragmentDirections; import awais.instagrabber.models.PostsLayoutPreferences; +import awais.instagrabber.repositories.responses.Location; import awais.instagrabber.repositories.responses.Media; +import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.discover.TopicCluster; import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.Constants; @@ -111,10 +111,12 @@ public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.O @Override public void onCommentsClick(final Media feedModel) { - final NavDirections commentsAction = DiscoverFragmentDirections.actionGlobalCommentsViewerFragment( + final User user = feedModel.getUser(); + if (user == null) return; + final NavDirections commentsAction = TopicPostsFragmentDirections.actionToComments( feedModel.getCode(), feedModel.getPk(), - feedModel.getUser().getPk() + user.getPk() ); NavHostFragment.findNavController(TopicPostsFragment.this).navigate(commentsAction); } @@ -128,13 +130,15 @@ public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.O @Override public void onHashtagClick(final String hashtag) { - final NavDirections action = DiscoverFragmentDirections.actionGlobalHashTagFragment(hashtag); + final NavDirections action = TopicPostsFragmentDirections.actionToHashtag(hashtag); NavHostFragment.findNavController(TopicPostsFragment.this).navigate(action); } @Override public void onLocationClick(final Media feedModel) { - final NavDirections action = DiscoverFragmentDirections.actionGlobalLocationFragment(feedModel.getLocation().getPk()); + final Location location = feedModel.getLocation(); + if (location == null) return; + final NavDirections action = TopicPostsFragmentDirections.actionToLocation(location.getPk()); NavHostFragment.findNavController(TopicPostsFragment.this).navigate(action); } @@ -150,7 +154,9 @@ public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.O @Override public void onProfilePicClick(final Media feedModel) { - navigateToProfile("@" + feedModel.getUser().getUsername()); + final User user = feedModel.getUser(); + if (user == null) return; + navigateToProfile("@" + user.getUsername()); } @Override @@ -164,12 +170,9 @@ public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.O } private void openPostDialog(final Media feedModel, final int position) { - final NavController navController = NavHostFragment.findNavController(TopicPostsFragment.this); - final Bundle bundle = new Bundle(); - bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel); - bundle.putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, position); try { - navController.navigate(R.id.action_global_post_view, bundle); + final NavDirections action = TopicPostsFragmentDirections.actionToPost(feedModel, position); + NavHostFragment.findNavController(TopicPostsFragment.this).navigate(action); } catch (Exception e) { Log.e(TAG, "openPostDialog: ", e); } @@ -215,11 +218,14 @@ public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.O public void onCreate(@Nullable final Bundle savedInstanceState) { super.onCreate(savedInstanceState); fragmentActivity = (MainActivity) requireActivity(); - final TransitionSet transitionSet = new TransitionSet(); - transitionSet.addTransition(new ChangeBounds()) - .addTransition(TransitionInflater.from(getContext()).inflateTransition(android.R.transition.move)) - .setDuration(200); - setSharedElementEnterTransition(transitionSet); + final Context context = getContext(); + if (context != null) { + final TransitionSet transitionSet = new TransitionSet(); + transitionSet.addTransition(new ChangeBounds()) + .addTransition(TransitionInflater.from(context).inflateTransition(android.R.transition.move)) + .setDuration(200); + setSharedElementEnterTransition(transitionSet); + } postponeEnterTransition(); setHasOptionsMenu(true); } @@ -378,10 +384,12 @@ public class TopicPostsFragment extends Fragment implements SwipeRefreshLayout.O } private void navigateToProfile(final String username) { - final NavController navController = NavHostFragment.findNavController(this); - final Bundle bundle = new Bundle(); - bundle.putString("username", username); - navController.navigate(R.id.action_global_profileFragment, bundle); + try { + final NavDirections action = TopicPostsFragmentDirections.actionToProfile().setUsername(username); + NavHostFragment.findNavController(this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "navigateToProfile: ", e); + } } private void showPostsLayoutPreferences() { diff --git a/app/src/main/java/awais/instagrabber/fragments/UserSearchFragment.java b/app/src/main/java/awais/instagrabber/fragments/UserSearchFragment.java deleted file mode 100644 index 4fa33649..00000000 --- a/app/src/main/java/awais/instagrabber/fragments/UserSearchFragment.java +++ /dev/null @@ -1,328 +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 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 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; - } - - public enum SearchMode { - USER_SEARCH("user_name"), - RAVEN("raven"), - RESHARE("reshare"); - - private final String name; - - SearchMode(final String name) { - this.name = name; - } - - public String getName() { - return name; - } - } -} diff --git a/app/src/main/java/awais/instagrabber/fragments/UserSearchFragment.kt b/app/src/main/java/awais/instagrabber/fragments/UserSearchFragment.kt new file mode 100644 index 00000000..a72be70a --- /dev/null +++ b/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): 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 + } +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/UserSearchMode.kt b/app/src/main/java/awais/instagrabber/fragments/UserSearchMode.kt new file mode 100644 index 00000000..fc0e9191 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/fragments/UserSearchMode.kt @@ -0,0 +1,7 @@ +package awais.instagrabber.fragments + +enum class UserSearchMode(val mode: String) { + USER_SEARCH("user_name"), + RAVEN("raven"), + RESHARE("reshare"); +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/comments/CommentsViewerFragment.java b/app/src/main/java/awais/instagrabber/fragments/comments/CommentsViewerFragment.java index 6e3ba016..6bc8643c 100644 --- a/app/src/main/java/awais/instagrabber/fragments/comments/CommentsViewerFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/comments/CommentsViewerFragment.java @@ -15,6 +15,7 @@ import androidx.annotation.NonNull; import androidx.annotation.Nullable; import androidx.constraintlayout.widget.ConstraintLayout; import androidx.fragment.app.FragmentActivity; +import androidx.fragment.app.FragmentTransaction; import androidx.lifecycle.LiveData; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; @@ -34,9 +35,11 @@ import awais.instagrabber.R; import awais.instagrabber.adapters.CommentsAdapter; import awais.instagrabber.customviews.helpers.RecyclerLazyLoader; import awais.instagrabber.databinding.FragmentCommentsBinding; +import awais.instagrabber.fragments.settings.PreferenceKeys; import awais.instagrabber.models.Comment; import awais.instagrabber.models.Resource; import awais.instagrabber.utils.TextUtils; +import awais.instagrabber.utils.Utils; import awais.instagrabber.viewmodels.AppStateViewModel; import awais.instagrabber.viewmodels.CommentsViewerViewModel; @@ -78,7 +81,7 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment { @NonNull @Override public Dialog onCreateDialog(@Nullable final Bundle savedInstanceState) { - return new BottomSheetDialog(getContext(), getTheme()) { + return new BottomSheetDialog(requireContext(), getTheme()) { @Override public void onBackPressed() { if (showingReplies) { @@ -205,12 +208,15 @@ public final class CommentsViewerFragment extends BottomSheetDialogFragment { viewModel, (comment, focusInput) -> { if (comment == null) return null; - final RepliesFragment repliesFragment = RepliesFragment.newInstance(comment, focusInput == null ? false : focusInput); - getChildFragmentManager().beginTransaction() - .setCustomAnimations(R.anim.slide_left, R.anim.slide_right, 0, R.anim.slide_right) - .add(R.id.replies_container_view, repliesFragment) - .addToBackStack(RepliesFragment.TAG) - .commit(); + final boolean disableTransition = Utils.settingsHelper.getBoolean(PreferenceKeys.PREF_DISABLE_SCREEN_TRANSITIONS); + final RepliesFragment repliesFragment = RepliesFragment.newInstance(comment, focusInput != null && focusInput); + final FragmentTransaction transaction = getChildFragmentManager().beginTransaction(); + if (!disableTransition) { + transaction.setCustomAnimations(R.anim.slide_left, R.anim.slide_right, 0, R.anim.slide_right); + } + transaction.add(R.id.replies_container_view, repliesFragment) + .addToBackStack(RepliesFragment.TAG) + .commit(); showingReplies = true; return null; })); diff --git a/app/src/main/java/awais/instagrabber/fragments/comments/Helper.java b/app/src/main/java/awais/instagrabber/fragments/comments/Helper.java index c3fc3f32..f3aabf54 100644 --- a/app/src/main/java/awais/instagrabber/fragments/comments/Helper.java +++ b/app/src/main/java/awais/instagrabber/fragments/comments/Helper.java @@ -1,7 +1,7 @@ package awais.instagrabber.fragments.comments; import android.content.Context; -import android.os.Bundle; +import android.graphics.drawable.Drawable; import android.text.Editable; import android.util.Log; import android.view.View; @@ -45,7 +45,10 @@ public final class Helper { @NonNull final RecyclerView.OnScrollListener lazyLoader) { list.setLayoutManager(layoutManager); final DividerItemDecoration itemDecoration = new DividerItemDecoration(context, LinearLayoutManager.VERTICAL); - itemDecoration.setDrawable(ContextCompat.getDrawable(context, R.drawable.pref_list_divider_material)); + final Drawable drawable = ContextCompat.getDrawable(context, R.drawable.pref_list_divider_material); + if (drawable != null) { + itemDecoration.setDrawable(drawable); + } list.addItemDecoration(itemDecoration); list.addOnScrollListener(lazyLoader); } @@ -68,8 +71,7 @@ public final class Helper { public void onHashtagClick(final String hashtag) { try { if (navController == null) return; - final NavDirections action = CommentsViewerFragmentDirections.actionGlobalHashTagFragment(hashtag); - navController.navigate(action); + navController.navigate(CommentsViewerFragmentDirections.actionToHashtag(hashtag)); } catch (Exception e) { Log.e(TAG, "onHashtagClick: ", e); } @@ -123,12 +125,10 @@ public final class Helper { @Override public void onViewLikes(final Comment comment) { - if (navController == null) return; try { - final Bundle bundle = new Bundle(); - bundle.putString("postId", comment.getPk()); - bundle.putBoolean("isComment", true); - navController.navigate(R.id.action_global_likesViewerFragment, bundle); + if (navController == null) return; + final NavDirections actionToLikes = CommentsViewerFragmentDirections.actionToLikes(comment.getPk(), true); + navController.navigate(actionToLikes); } catch (Exception e) { Log.e(TAG, "onViewLikes: ", e); } @@ -144,10 +144,7 @@ public final class Helper { Toast.makeText(context, R.string.downloader_unknown_error, Toast.LENGTH_SHORT).show(); return; } - String username = ""; - if (comment.getUser() != null) { - username = comment.getUser().getUsername(); - } + final String username = comment.getUser().getUsername(); new MaterialAlertDialogBuilder(context) .setTitle(username) .setMessage(result) @@ -192,9 +189,9 @@ public final class Helper { private static void openProfile(final NavController navController, @NonNull final String username) { - if (navController == null) return; try { - final NavDirections action = CommentsViewerFragmentDirections.actionGlobalProfileFragment(username); + if (navController == null) return; + final NavDirections action = CommentsViewerFragmentDirections.actionToProfile().setUsername(username); navController.navigate(action); } catch (Exception e) { Log.e(TAG, "openProfile: ", e); diff --git a/app/src/main/java/awais/instagrabber/fragments/comments/RepliesFragment.java b/app/src/main/java/awais/instagrabber/fragments/comments/RepliesFragment.java index e799e673..bbb12a7e 100644 --- a/app/src/main/java/awais/instagrabber/fragments/comments/RepliesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/comments/RepliesFragment.java @@ -83,8 +83,13 @@ public class RepliesFragment extends Fragment { @Override public Animation onCreateAnimation(int transit, boolean enter, int nextAnim) { - if (!enter || nextAnim == 0) { - return super.onCreateAnimation(transit, enter, nextAnim); + if (!enter) { + return super.onCreateAnimation(transit, false, nextAnim); + } + if (nextAnim == 0) { + setupList(); + setupObservers(); + return super.onCreateAnimation(transit, true, nextAnim); } final Animation animation = AnimationUtils.loadAnimation(getContext(), nextAnim); animation.setAnimationListener(new Animation.AnimationListener() { @@ -185,18 +190,22 @@ public class RepliesFragment extends Fragment { private void setupAdapter(final long currentUserId) { final Context context = getContext(); if (context == null) return; - commentsAdapter = new CommentsAdapter(currentUserId, - true, - Helper.getCommentCallback(context, - getViewLifecycleOwner(), - getNavController(), - viewModel, - (comment, focusInput) -> { - viewModel.setReplyTo(comment); - binding.commentText.setText(String.format("@%s ", comment.getUser().getUsername())); - if (focusInput) Utils.showKeyboard(binding.commentText); - return null; - })); + commentsAdapter = new CommentsAdapter( + currentUserId, + true, + Helper.getCommentCallback( + context, + getViewLifecycleOwner(), + getNavController(), + viewModel, + (comment, focusInput) -> { + viewModel.setReplyTo(comment); + binding.commentText.setText(String.format("@%s ", comment.getUser().getUsername())); + if (focusInput) Utils.showKeyboard(binding.commentText); + return null; + } + ) + ); binding.comments.setAdapter(commentsAdapter); final Resource> listResource = viewModel.getReplyList().getValue(); commentsAdapter.submitList(listResource != null ? listResource.data : Collections.emptyList()); diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.kt b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.kt index 3138f0e7..78aa6488 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.kt +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageInboxFragment.kt @@ -7,11 +7,9 @@ import android.os.Handler import android.os.Looper import android.util.Log import android.view.* -import androidx.coordinatorlayout.widget.CoordinatorLayout import androidx.fragment.app.Fragment import androidx.fragment.app.activityViewModels -import androidx.lifecycle.Observer -import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener import awais.instagrabber.R @@ -33,18 +31,16 @@ class DirectMessageInboxFragment : Fragment(), OnRefreshListener { private val viewModel: DirectInboxViewModel by activityViewModels() private lateinit var fragmentActivity: MainActivity - private lateinit var root: CoordinatorLayout private lateinit var binding: FragmentDirectMessagesInboxBinding - private lateinit var inboxAdapter: DirectMessageInboxAdapter private lateinit var lazyLoader: RecyclerLazyLoaderAtEdge - private var shouldRefresh = true private var scrollToTop = false private var navigating = false - private var threadsObserver: Observer>? = null + private var pendingRequestsMenuItem: MenuItem? = null private var pendingRequestTotalBadgeDrawable: BadgeDrawable? = null private var isPendingRequestTotalBadgeAttached = false + private var inboxAdapter: DirectMessageInboxAdapter? = null override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) @@ -57,17 +53,11 @@ class DirectMessageInboxFragment : Fragment(), OnRefreshListener { container: ViewGroup?, savedInstanceState: Bundle?, ): View { - if (this::root.isInitialized) { - shouldRefresh = false - return root - } binding = FragmentDirectMessagesInboxBinding.inflate(inflater, container, false) - root = binding.root - return root + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - if (!shouldRefresh) return init() } @@ -90,11 +80,6 @@ class DirectMessageInboxFragment : Fragment(), OnRefreshListener { } } - override fun onResume() { - super.onResume() - setupObservers() - } - override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) { inflater.inflate(R.menu.dm_inbox_menu, menu) pendingRequestsMenuItem = menu.findItem(R.id.pending_requests) @@ -103,9 +88,9 @@ class DirectMessageInboxFragment : Fragment(), OnRefreshListener { override fun onOptionsItemSelected(item: MenuItem): Boolean { if (item.itemId == R.id.pending_requests) { - val directions = DirectMessageInboxFragmentDirections.actionInboxToPendingInbox() try { - NavHostFragment.findNavController(this).navigate(directions) + val directions = DirectMessageInboxFragmentDirections.actionToPendingInbox() + findNavController().navigate(directions) } catch (e: Exception) { Log.e(TAG, "onOptionsItemSelected: ", e) } @@ -119,23 +104,14 @@ class DirectMessageInboxFragment : Fragment(), OnRefreshListener { init() } - override fun onDestroy() { - super.onDestroy() - removeViewModelObservers() - viewModel.onDestroy() - } - private fun setupObservers() { - removeViewModelObservers() - threadsObserver = Observer { list: List -> - if (!this::inboxAdapter.isInitialized) return@Observer - inboxAdapter.submitList(list) { + viewModel.threads.observe(viewLifecycleOwner, { list: List -> + inboxAdapter?.submitList(list) { if (!scrollToTop) return@submitList binding.inboxList.post { binding.inboxList.smoothScrollToPosition(0) } scrollToTop = false } - } - threadsObserver?.let { viewModel.threads.observe(fragmentActivity, it) } + }) viewModel.inbox.observe(viewLifecycleOwner, { inboxResource: Resource? -> if (inboxResource == null) return@observe 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() { val context = context ?: return setupObservers() @@ -210,18 +181,20 @@ class DirectMessageInboxFragment : Fragment(), OnRefreshListener { if (navigating || threadId.isNullOrBlank() || threadTitle.isNullOrBlank()) return@DirectMessageInboxAdapter navigating = true if (isAdded) { - val directions = DirectMessageInboxFragmentDirections.actionInboxToThread(threadId, threadTitle) try { - NavHostFragment.findNavController(this).navigate(directions) + val directions = DirectMessageInboxFragmentDirections.actionToThread(threadId, threadTitle) + findNavController().navigate(directions) } catch (e: Exception) { Log.e(TAG, "init: ", e) } } navigating = false + }.also { + it.setHasStableIds(true) } - inboxAdapter.setHasStableIds(true) 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) + } } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.kt b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.kt index ee39b885..83fe3274 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.kt +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageSettingsFragment.kt @@ -11,8 +11,8 @@ import androidx.fragment.app.activityViewModels import androidx.lifecycle.LiveData import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager -import awais.instagrabber.ProfileNavGraphDirections import awais.instagrabber.R import awais.instagrabber.activities.MainActivity import awais.instagrabber.adapters.DirectPendingUsersAdapter @@ -25,13 +25,11 @@ import awais.instagrabber.dialogs.ConfirmDialogFragment import awais.instagrabber.dialogs.ConfirmDialogFragment.ConfirmDialogFragmentCallback import awais.instagrabber.dialogs.MultiOptionDialogFragment import awais.instagrabber.dialogs.MultiOptionDialogFragment.MultiOptionDialogSingleCallback -import awais.instagrabber.fragments.UserSearchFragment -import awais.instagrabber.fragments.UserSearchFragmentDirections +import awais.instagrabber.fragments.UserSearchMode import awais.instagrabber.models.Resource import awais.instagrabber.repositories.responses.User import awais.instagrabber.repositories.responses.directmessages.DirectThreadParticipantRequestsResponse import awais.instagrabber.repositories.responses.directmessages.RankedRecipient -import awais.instagrabber.utils.TextUtils.isEmpty import awais.instagrabber.utils.Utils import awais.instagrabber.utils.extensions.TAG import awais.instagrabber.viewmodels.AppStateViewModel @@ -203,19 +201,22 @@ class DirectMessageSettingsFragment : Fragment(), ConfirmDialogFragmentCallback } binding.addMembers.setOnClickListener { if (!isAdded) return@setOnClickListener - val navController = NavHostFragment.findNavController(this) - val currentDestination = navController.currentDestination ?: return@setOnClickListener - if (currentDestination.id != R.id.directMessagesSettingsFragment) return@setOnClickListener - val users = viewModel.getUsers().value - val currentUserIds: LongArray = users?.asSequence()?.map { obj: User -> obj.pk }?.sorted()?.toList()?.toLongArray() ?: LongArray(0) - val actionGlobalUserSearch = UserSearchFragmentDirections - .actionGlobalUserSearch() - .setTitle(getString(R.string.add_members)) - .setActionLabel(getString(R.string.add)) - .setHideUserIds(currentUserIds) - .setSearchMode(UserSearchFragment.SearchMode.RAVEN) - .setMultiple(true) - navController.navigate(actionGlobalUserSearch) + try { + val navController = findNavController() + if (navController.currentDestination?.id != R.id.directMessagesSettingsFragment) return@setOnClickListener + val users = viewModel.getUsers().value ?: return@setOnClickListener + val currentUserIds = users.asSequence().map(User::pk).sorted().toList().toLongArray() + val actionGlobalUserSearch = DirectMessageSettingsFragmentDirections.actionToUserSearch().apply { + title = getString(R.string.add_members) + actionLabel = getString(R.string.add) + hideUserIds = currentUserIds + searchMode = UserSearchMode.RAVEN + multiple = true + } + navController.navigate(actionGlobalUserSearch) + } catch (e: Exception) { + Log.e(TAG, "setupSettings: ", e) + } } binding.muteMentionsLabel.setOnClickListener { binding.muteMentions.toggle() } binding.muteMentions.setOnCheckedChangeListener { buttonView: CompoundButton, isChecked: Boolean -> @@ -300,10 +301,13 @@ class DirectMessageSettingsFragment : Fragment(), ConfirmDialogFragmentCallback Utils.openURL(context, "https://facebook.com/" + user.interopMessagingUserFbid) return@DirectUsersAdapter } - if (isEmpty(user.username)) return@DirectUsersAdapter - val directions = ProfileNavGraphDirections - .actionGlobalProfileFragment("@" + user.username) - NavHostFragment.findNavController(this).navigate(directions) + if (user.username.isBlank()) return@DirectUsersAdapter + try { + val directions = DirectMessageSettingsFragmentDirections.actionToProfile().apply { this.username = user.username } + findNavController().navigate(directions) + } catch (e: Exception) { + Log.e(TAG, "setupMembers: ", e) + } }, { _: Int, user: User? -> val options = viewModel.createUserOptions(user) @@ -339,9 +343,12 @@ class DirectMessageSettingsFragment : Fragment(), ConfirmDialogFragmentCallback binding.pendingMembers.layoutManager = LinearLayoutManager(context) pendingUsersAdapter = DirectPendingUsersAdapter(object : PendingUserCallback { override fun onClick(position: Int, pendingUser: PendingUser) { - val directions = ProfileNavGraphDirections - .actionGlobalProfileFragment("@" + pendingUser.user.username) - NavHostFragment.findNavController(this@DirectMessageSettingsFragment).navigate(directions) + try { + val directions = DirectMessageSettingsFragmentDirections.actionToProfile().apply { this.username = pendingUser.user.username } + findNavController().navigate(directions) + } catch (e: Exception) { + Log.e(TAG, "onClick: ", e) + } } override fun onApprove(position: Int, pendingUser: PendingUser) { @@ -420,7 +427,7 @@ class DirectMessageSettingsFragment : Fragment(), ConfirmDialogFragmentCallback if (resource == null) return@observe when (resource.status) { Resource.Status.SUCCESS -> { - val directions = DirectMessageSettingsFragmentDirections.actionSettingsToInbox() + val directions = DirectMessageSettingsFragmentDirections.actionToInbox() NavHostFragment.findNavController(this).navigate(directions) } Resource.Status.ERROR -> { diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java index 1169a989..5c74e2a8 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectMessageThreadFragment.java @@ -65,9 +65,7 @@ import java.util.List; import java.util.Set; import java.util.function.Function; -import awais.instagrabber.ProfileNavGraphDirections; import awais.instagrabber.R; -import awais.instagrabber.UserSearchNavGraphDirections; import awais.instagrabber.activities.CameraActivity; import awais.instagrabber.activities.MainActivity; import awais.instagrabber.adapters.DirectItemsAdapter; @@ -95,9 +93,7 @@ import awais.instagrabber.customviews.helpers.TranslateDeferringInsetsAnimationC import awais.instagrabber.databinding.FragmentDirectMessagesThreadBinding; import awais.instagrabber.dialogs.DirectItemReactionDialogFragment; import awais.instagrabber.dialogs.GifPickerBottomDialogFragment; -import awais.instagrabber.fragments.PostViewV2Fragment; -import awais.instagrabber.fragments.UserSearchFragment; -import awais.instagrabber.fragments.UserSearchFragmentDirections; +import awais.instagrabber.fragments.UserSearchMode; import awais.instagrabber.fragments.settings.PreferenceKeys; import awais.instagrabber.models.Resource; import awais.instagrabber.models.enums.DirectItemType; @@ -107,6 +103,9 @@ import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.directmessages.DirectItem; import awais.instagrabber.repositories.responses.directmessages.DirectItemEmojiReaction; +import awais.instagrabber.repositories.responses.directmessages.DirectItemLink; +import awais.instagrabber.repositories.responses.directmessages.DirectItemReactions; +import awais.instagrabber.repositories.responses.directmessages.DirectItemReelShare; import awais.instagrabber.repositories.responses.directmessages.DirectItemStoryShare; import awais.instagrabber.repositories.responses.directmessages.DirectItemVisualMedia; import awais.instagrabber.repositories.responses.directmessages.DirectThread; @@ -136,7 +135,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact private InsetsAnimationLinearLayout root; private boolean shouldRefresh = true; private List itemOrHeaders; - private List users; private FragmentDirectMessagesThreadBinding binding; private Tooltip tooltip; private float initialSendX; @@ -163,13 +161,13 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact private LiveData> usersLiveData; private boolean autoMarkAsSeen = false; private MenuItem markAsSeenMenuItem; - private Media tempMedia; private DirectItem addReactionItem; private TranslateDeferringInsetsAnimationCallback inputHolderAnimationCallback; private TranslateDeferringInsetsAnimationCallback chatsAnimationCallback; private EmojiPickerInsetsAnimationCallback emojiPickerAnimationCallback; private boolean hasKbOpenedOnce; private boolean wasToggled; + private SwipeAndRestoreItemTouchHelperCallback touchHelperCallback; private final AppExecutors appExecutors = AppExecutors.INSTANCE; private final Animatable2Compat.AnimationCallback micToSendAnimationCallback = new Animatable2Compat.AnimationCallback() { @@ -189,8 +187,12 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact private final DirectItemCallback directItemCallback = new DirectItemCallback() { @Override public void onHashtagClick(final String hashtag) { - final NavDirections action = DirectMessageThreadFragmentDirections.actionGlobalHashTagFragment(hashtag); - NavHostFragment.findNavController(DirectMessageThreadFragment.this).navigate(action); + try { + final NavDirections action = DirectMessageThreadFragmentDirections.actionToHashtag(hashtag); + NavHostFragment.findNavController(DirectMessageThreadFragment.this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "onHashtagClick: ", e); + } } @Override @@ -200,8 +202,12 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact @Override public void onLocationClick(final long locationId) { - final NavDirections action = DirectMessageThreadFragmentDirections.actionGlobalLocationFragment(locationId); - NavHostFragment.findNavController(DirectMessageThreadFragment.this).navigate(action); + try { + final NavDirections action = DirectMessageThreadFragmentDirections.actionToLocation(locationId); + NavHostFragment.findNavController(DirectMessageThreadFragment.this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "onLocationClick: ", e); + } } @Override @@ -221,26 +227,23 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact @Override public void onMediaClick(final Media media, final int index) { if (media.isReelMedia()) { - final String pk = media.getPk(); try { + final String pk = media.getPk(); + if (pk == null) return; final long mediaId = Long.parseLong(pk); final User user = media.getUser(); if (user == null) return; final String username = user.getUsername(); - final NavDirections action = DirectMessageThreadFragmentDirections - .actionThreadToStory(StoryViewerOptions.forStory(mediaId, username)); + final NavDirections action = DirectMessageThreadFragmentDirections.actionToStory(StoryViewerOptions.forStory(mediaId, username)); NavHostFragment.findNavController(DirectMessageThreadFragment.this).navigate(action); - } catch (NumberFormatException e) { + } catch (Exception e) { Log.e(TAG, "onMediaClick (story): ", e); } return; } - final NavController navController = NavHostFragment.findNavController(DirectMessageThreadFragment.this); - final Bundle bundle = new Bundle(); - bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, media); - bundle.putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, index); try { - navController.navigate(R.id.action_global_post_view, bundle); + final NavDirections actionToPost = DirectMessageThreadFragmentDirections.actionToPost(media, index); + NavHostFragment.findNavController(DirectMessageThreadFragment.this).navigate(actionToPost); } catch (Exception e) { Log.e(TAG, "openPostDialog: ", e); } @@ -248,16 +251,18 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact @Override public void onStoryClick(final DirectItemStoryShare storyShare) { - final String pk = storyShare.getReelId(); try { + final String pk = storyShare.getReelId(); + if (pk == null) return; final long mediaId = Long.parseLong(pk); - final User user = storyShare.getMedia().getUser(); + final Media media = storyShare.getMedia(); + if (media == null) return; + final User user = media.getUser(); if (user == null) return; final String username = user.getUsername(); - final NavDirections action = DirectMessageThreadFragmentDirections - .actionThreadToStory(StoryViewerOptions.forUser(mediaId, username)); + final NavDirections action = DirectMessageThreadFragmentDirections.actionToStory(StoryViewerOptions.forUser(mediaId, username)); NavHostFragment.findNavController(DirectMessageThreadFragment.this).navigate(action); - } catch (NumberFormatException e) { + } catch (Exception e) { Log.e(TAG, "onStoryClick: ", e); } } @@ -266,9 +271,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact public void onReaction(final DirectItem item, final Emoji emoji) { if (item == null || emoji == null) return; final LiveData> resourceLiveData = viewModel.sendReaction(item, emoji); - if (resourceLiveData != null) { - resourceLiveData.observe(getViewLifecycleOwner(), directItemResource -> handleSentMessage(resourceLiveData)); - } + resourceLiveData.observe(getViewLifecycleOwner(), directItemResource -> handleSentMessage(resourceLiveData)); } @Override @@ -284,15 +287,14 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact } if (itemId == R.id.forward) { itemToForward = item; - final UserSearchNavGraphDirections.ActionGlobalUserSearch actionGlobalUserSearch = UserSearchFragmentDirections - .actionGlobalUserSearch() + final NavDirections actionGlobalUserSearch = DirectMessageThreadFragmentDirections + .actionToUserSearch() .setTitle(getString(R.string.forward)) .setActionLabel(getString(R.string.send)) .setShowGroups(true) .setMultiple(true) - .setSearchMode(UserSearchFragment.SearchMode.RAVEN); - final NavController navController = NavHostFragment.findNavController(DirectMessageThreadFragment.this); - navController.navigate(actionGlobalUserSearch); + .setSearchMode(UserSearchMode.RAVEN); + NavHostFragment.findNavController(DirectMessageThreadFragment.this).navigate(actionGlobalUserSearch); } if (itemId == R.id.download) { downloadItem(item); @@ -418,10 +420,10 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact public boolean onOptionsItemSelected(@NonNull final MenuItem item) { final int itemId = item.getItemId(); if (itemId == R.id.info) { - final DirectMessageThreadFragmentDirections.ActionThreadToSettings directions = DirectMessageThreadFragmentDirections - .actionThreadToSettings(viewModel.getThreadId(), null); final Boolean pending = viewModel.isPending().getValue(); - directions.setPending(pending != null && pending); + final NavDirections directions = DirectMessageThreadFragmentDirections + .actionToSettings(viewModel.getThreadId(), null) + .setPending(pending != null && pending); NavHostFragment.findNavController(this).navigate(directions); return true; } @@ -485,7 +487,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact } final Uri uri = data.getData(); final String mimeType = Utils.getMimeType(uri, context.getContentResolver()); - if (mimeType.startsWith("image")) { + if (mimeType != null && mimeType.startsWith("image")) { navigateToImageEditFragment(uri); return; } @@ -584,7 +586,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact if (getArguments() == null) return; actionBar = fragmentActivity.getSupportActionBar(); setupList(); - root.post(this::setupInput); } private void setupList() { @@ -612,20 +613,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact } }); binding.chats.addItemDecoration(headerItemDecoration); - final SwipeAndRestoreItemTouchHelperCallback touchHelperCallback = new SwipeAndRestoreItemTouchHelperCallback( - context, - (adapterPosition, viewHolder) -> { - if (itemsAdapter == null) return; - final DirectItemOrHeader directItemOrHeader = itemsAdapter.getList().get(adapterPosition); - if (directItemOrHeader.isHeader()) return; - viewModel.setReplyToItem(directItemOrHeader.item); - } - ); - final Integer inputMode = viewModel.getInputMode().getValue(); - if (inputMode != null && inputMode != 1) { - itemTouchHelper = new ItemTouchHelper(touchHelperCallback); - itemTouchHelper.attachToRecyclerView(binding.chats); - } } private void setObservers() { @@ -653,8 +640,12 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact inputModeLiveData = viewModel.getInputMode(); inputModeLiveData.observe(getViewLifecycleOwner(), inputMode -> { final Boolean isPending = viewModel.isPending().getValue(); - if (isPending != null && isPending) return; - if (inputMode == null || inputMode == 0) return; + if (isPending != null && isPending || inputMode == null) return; + setupInput(inputMode); + if (inputMode == 0) { + setupTouchHelper(); + return; + } if (inputMode == 1) { hideInput(); } @@ -754,6 +745,22 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact }); } + private void setupTouchHelper() { + final Context context = getContext(); + if (context == null) return; + touchHelperCallback = new SwipeAndRestoreItemTouchHelperCallback( + context, + (adapterPosition, viewHolder) -> { + if (itemsAdapter == null) return; + final DirectItemOrHeader directItemOrHeader = itemsAdapter.getList().get(adapterPosition); + if (directItemOrHeader.isHeader()) return; + viewModel.setReplyToItem(directItemOrHeader.item); + } + ); + itemTouchHelper = new ItemTouchHelper(touchHelperCallback); + itemTouchHelper.attachToRecyclerView(binding.chats); + } + private void removeObservers() { pendingLiveData.removeObservers(getViewLifecycleOwner()); inputModeLiveData.removeObservers(getViewLifecycleOwner()); @@ -894,19 +901,26 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact binding.gallery.setVisibility(View.GONE); } - private String getDirectItemPreviewText(final DirectItem item) { - switch (item.getItemType()) { + private String getDirectItemPreviewText(@NonNull final DirectItem item) { + final DirectItemType itemType = item.getItemType(); + if (itemType == null) return ""; + switch (itemType) { case TEXT: return item.getText(); case LINK: - return item.getLink().getText(); + final DirectItemLink link = item.getLink(); + if (link == null) return ""; + return link.getText(); case MEDIA: { final Media media = item.getMedia(); + if (media == null) return ""; return getMediaPreviewTextString(media); } case RAVEN_MEDIA: { final DirectItemVisualMedia visualMedia = item.getVisualMedia(); + if (visualMedia == null) return ""; final Media media = visualMedia.getMedia(); + if (media == null) return ""; return getMediaPreviewTextString(media); } case VOICE_MEDIA: @@ -914,14 +928,17 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact case MEDIA_SHARE: return getString(R.string.post); case REEL_SHARE: - return item.getReelShare().getText(); + final DirectItemReelShare reelShare = item.getReelShare(); + if (reelShare == null) return ""; + return reelShare.getText(); } return ""; } @NonNull - private String getMediaPreviewTextString(final Media media) { + private String getMediaPreviewTextString(@NonNull final Media media) { final MediaItemType mediaType = media.getType(); + if (mediaType == null) return ""; switch (mediaType) { case MEDIA_TYPE_IMAGE: return getString(R.string.photo); @@ -932,8 +949,11 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact } } - private String getDirectItemPreviewImageUrl(final DirectItem item) { - switch (item.getItemType()) { + @Nullable + private String getDirectItemPreviewImageUrl(@NonNull final DirectItem item) { + final DirectItemType itemType = item.getItemType(); + if (itemType == null) return null; + switch (itemType) { case TEXT: case LINK: case VOICE_MEDIA: @@ -945,6 +965,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact } case RAVEN_MEDIA: { final DirectItemVisualMedia visualMedia = item.getVisualMedia(); + if (visualMedia == null) return null; final Media media = visualMedia.getMedia(); return ResponseBodyUtils.getThumbUrl(media); } @@ -1009,7 +1030,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact itemsAdapter.setStateRestorationPolicy(RecyclerView.Adapter.StateRestorationPolicy.PREVENT_WHEN_EMPTY); binding.chats.setAdapter(itemsAdapter); registerDataObserver(); - users = thread.getUsers(); final List items = viewModel.getItems().getValue(); if (items != null && itemsAdapter.getItems() != items) { submitItemsToAdapter(items); @@ -1032,8 +1052,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact }); } - private void setupInput() { - final Integer inputMode = viewModel.getInputMode().getValue(); + private void setupInput(@Nullable final Integer inputMode) { if (inputMode != null && inputMode == 1) return; final Context context = getContext(); if (context == null) return; @@ -1234,9 +1253,12 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact } private void navigateToImageEditFragment(final Uri uri) { - final NavDirections navDirections = DirectMessageThreadFragmentDirections.actionThreadToImageEdit(uri); - final NavController navController = NavHostFragment.findNavController(this); - navController.navigate(navDirections); + try { + final NavDirections navDirections = DirectMessageThreadFragmentDirections.actionToImageEdit(uri); + NavHostFragment.findNavController(this).navigate(navDirections); + } catch (Exception e) { + Log.e(TAG, "navigateToImageEditFragment: ", e); + } } private void handleSentMessage(final LiveData> resourceLiveData) { @@ -1403,11 +1425,14 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact final Context context = getContext(); if (context == null) return; final DirectItemType itemType = item.getItemType(); + if (itemType == null) return; //noinspection SwitchStatementWithTooFewBranches switch (itemType) { case VOICE_MEDIA: downloadItem(context, item.getVoiceMedia() == null ? null : item.getVoiceMedia().getMedia()); break; + default: + break; } } @@ -1421,15 +1446,6 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact Toast.makeText(context, R.string.downloader_downloading_media, Toast.LENGTH_SHORT).show(); } - @Nullable - private User getUser(final long userId) { - for (final User user : users) { - if (userId != user.getPk()) continue; - return user; - } - return null; - } - // Sets the translationY of views to height with animation private void animatePan(final int height, @Nullable final Function onAnimationStart, @@ -1476,17 +1492,22 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact final LiveData> leftUsers = viewModel.getLeftUsers(); final ArrayList allUsers = new ArrayList<>(); allUsers.add(viewModel.getCurrentUser()); - if (users != null && users.getValue() != null) { + if (users.getValue() != null) { allUsers.addAll(users.getValue()); } - if (leftUsers != null && leftUsers.getValue() != null) { + if (leftUsers.getValue() != null) { allUsers.addAll(leftUsers.getValue()); } - reactionDialogFragment = DirectItemReactionDialogFragment - .newInstance(viewModel.getViewerId(), - allUsers, - item.getItemId(), - item.getReactions()); + final String itemId = item.getItemId(); + if (itemId == null) return; + final DirectItemReactions reactions = item.getReactions(); + if (reactions == null) return; + reactionDialogFragment = DirectItemReactionDialogFragment.newInstance( + viewModel.getViewerId(), + allUsers, + itemId, + reactions + ); reactionDialogFragment.show(getChildFragmentManager(), "reactions_dialog"); } @@ -1498,9 +1519,7 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact if (itemId == null || reaction == null) return; if (reaction.getSenderId() == viewModel.getViewerId()) { final LiveData> resourceLiveData = viewModel.sendDeleteReaction(itemId); - if (resourceLiveData != null) { - resourceLiveData.observe(getViewLifecycleOwner(), directItemResource -> handleSentMessage(resourceLiveData)); - } + resourceLiveData.observe(getViewLifecycleOwner(), directItemResource -> handleSentMessage(resourceLiveData)); return; } // navigate to user @@ -1510,17 +1529,18 @@ public class DirectMessageThreadFragment extends Fragment implements DirectReact } private void navigateToUser(@NonNull final String username) { - final ProfileNavGraphDirections.ActionGlobalProfileFragment direction = ProfileNavGraphDirections - .actionGlobalProfileFragment("@" + username); - NavHostFragment.findNavController(DirectMessageThreadFragment.this).navigate(direction); + try { + final NavDirections direction = DirectMessageThreadFragmentDirections.actionToProfile().setUsername(username); + NavHostFragment.findNavController(DirectMessageThreadFragment.this).navigate(direction); + } catch (Exception e) { + Log.e(TAG, "navigateToUser: ", e); + } } @Override public void onClick(final View view, final Emoji emoji) { if (addReactionItem == null || emoji == null) return; final LiveData> resourceLiveData = viewModel.sendReaction(addReactionItem, emoji); - if (resourceLiveData != null) { - resourceLiveData.observe(getViewLifecycleOwner(), directItemResource -> handleSentMessage(resourceLiveData)); - } + resourceLiveData.observe(getViewLifecycleOwner(), directItemResource -> handleSentMessage(resourceLiveData)); } } diff --git a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectPendingInboxFragment.kt b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectPendingInboxFragment.kt index efb4d73f..f321a556 100644 --- a/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectPendingInboxFragment.kt +++ b/app/src/main/java/awais/instagrabber/fragments/directmessages/DirectPendingInboxFragment.kt @@ -134,7 +134,7 @@ class DirectPendingInboxFragment : Fragment(), OnRefreshListener { val threadTitle = thread.threadTitle ?: return@DirectMessageInboxAdapter navigating = true if (isAdded) { - val directions = DirectPendingInboxFragmentDirections.actionPendingInboxToThread(threadId, threadTitle) + val directions = DirectPendingInboxFragmentDirections.actionToThread(threadId, threadTitle) directions.pending = true NavHostFragment.findNavController(this).navigate(directions) } diff --git a/app/src/main/java/awais/instagrabber/fragments/main/DiscoverFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/DiscoverFragment.java index 80120a44..1f832c6b 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/DiscoverFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/DiscoverFragment.java @@ -13,7 +13,7 @@ import androidx.appcompat.app.AlertDialog; import androidx.coordinatorlayout.widget.CoordinatorLayout; import androidx.fragment.app.Fragment; import androidx.lifecycle.ViewModelProvider; -import androidx.navigation.NavController; +import androidx.navigation.NavDirections; import androidx.navigation.fragment.FragmentNavigator; import androidx.navigation.fragment.NavHostFragment; import androidx.swiperefreshlayout.widget.SwipeRefreshLayout; @@ -26,7 +26,6 @@ import awais.instagrabber.activities.MainActivity; import awais.instagrabber.adapters.DiscoverTopicsAdapter; import awais.instagrabber.customviews.helpers.GridSpacingItemDecoration; import awais.instagrabber.databinding.FragmentDiscoverBinding; -import awais.instagrabber.fragments.PostViewV2Fragment; import awais.instagrabber.repositories.responses.Media; import awais.instagrabber.repositories.responses.discover.TopicCluster; import awais.instagrabber.repositories.responses.discover.TopicalExploreFeedResponse; @@ -98,11 +97,14 @@ public class DiscoverFragment extends Fragment implements SwipeRefreshLayout.OnR binding.topicsRecyclerView.addItemDecoration(new GridSpacingItemDecoration(Utils.convertDpToPx(2))); final DiscoverTopicsAdapter.OnTopicClickListener otcl = new DiscoverTopicsAdapter.OnTopicClickListener() { public void onTopicClick(final TopicCluster topicCluster, final View cover, final int titleColor, final int backgroundColor) { - final FragmentNavigator.Extras.Builder builder = new FragmentNavigator.Extras.Builder() - .addSharedElement(cover, "cover-" + topicCluster.getId()); - final DiscoverFragmentDirections.ActionDiscoverFragmentToTopicPostsFragment action = DiscoverFragmentDirections - .actionDiscoverFragmentToTopicPostsFragment(topicCluster, titleColor, backgroundColor); - NavHostFragment.findNavController(DiscoverFragment.this).navigate(action, builder.build()); + try { + final FragmentNavigator.Extras.Builder builder = new FragmentNavigator.Extras.Builder() + .addSharedElement(cover, "cover-" + topicCluster.getId()); + final NavDirections action = DiscoverFragmentDirections.actionToTopicPosts(topicCluster, titleColor, backgroundColor); + NavHostFragment.findNavController(DiscoverFragment.this).navigate(action, builder.build()); + } catch (Exception e) { + Log.e(TAG, "onTopicClick: ", e); + } } public void onTopicLongClick(final Media coverMedia) { @@ -123,11 +125,9 @@ public class DiscoverFragment extends Fragment implements SwipeRefreshLayout.OnR } catch (Throwable ignored) {} return; } - final NavController navController = NavHostFragment.findNavController(DiscoverFragment.this); - final Bundle bundle = new Bundle(); - bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, media); try { - navController.navigate(R.id.action_global_post_view, bundle); + final NavDirections action = DiscoverFragmentDirections.actionToPost(media, 0); + NavHostFragment.findNavController(DiscoverFragment.this).navigate(action); alertDialog.dismiss(); } catch (Exception e) { Log.e(TAG, "onTopicLongClick: ", e); @@ -148,11 +148,13 @@ public class DiscoverFragment extends Fragment implements SwipeRefreshLayout.OnR public void onSuccess(final TopicalExploreFeedResponse result) { if (result == null) return; final List clusters = result.getClusters(); + if (clusters == null || result.getItems() == null) return; binding.swipeRefreshLayout.setRefreshing(false); if (clusters.size() == 1 && result.getItems().size() > 0) { final TopicCluster cluster = clusters.get(0); - if (cluster.getCoverMedia() == null) + if (cluster.getCoverMedia() == null) { cluster.setCoverMedia(result.getItems().get(0).getMedia()); + } topicClusterViewModel.getList().postValue(Collections.singletonList(cluster)); return; } diff --git a/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java b/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java index 539c8361..39dcbbdd 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/main/FeedFragment.java @@ -40,10 +40,11 @@ import awais.instagrabber.asyncs.FeedPostFetchService; import awais.instagrabber.customviews.PrimaryActionModeCallback; import awais.instagrabber.databinding.FragmentFeedBinding; import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment; -import awais.instagrabber.fragments.PostViewV2Fragment; import awais.instagrabber.models.PostsLayoutPreferences; import awais.instagrabber.repositories.requests.StoryViewerOptions; +import awais.instagrabber.repositories.responses.Location; import awais.instagrabber.repositories.responses.Media; +import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.stories.Story; import awais.instagrabber.utils.AppExecutors; import awais.instagrabber.utils.Constants; @@ -62,7 +63,6 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre private FragmentFeedBinding binding; private StoriesRepository storiesRepository; private boolean shouldRefresh = true; - private final boolean isRotate = false; private FeedStoriesViewModel feedStoriesViewModel; private boolean storiesFetching; private ActionMode actionMode; @@ -77,15 +77,20 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre public void onFeedStoryClick(Story model, int position) { final NavController navController = NavHostFragment.findNavController(FeedFragment.this); if (isSafeToNavigate(navController)) { - final NavDirections action = FeedFragmentDirections - .actionFeedFragmentToStoryViewerFragment(StoryViewerOptions.forFeedStoryPosition(position)); - navController.navigate(action); + try { + final NavDirections action = FeedFragmentDirections.actionToStory(StoryViewerOptions.forFeedStoryPosition(position)); + navController.navigate(action); + } catch (Exception e) { + Log.e(TAG, "onFeedStoryClick: ", e); + } } } @Override public void onFeedStoryLongClick(Story model, int position) { - navigateToProfile("@" + model.getUser().getUsername()); + final User user = model.getUser(); + if (user == null) return; + navigateToProfile("@" + user.getUsername()); } } ); @@ -104,10 +109,12 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre @Override public void onCommentsClick(final Media feedModel) { try { - final NavDirections commentsAction = FeedFragmentDirections.actionGlobalCommentsViewerFragment( + final User user = feedModel.getUser(); + if (user == null) return; + final NavDirections commentsAction = FeedFragmentDirections.actionToComments( feedModel.getCode(), feedModel.getPk(), - feedModel.getUser().getPk() + user.getPk() ); NavHostFragment.findNavController(FeedFragment.this).navigate(commentsAction); } catch (Exception e) { @@ -124,14 +131,24 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre @Override public void onHashtagClick(final String hashtag) { - final NavDirections action = FeedFragmentDirections.actionGlobalHashTagFragment(hashtag); - NavHostFragment.findNavController(FeedFragment.this).navigate(action); + try { + final NavDirections action = FeedFragmentDirections.actionToHashtag(hashtag); + NavHostFragment.findNavController(FeedFragment.this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "onHashtagClick: ", e); + } } @Override public void onLocationClick(final Media feedModel) { - final NavDirections action = FeedFragmentDirections.actionGlobalLocationFragment(feedModel.getLocation().getPk()); - NavHostFragment.findNavController(FeedFragment.this).navigate(action); + final Location location = feedModel.getLocation(); + if (location == null) return; + try { + final NavDirections action = FeedFragmentDirections.actionToLocation(location.getPk()); + NavHostFragment.findNavController(FeedFragment.this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "onLocationClick: ", e); + } } @Override @@ -162,12 +179,9 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre } private void openPostDialog(final Media feedModel, final int position) { - final NavController navController = NavHostFragment.findNavController(FeedFragment.this); - final Bundle bundle = new Bundle(); - bundle.putSerializable(PostViewV2Fragment.ARG_MEDIA, feedModel); - bundle.putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, position); try { - navController.navigate(R.id.action_global_post_view, bundle); + final NavDirections action = FeedFragmentDirections.actionToPost(feedModel, position); + NavHostFragment.findNavController(FeedFragment.this).navigate(action); } catch (Exception e) { Log.e(TAG, "openPostDialog: ", e); } @@ -237,10 +251,12 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre }; private void navigateToProfile(final String username) { - final NavController navController = NavHostFragment.findNavController(this); - final Bundle bundle = new Bundle(); - bundle.putString("username", username); - navController.navigate(R.id.action_global_profileFragment, bundle); + try { + final NavDirections action = FeedFragmentDirections.actionToProfile().setUsername(username); + NavHostFragment.findNavController(this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "navigateToProfile: ", e); + } } @Override @@ -301,8 +317,12 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre @Override public boolean onOptionsItemSelected(@NonNull final MenuItem item) { if (item.getItemId() == R.id.storyList) { - final NavDirections action = FeedFragmentDirections.actionGlobalStoryListViewerFragment("feed"); - NavHostFragment.findNavController(FeedFragment.this).navigate(action); + try { + final NavDirections action = FeedFragmentDirections.actionToStoryList("feed"); + NavHostFragment.findNavController(FeedFragment.this).navigate(action); + } catch (Exception e) { + Log.e(TAG, "onOptionsItemSelected: ", e); + } } else if (item.getItemId() == R.id.layout) { showPostsLayoutPreferences(); return true; @@ -316,14 +336,6 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre fetchStories(); } - @Override - public void onDestroyView() { - super.onDestroyView(); - if (storiesRecyclerView != null) { - fragmentActivity.removeCollapsingView(storiesRecyclerView); - } - } - private void setupFeed() { binding.feedRecyclerView.setViewModelStoreOwner(this) .setLifeCycleOwner(this) @@ -333,7 +345,6 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre .setFeedItemCallback(feedItemCallback) .setSelectionModeCallback(selectionModeCallback) .init(); - binding.feedSwipeRefreshLayout.setRefreshing(true); binding.feedRecyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { @Override public void onScrolled(@NonNull final RecyclerView recyclerView, final int dx, final int dy) { @@ -397,7 +408,8 @@ public class FeedFragment extends Fragment implements SwipeRefreshLayout.OnRefre preferences -> { layoutPreferences = preferences; new Handler().postDelayed(() -> binding.feedRecyclerView.setLayoutPreferences(preferences), 200); - }); + } + ); fragment.show(getChildFragmentManager(), "posts_layout_preferences"); } diff --git a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt index 326bc57a..34f5a35d 100644 --- a/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt +++ b/app/src/main/java/awais/instagrabber/fragments/main/ProfileFragment.kt @@ -13,13 +13,13 @@ import android.view.* import android.widget.Toast import androidx.activity.OnBackPressedCallback import androidx.appcompat.content.res.AppCompatResources -import androidx.constraintlayout.motion.widget.MotionLayout import androidx.fragment.app.Fragment import androidx.fragment.app.FragmentTransaction import androidx.lifecycle.MutableLiveData import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.navigation.fragment.NavHostFragment +import androidx.navigation.fragment.findNavController import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView import androidx.swiperefreshlayout.widget.SwipeRefreshLayout.OnRefreshListener @@ -40,10 +40,7 @@ import awais.instagrabber.dialogs.MultiOptionDialogFragment.MultiOptionDialogSin import awais.instagrabber.dialogs.MultiOptionDialogFragment.Option import awais.instagrabber.dialogs.PostsLayoutPreferencesDialogFragment import awais.instagrabber.dialogs.ProfilePicDialogFragment -import awais.instagrabber.fragments.HashTagFragment.ARG_HASHTAG -import awais.instagrabber.fragments.PostViewV2Fragment -import awais.instagrabber.fragments.UserSearchFragment -import awais.instagrabber.fragments.UserSearchFragmentDirections +import awais.instagrabber.fragments.UserSearchMode import awais.instagrabber.managers.DirectMessagesManager import awais.instagrabber.models.Resource import awais.instagrabber.models.enums.PostItemType @@ -77,16 +74,26 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall private var selectedMedia: List? = null private var actionMode: ActionMode? = null private var disableDm: Boolean = false - private var shouldRefresh: Boolean = true + + // private var shouldRefresh: Boolean = true private var highlightsAdapter: HighlightsAdapter? = null private var layoutPreferences = Utils.getPostsLayoutPreferences(Constants.PREF_PROFILE_POSTS_LAYOUT) private lateinit var mainActivity: MainActivity - private lateinit var root: MotionLayout + + // private lateinit var root: MotionLayout private lateinit var binding: FragmentProfileBinding private lateinit var appStateViewModel: AppStateViewModel 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 ppOptsDialogRequestCode = 101 private val bioDialogRequestCode = 102 @@ -105,8 +112,12 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall } override fun onLocationClick(media: Media?) { - val action = FeedFragmentDirections.actionGlobalLocationFragment(media?.location?.pk ?: return) - NavHostFragment.findNavController(this@ProfileFragment).navigate(action) + try { + val action = ProfileFragmentDirections.actionToLocation(media?.location?.pk ?: return) + findNavController().navigate(action) + } catch (e: Exception) { + Log.e(TAG, "onLocationClick: ", e) + } } override fun onMentionClick(mention: String?) { @@ -114,17 +125,25 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall } override fun onHashtagClick(hashtag: String?) { - val action = FeedFragmentDirections.actionGlobalHashTagFragment(hashtag ?: return) - NavHostFragment.findNavController(this@ProfileFragment).navigate(action) + try { + val action = ProfileFragmentDirections.actionToHashtag(hashtag ?: return) + findNavController().navigate(action) + } catch (e: Exception) { + Log.e(TAG, "onHashtagClick: ", e) + } } override fun onCommentsClick(media: Media?) { - val commentsAction = ProfileFragmentDirections.actionGlobalCommentsViewerFragment( - media?.code ?: return, - media.pk ?: return, - media.user?.pk ?: return - ) - NavHostFragment.findNavController(this@ProfileFragment).navigate(commentsAction) + try { + val commentsAction = ProfileFragmentDirections.actionToComments( + media?.code ?: return, + media.pk ?: return, + media.user?.pk ?: return + ) + findNavController().navigate(commentsAction) + } catch (e: Exception) { + Log.e(TAG, "onCommentsClick: ", e) + } } override fun onDownloadClick(media: Media?, childPosition: Int, popupLocation: View) { @@ -216,24 +235,24 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall } private val onFollowersClickListener = View.OnClickListener { try { - val action = ProfileFragmentDirections.actionProfileFragmentToFollowViewerFragment( + val action = ProfileFragmentDirections.actionToFollowViewer( viewModel.profile.value?.data?.pk ?: return@OnClickListener, true, viewModel.profile.value?.data?.username ?: return@OnClickListener ) - NavHostFragment.findNavController(this).navigate(action) + findNavController().navigate(action) } catch (e: Exception) { Log.e(TAG, "onFollowersClickListener: ", e) } } private val onFollowingClickListener = View.OnClickListener { try { - val action = ProfileFragmentDirections.actionProfileFragmentToFollowViewerFragment( + val action = ProfileFragmentDirections.actionToFollowViewer( viewModel.profile.value?.data?.pk ?: return@OnClickListener, false, viewModel.profile.value?.data?.username ?: return@OnClickListener ) - NavHostFragment.findNavController(this).navigate(action) + findNavController().navigate(action) } catch (e: Exception) { Log.e(TAG, "onFollowersClickListener: ", e) } @@ -243,9 +262,8 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall } private val onHashtagClickListener = OnHashtagClickListener { try { - val bundle = Bundle() - bundle.putString(ARG_HASHTAG, it.originalText.trimAll()) - NavHostFragment.findNavController(this).navigate(R.id.action_global_hashTagFragment, bundle) + val actionToHashtag = ProfileFragmentDirections.actionToHashtag(it.originalText.trimAll()) + findNavController().navigate(actionToHashtag) } catch (e: Exception) { Log.e(TAG, "onHashtagClickListener: ", e) } @@ -280,13 +298,9 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall } private fun openPostDialog(media: Media, position: Int) { - val bundle = Bundle().apply { - putSerializable(PostViewV2Fragment.ARG_MEDIA, media) - putInt(PostViewV2Fragment.ARG_SLIDER_POSITION, position) - } try { - val navController = NavHostFragment.findNavController(this) - navController.navigate(R.id.action_global_post_view, bundle) + val actionToPost = ProfileFragmentDirections.actionToPost(media, position) + findNavController().navigate(actionToPost) } catch (e: Exception) { Log.e(TAG, "openPostDialog: ", e) } @@ -304,7 +318,15 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall viewModel = ViewModelProvider( this, ProfileFragmentViewModelFactory( - FavoriteRepository.getInstance(requireContext()), + csrfToken, + deviceUuid, + userRepository, + friendshipRepository, + storiesRepository, + mediaRepository, + graphQLRepository, + favoriteRepository, + directMessagesRepository, if (isLoggedIn) DirectMessagesManager else null, this, arguments @@ -314,23 +336,12 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall } 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) - root = binding.root - return root + return binding.root } override fun onViewCreated(view: View, savedInstanceState: Bundle?) { - if (!shouldRefresh) { - setupObservers() - return - } init() - shouldRefresh = false } override fun onRefresh() { @@ -378,17 +389,21 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall } } + override fun onDestroyView() { + super.onDestroyView() + setupPostsDone = false + } + private fun shareProfileViaDm() { - val actionGlobalUserSearch = UserSearchFragmentDirections.actionGlobalUserSearch().apply { - setTitle(getString(R.string.share)) - setActionLabel(getString(R.string.send)) - showGroups = true - multiple = true - setSearchMode(UserSearchFragment.SearchMode.RAVEN) - } try { - val navController = NavHostFragment.findNavController(this@ProfileFragment) - navController.navigate(actionGlobalUserSearch) + val actionToUserSearch = ProfileFragmentDirections.actionToUserSearch().apply { + title = getString(R.string.share) + actionLabel = getString(R.string.send) + showGroups = true + multiple = true + searchMode = UserSearchMode.RAVEN + } + findNavController().navigate(actionToUserSearch) } catch (e: Exception) { Log.e(TAG, "shareProfileViaDm: ", e) } @@ -405,12 +420,12 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall private fun navigateToChaining() { viewModel.currentUser.value?.data ?: return val profile = viewModel.profile.value?.data ?: return - val bundle = Bundle().apply { - putString("type", "chaining") - putLong("targetId", profile.pk) - } try { - NavHostFragment.findNavController(this).navigate(R.id.action_global_notificationsViewerFragment, bundle) + val actionToNotifications = ProfileFragmentDirections.actionToNotifications().apply { + type = "chaining" + targetId = profile.pk + } + findNavController().navigate(actionToNotifications) } catch (e: Exception) { Log.e(TAG, "navigateToChaining: ", e) } @@ -418,13 +433,17 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall private fun init() { binding.swipeRefreshLayout.setOnRefreshListener(this) - disableDm = !Utils.isNavRootInCurrentTabs("direct_messages_nav_graph") + disableDm = !isNavRootInCurrentTabs("direct_messages_nav_graph") setupHighlights() 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) { val (currentUserResource, profileResource) = it if (currentUserResource.status == Resource.Status.ERROR || profileResource.status == Resource.Status.ERROR) { @@ -449,7 +468,7 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall context?.let { ctx -> Toast.makeText(ctx, R.string.error_loading_profile, Toast.LENGTH_LONG).show() } return@observe } - root.loadLayoutDescription(R.xml.header_list_scene) + binding.root.loadLayoutDescription(R.xml.header_list_scene) setupFavChip(profile, currentUser) setupFavButton(currentUser, profile) setupSavedButton(currentUser, profile) @@ -529,7 +548,7 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall binding.privatePage2.visibility = VISIBLE binding.postsRecyclerView.visibility = GONE binding.swipeRefreshLayout.isRefreshing = false - root.getTransition(R.id.transition)?.setEnable(false) + binding.root.getTransition(R.id.transition)?.setEnable(false) } private fun setupProfileContext(contextPair: Pair?>) { @@ -709,14 +728,14 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall } binding.header.btnLiked.setOnClickListener { try { - val action = ProfileFragmentDirections.actionProfileFragmentToSavedViewerFragment( + val action = ProfileFragmentDirections.actionToSaved( viewModel.profile.value?.data?.username ?: return@setOnClickListener, viewModel.profile.value?.data?.pk ?: return@setOnClickListener, PostItemType.LIKED ) - NavHostFragment.findNavController(this).navigate(action) + findNavController().navigate(action) } catch (e: Exception) { - Log.e(TAG, "setupTaggedButton: ", e) + Log.e(TAG, "setupLikedButton: ", e) } } } @@ -730,12 +749,12 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall } binding.header.btnTagged.setOnClickListener { try { - val action = ProfileFragmentDirections.actionProfileFragmentToSavedViewerFragment( + val action = ProfileFragmentDirections.actionToSaved( viewModel.profile.value?.data?.username ?: return@setOnClickListener, viewModel.profile.value?.data?.pk ?: return@setOnClickListener, PostItemType.TAGGED ) - NavHostFragment.findNavController(this).navigate(action) + findNavController().navigate(action) } catch (e: Exception) { Log.e(TAG, "setupTaggedButton: ", e) } @@ -751,8 +770,8 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall } binding.header.btnSaved.setOnClickListener { try { - val action = ProfileFragmentDirections.actionGlobalSavedCollectionsFragment(false) - NavHostFragment.findNavController(this).navigate(action) + val action = ProfileFragmentDirections.actionToSavedCollections().apply { isSaving = false } + findNavController().navigate(action) } catch (e: Exception) { Log.e(TAG, "setupSavedButton: ", e) } @@ -833,7 +852,7 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall } 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.privatePage2.visibility = View.VISIBLE binding.privatePage1.setImageResource(R.drawable.ic_outline_info_24) @@ -843,9 +862,8 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall private fun setupHighlights() { val context = context ?: return highlightsAdapter = HighlightsAdapter { model, position -> - val options = StoryViewerOptions.forHighlight(model.user!!.pk, "") - options.currentFeedStoryIndex = position - val action = ProfileFragmentDirections.actionProfileFragmentToStoryViewerFragment(options) + val options = StoryViewerOptions.forHighlight(model.user!!.pk, "").apply { currentFeedStoryIndex = position } + val action = ProfileFragmentDirections.actionToStory(options) NavHostFragment.findNavController(this).navigate(action) } binding.header.highlightsList.layoutManager = LinearLayoutManager(context, RecyclerView.HORIZONTAL, false) @@ -869,7 +887,7 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy) val canScrollVertically = recyclerView.canScrollVertically(-1) - root.getTransition(R.id.transition)?.setEnable(!canScrollVertically) + binding.root.getTransition(R.id.transition)?.setEnable(!canScrollVertically) } }) setupPostsDone = true @@ -877,10 +895,9 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall private fun navigateToProfile(username: String?) { try { - val bundle = Bundle() - bundle.putString("username", username ?: return) - val navController = NavHostFragment.findNavController(this) - navController.navigate(R.id.action_global_profileFragment, bundle) + val username1 = username ?: return + val actionToProfile = ProfileFragmentDirections.actionToProfile().apply { this.username = username1 } + findNavController().navigate(actionToProfile) } catch (e: Exception) { Log.e(TAG, "navigateToProfile: ", e) } @@ -933,13 +950,13 @@ class ProfileFragment : Fragment(), OnRefreshListener, ConfirmDialogFragmentCall "profile_pic" -> showProfilePicDialog() "show_stories" -> { try { - val action = ProfileFragmentDirections.actionProfileFragmentToStoryViewerFragment( + val action = ProfileFragmentDirections.actionToStory( StoryViewerOptions.forUser( viewModel.profile.value?.data?.pk ?: return, viewModel.profile.value?.data?.username ?: return, ) ) - NavHostFragment.findNavController(this).navigate(action) + findNavController().navigate(action) } catch (e: Exception) { Log.e(TAG, "omPpOptionSelect: ", e) } diff --git a/app/src/main/java/awais/instagrabber/fragments/search/SearchFragment.java b/app/src/main/java/awais/instagrabber/fragments/search/SearchFragment.java index 96cb0191..ea6bc5bc 100644 --- a/app/src/main/java/awais/instagrabber/fragments/search/SearchFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/search/SearchFragment.java @@ -19,7 +19,7 @@ import androidx.fragment.app.FragmentActivity; import androidx.lifecycle.LiveData; import androidx.lifecycle.Observer; import androidx.lifecycle.ViewModelProvider; -import androidx.navigation.NavController; +import androidx.navigation.NavDirections; import androidx.navigation.fragment.NavHostFragment; import com.google.android.material.snackbar.Snackbar; @@ -125,7 +125,9 @@ public class SearchFragment extends Fragment implements SearchCategoryFragment.O mainActivity.showSearchView(); } if (settingsHelper.getBoolean(PREF_SEARCH_FOCUS_KEYBOARD)) { - searchInput.requestFocus(); + if (searchInput != null) { + searchInput.requestFocus(); + } final InputMethodManager imm = (InputMethodManager) requireContext().getSystemService(Context.INPUT_METHOD_SERVICE); if (imm != null) imm.showSoftInput(searchInput, InputMethodManager.SHOW_IMPLICIT); } @@ -205,24 +207,23 @@ public class SearchFragment extends Fragment implements SearchCategoryFragment.O if (!searchItem.isFavorite()) { viewModel.saveToRecentSearches(searchItem); // insert or update recent } - final NavController navController = NavHostFragment.findNavController(this); - final Bundle bundle = new Bundle(); + final NavDirections action; switch (type) { case USER: - bundle.putString("username", searchItem.getUser().getUsername()); - navController.navigate(R.id.action_global_profileFragment, bundle); + action = SearchFragmentDirections.actionToProfile().setUsername(searchItem.getUser().getUsername()); + NavHostFragment.findNavController(this).navigate(action); break; case HASHTAG: - bundle.putString("hashtag", searchItem.getHashtag().getName()); - navController.navigate(R.id.action_global_hashTagFragment, bundle); + action = SearchFragmentDirections.actionToHashtag(searchItem.getHashtag().getName()); + NavHostFragment.findNavController(this).navigate(action); break; case LOCATION: - bundle.putLong("locationId", searchItem.getPlace().getLocation().getPk()); - navController.navigate(R.id.action_global_locationFragment, bundle); + action = SearchFragmentDirections.actionToLocation(searchItem.getPlace().getLocation().getPk()); break; default: - break; + return; } + NavHostFragment.findNavController(this).navigate(action); } catch (Exception e) { Log.e(TAG, "onSearchItemClick: ", e); } diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java index 1ff802fd..a5d2024b 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/DownloadsPreferencesFragment.java @@ -1,5 +1,7 @@ package awais.instagrabber.fragments.settings; +import android.annotation.SuppressLint; +import android.content.ActivityNotFoundException; import android.content.Context; import android.content.Intent; import android.net.Uri; @@ -79,9 +81,17 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && initialUri != null) { intent.putExtra(DocumentsContract.EXTRA_INITIAL_URI, initialUri); } - startActivityForResult(intent, SELECT_DIR_REQUEST_CODE); + try { + startActivityForResult(intent, SELECT_DIR_REQUEST_CODE); + } catch (ActivityNotFoundException e) { + Log.e(TAG, "openDirectoryChooser: ", e); + showErrorDialog(getString(R.string.no_directory_picker_activity)); + } catch (Exception e) { + Log.e(TAG, "openDirectoryChooser: ", e); + } } + @SuppressLint("StringFormatInvalid") @Override public void onActivityResult(final int requestCode, final int resultCode, @Nullable final Intent data) { if (requestCode != SELECT_DIR_REQUEST_CODE) return; @@ -105,17 +115,9 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment { try (final StringWriter sw = new StringWriter(); final PrintWriter pw = new PrintWriter(sw)) { e.printStackTrace(pw); - final ConfirmDialogFragment dialogFragment = ConfirmDialogFragment.newInstance( - 123, - R.string.error, - "com.android.externalstorage.documents".equals(data.getData().getAuthority()) - ? "Please report this error to the developers:\n\n" + sw.toString() - : getString(R.string.dir_select_no_download_folder, data.getData().getAuthority()), - R.string.ok, - 0, - 0 - ); - dialogFragment.show(getChildFragmentManager(), ConfirmDialogFragment.class.getSimpleName()); + showErrorDialog("com.android.externalstorage.documents".equals(data.getData().getAuthority()) + ? "Please report this error to the developers:\n\n" + sw.toString() + : getString(R.string.dir_select_no_download_folder, data.getData().getAuthority())); } catch (IOException ioException) { Log.e(TAG, "onActivityResult: ", ioException); } @@ -123,6 +125,18 @@ public class DownloadsPreferencesFragment extends BasePreferencesFragment { }, 500); } + private void showErrorDialog(final String message) { + final ConfirmDialogFragment dialogFragment = ConfirmDialogFragment.newInstance( + 123, + R.string.error, + message, + R.string.ok, + 0, + 0 + ); + dialogFragment.show(getChildFragmentManager(), ConfirmDialogFragment.class.getSimpleName()); + } + private Preference getPrependUsernameToFilenamePreference(@NonNull final Context context) { final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context); preference.setKey(PreferenceKeys.DOWNLOAD_PREPEND_USER_NAME); diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/GeneralPreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/GeneralPreferencesFragment.java index e8403162..52e7fdd6 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/GeneralPreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/GeneralPreferencesFragment.java @@ -1,7 +1,6 @@ package awais.instagrabber.fragments.settings; import android.content.Context; -import android.util.Pair; import androidx.annotation.NonNull; import androidx.preference.ListPreference; @@ -17,8 +16,9 @@ import awais.instagrabber.dialogs.TabOrderPreferenceDialogFragment; import awais.instagrabber.models.Tab; import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; +import awais.instagrabber.utils.NavigationHelperKt; import awais.instagrabber.utils.TextUtils; -import awais.instagrabber.utils.Utils; +import kotlin.Pair; import static awais.instagrabber.utils.Utils.settingsHelper; @@ -34,30 +34,32 @@ public class GeneralPreferencesFragment extends BasePreferencesFragment implemen screen.addPreference(getDefaultTabPreference(context)); screen.addPreference(getTabOrderPreference(context)); } + screen.addPreference(getDisableScreenTransitionsPreference(context)); screen.addPreference(getUpdateCheckPreference(context)); screen.addPreference(getFlagSecurePreference(context)); screen.addPreference(getSearchFocusPreference(context)); - final List preferences = FlavorSettings.getInstance() - .getPreferences(context, - getChildFragmentManager(), - SettingCategory.GENERAL); - if (preferences != null) { - for (final Preference preference : preferences) { - screen.addPreference(preference); - } + final List preferences = FlavorSettings + .getInstance() + .getPreferences( + context, + getChildFragmentManager(), + SettingCategory.GENERAL + ); + for (final Preference preference : preferences) { + screen.addPreference(preference); } } private Preference getDefaultTabPreference(@NonNull final Context context) { final ListPreference preference = new ListPreference(context); preference.setSummaryProvider(ListPreference.SimpleSummaryProvider.getInstance()); - final Pair, List> listPair = Utils.getNavTabList(context); - final List tabs = listPair.first; + final Pair, List> listPair = NavigationHelperKt.getLoggedInNavTabs(context); + final List tabs = listPair.getFirst(); final String[] titles = tabs.stream() .map(Tab::getTitle) .toArray(String[]::new); final String[] navGraphFileNames = tabs.stream() - .map(Tab::getGraphName) + .map(tab -> NavigationHelperKt.getNavGraphNameForNavRootId(tab.getNavigationRootId())) .toArray(String[]::new); preference.setKey(Constants.DEFAULT_TAB); preference.setTitle(R.string.pref_start_screen); @@ -81,6 +83,14 @@ public class GeneralPreferencesFragment extends BasePreferencesFragment implemen return preference; } + private Preference getDisableScreenTransitionsPreference(@NonNull final Context context) { + final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context); + preference.setKey(PreferenceKeys.PREF_DISABLE_SCREEN_TRANSITIONS); + preference.setTitle(R.string.disable_screen_transitions); + preference.setIconSpaceReserved(false); + return preference; + } + private Preference getUpdateCheckPreference(@NonNull final Context context) { final SwitchPreferenceCompat preference = new SwitchPreferenceCompat(context); preference.setKey(PreferenceKeys.CHECK_UPDATES); diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java index 64546efc..51186d50 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java +++ b/app/src/main/java/awais/instagrabber/fragments/settings/MorePreferencesFragment.java @@ -35,6 +35,7 @@ import awais.instagrabber.utils.Constants; import awais.instagrabber.utils.CookieUtils; import awais.instagrabber.utils.CoroutineUtilsKt; import awais.instagrabber.utils.FlavorTown; +import awais.instagrabber.utils.NavigationHelperKt; import awais.instagrabber.utils.ProcessPhoenix; import awais.instagrabber.utils.TextUtils; import awais.instagrabber.utils.Utils; @@ -174,14 +175,18 @@ public class MorePreferencesFragment extends BasePreferencesFragment { boolean showActivity = true; boolean showExplore = false; if (activity != null) { - showActivity = !Utils.isNavRootInCurrentTabs("notification_viewer_nav_graph"); - showExplore = !Utils.isNavRootInCurrentTabs("discover_nav_graph"); + showActivity = !NavigationHelperKt.isNavRootInCurrentTabs("notification_viewer_nav_graph"); + showExplore = !NavigationHelperKt.isNavRootInCurrentTabs("discover_nav_graph"); } if (showActivity) { screen.addPreference(getPreference(R.string.action_notif, R.drawable.ic_not_liked, preference -> { if (isSafeToNavigate(navController)) { - final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("notif"); - navController.navigate(navDirections); + try { + final NavDirections navDirections = MorePreferencesFragmentDirections.actionToNotifications().setType("notif"); + navController.navigate(navDirections); + } catch (Exception e) { + Log.e(TAG, "setupPreferenceScreen: ", e); + } } return true; })); @@ -197,15 +202,23 @@ public class MorePreferencesFragment extends BasePreferencesFragment { screen.addPreference(getPreference(R.string.action_ayml, R.drawable.ic_suggested_users, preference -> { if (isSafeToNavigate(navController)) { - final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalNotificationsViewerFragment("ayml"); - navController.navigate(navDirections); + try { + final NavDirections navDirections = MorePreferencesFragmentDirections.actionToNotifications().setType("ayml"); + navController.navigate(navDirections); + } catch (Exception e) { + Log.e(TAG, "setupPreferenceScreen: ", e); + } } return true; })); screen.addPreference(getPreference(R.string.action_archive, R.drawable.ic_archive, preference -> { if (isSafeToNavigate(navController)) { - final NavDirections navDirections = MorePreferencesFragmentDirections.actionGlobalStoryListViewerFragment("archive"); - navController.navigate(navDirections); + try { + final NavDirections navDirections = MorePreferencesFragmentDirections.actionToStoryList("archive"); + navController.navigate(navDirections); + } catch (Exception e) { + Log.e(TAG, "setupPreferenceScreen: ", e); + } } return true; })); @@ -214,13 +227,17 @@ public class MorePreferencesFragment extends BasePreferencesFragment { // Check if favorites has been added as a tab. And if so, do not add in this list boolean showFavorites = true; if (activity != null) { - showFavorites = !Utils.isNavRootInCurrentTabs("favorites_nav_graph"); + showFavorites = !NavigationHelperKt.isNavRootInCurrentTabs("favorites_nav_graph"); } if (showFavorites) { screen.addPreference(getPreference(R.string.title_favorites, R.drawable.ic_star_24, preference -> { if (isSafeToNavigate(navController)) { - final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToFavoritesFragment(); - navController.navigate(navDirections); + try { + final NavDirections navDirections = MorePreferencesFragmentDirections.actionToFavorites(); + navController.navigate(navDirections); + } catch (Exception e) { + Log.e(TAG, "setupPreferenceScreen: ", e); + } } return true; })); @@ -229,36 +246,50 @@ public class MorePreferencesFragment extends BasePreferencesFragment { screen.addPreference(getDivider(context)); screen.addPreference(getPreference(R.string.action_settings, R.drawable.ic_outline_settings_24, preference -> { if (isSafeToNavigate(navController)) { - final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToSettingsPreferencesFragment(); - navController.navigate(navDirections); + try { + final NavDirections navDirections = MorePreferencesFragmentDirections.actionToSettings(); + navController.navigate(navDirections); + } catch (Exception e) { + Log.e(TAG, "setupPreferenceScreen: ", e); + } } return true; })); screen.addPreference(getPreference(R.string.backup_and_restore, R.drawable.ic_settings_backup_restore_24, preference -> { if (isSafeToNavigate(navController)) { - final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToBackupPreferencesFragment(); - navController.navigate(navDirections); + try { + final NavDirections navDirections = MorePreferencesFragmentDirections.actionToBackup(); + navController.navigate(navDirections); + } catch (Exception e) { + Log.e(TAG, "setupPreferenceScreen: ", e); + } } return true; })); screen.addPreference(getPreference(R.string.action_about, R.drawable.ic_outline_info_24, preference1 -> { if (isSafeToNavigate(navController)) { - final NavDirections navDirections = MorePreferencesFragmentDirections.actionMorePreferencesFragmentToAboutFragment(); - navController.navigate(navDirections); + try { + final NavDirections navDirections = MorePreferencesFragmentDirections.actionToAbout(); + navController.navigate(navDirections); + } catch (Exception e) { + Log.e(TAG, "setupPreferenceScreen: ", e); + } } return true; })); screen.addPreference(getDivider(context)); - screen.addPreference(getPreference(R.string.version, - BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")", - -1, - preference -> { - if (BuildConfig.isPre) return true; - if (activity == null) return false; - FlavorTown.updateCheck(activity, true); - return true; - })); + screen.addPreference(getPreference( + R.string.version, + BuildConfig.VERSION_NAME + " (" + BuildConfig.VERSION_CODE + ")", + -1, + preference -> { + if (BuildConfig.isPre) return true; + if (activity == null) return false; + FlavorTown.updateCheck(activity, true); + return true; + }) + ); screen.addPreference(getDivider(context)); final Preference reminderPreference = getPreference(R.string.reminder, R.string.reminder_summary, R.drawable.ic_warning, null); @@ -285,32 +316,33 @@ public class MorePreferencesFragment extends BasePreferencesFragment { // adds cookies to database for quick access final long uid = CookieUtils.getUserIdFromCookie(cookie); final UserRepository userRepository = UserRepository.Companion.getInstance(); - userRepository.getUserInfo(uid, CoroutineUtilsKt.getContinuation((user, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { - if (throwable != null) { - Log.e(TAG, "Error fetching user info", throwable); - return; - } - if (user != null) { - accountRepository.insertOrUpdateAccount( - uid, - user.getUsername(), - cookie, - user.getFullName(), - user.getProfilePicUrl(), - CoroutineUtilsKt.getContinuation((account, throwable1) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { - if (throwable1 != null) { - Log.e(TAG, "onActivityResult: ", throwable1); - return; - } - AppExecutors.INSTANCE.getMainThread().execute(() -> { - final Context context = getContext(); - if (context == null) return; - ProcessPhoenix.triggerRebirth(context); - }, 200); - }), Dispatchers.getIO()) - ); - } - }), Dispatchers.getIO())); + userRepository + .getUserInfo(uid, CoroutineUtilsKt.getContinuation((user, throwable) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { + if (throwable != null) { + Log.e(TAG, "Error fetching user info", throwable); + return; + } + if (user != null) { + accountRepository.insertOrUpdateAccount( + uid, + user.getUsername(), + cookie, + user.getFullName(), + user.getProfilePicUrl(), + CoroutineUtilsKt.getContinuation((account, throwable1) -> AppExecutors.INSTANCE.getMainThread().execute(() -> { + if (throwable1 != null) { + Log.e(TAG, "onActivityResult: ", throwable1); + return; + } + AppExecutors.INSTANCE.getMainThread().execute(() -> { + final Context context = getContext(); + if (context == null) return; + ProcessPhoenix.triggerRebirth(context); + }, 200); + }), Dispatchers.getIO()) + ); + } + }), Dispatchers.getIO())); } } diff --git a/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt index d104c7b8..450b31f5 100644 --- a/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt +++ b/app/src/main/java/awais/instagrabber/fragments/settings/PreferenceKeys.kt @@ -11,6 +11,7 @@ object PreferenceKeys { const val PREF_SHOWN_COUNT_TOOLTIP = "shown_count_tooltip" const val PREF_SEARCH_FOCUS_KEYBOARD = "search_focus_keyboard" const val PREF_AUTO_BACKUP_ENABLED = "auto_backup_enabled" + const val PREF_DISABLE_SCREEN_TRANSITIONS = "disable_screen_transitions" const val PREF_STORY_SHOW_LIST = "story_show_list" // string prefs diff --git a/app/src/main/java/awais/instagrabber/models/Tab.kt b/app/src/main/java/awais/instagrabber/models/Tab.kt index 5a730d1e..d31352a1 100644 --- a/app/src/main/java/awais/instagrabber/models/Tab.kt +++ b/app/src/main/java/awais/instagrabber/models/Tab.kt @@ -9,12 +9,6 @@ data class Tab( val title: String, val isRemovable: Boolean, - /** - * This is name part of the navigation resource - * eg: @navigation/ **graphName** - */ - val graphName: String, - /** * This is the actual resource id of the navigation resource (R.navigation.graphName = navigationResId) */ diff --git a/app/src/main/java/awais/instagrabber/utils/BarinstaDeepLinkHelper.kt b/app/src/main/java/awais/instagrabber/utils/BarinstaDeepLinkHelper.kt new file mode 100644 index 00000000..1517e157 --- /dev/null +++ b/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() \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/Constants.kt b/app/src/main/java/awais/instagrabber/utils/Constants.kt index 82b1f87e..7710379e 100644 --- a/app/src/main/java/awais/instagrabber/utils/Constants.kt +++ b/app/src/main/java/awais/instagrabber/utils/Constants.kt @@ -90,4 +90,5 @@ object Constants { const val DM_THREAD_ACTION_EXTRA_THREAD_TITLE = "thread_title" const val X_IG_APP_ID = "936619743392459" const val EXTRA_INITIAL_URI = "initial_uri" + const val defaultDateTimeFormat = "hh:mm:ss a 'on' dd-MM-yyyy" } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/DateUtils.kt b/app/src/main/java/awais/instagrabber/utils/DateUtils.kt index 474d4eb8..299c1f71 100644 --- a/app/src/main/java/awais/instagrabber/utils/DateUtils.kt +++ b/app/src/main/java/awais/instagrabber/utils/DateUtils.kt @@ -1,6 +1,9 @@ package awais.instagrabber.utils +import android.util.Log +import awais.instagrabber.utils.extensions.TAG import java.time.LocalDateTime +import java.time.format.DateTimeFormatter import java.util.* object DateUtils { @@ -14,4 +17,13 @@ object DateUtils { fun isBeforeOrEqual(localDateTime: LocalDateTime, comparedTo: LocalDateTime): Boolean { return localDateTime.isBefore(comparedTo) || localDateTime.isEqual(comparedTo) } + + @JvmStatic + fun checkFormatterValid(datetimeParser: DateTimeFormatter): Boolean = try { + LocalDateTime.now().format(datetimeParser) + true + } catch (e: Exception) { + Log.e(TAG, "checkFormatterValid: ", e) + false + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/KeywordsFilterUtils.kt b/app/src/main/java/awais/instagrabber/utils/KeywordsFilterUtils.kt index 1a972f8b..355144da 100644 --- a/app/src/main/java/awais/instagrabber/utils/KeywordsFilterUtils.kt +++ b/app/src/main/java/awais/instagrabber/utils/KeywordsFilterUtils.kt @@ -2,8 +2,8 @@ package awais.instagrabber.utils import awais.instagrabber.repositories.responses.Media import java.util.* +import kotlin.collections.ArrayList -class KeywordsFilterUtils(private val keywords: ArrayList) { // fun filter(caption: String?): Boolean { // if (caption == null) return false // if (keywords.isEmpty()) return false @@ -14,24 +14,17 @@ class KeywordsFilterUtils(private val keywords: ArrayList) { // return false // } - fun filter(media: Media?): Boolean { - if (media == null) return false - val (_, text) = media.caption ?: return false - if (keywords.isEmpty()) return false - val temp = text!!.lowercase(Locale.getDefault()) - for (s in keywords) { - if (temp.contains(s)) return true - } - return false - } +private fun containsAnyKeyword(keywords: List, media: Media?): Boolean { + if (media == null || keywords.isEmpty()) return false + val (_, text) = media.caption ?: return false + val temp = text!!.lowercase(Locale.getDefault()) + return keywords.any { temp.contains(it) } +} - fun filter(media: List?): List? { - if (keywords.isEmpty()) return media - if (media == null) return ArrayList() - val result: MutableList = ArrayList() - for (m in media) { - if (!filter(m)) result.add(m) - } - return result - } +fun filter(keywords: List, media: List?): List? { + if (keywords.isEmpty()) return media + if (media == null) return ArrayList() + val result: MutableList = ArrayList() + media.filterNotTo(result) { containsAnyKeyword(keywords, it) } + return result } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java b/app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java index bc0f3cb4..02c8b0a7 100644 --- a/app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java +++ b/app/src/main/java/awais/instagrabber/utils/NavigationExtensions.java @@ -1,247 +1,247 @@ -package awais.instagrabber.utils; - -import android.annotation.SuppressLint; -import android.content.Intent; -import android.util.Log; -import android.util.SparseArray; - -import androidx.annotation.NonNull; -import androidx.fragment.app.Fragment; -import androidx.fragment.app.FragmentManager; -import androidx.fragment.app.FragmentTransaction; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; -import androidx.navigation.NavController; -import androidx.navigation.NavDestination; -import androidx.navigation.NavGraph; -import androidx.navigation.fragment.NavHostFragment; - -import com.google.android.material.bottomnavigation.BottomNavigationView; - -import java.util.List; - -import awais.instagrabber.R; -import awais.instagrabber.customviews.NavHostFragmentWithDefaultAnimations; -import awais.instagrabber.fragments.main.FeedFragment; - -/** - * This is a Java rewrite of NavigationExtensions - * from architecture-components-samples. Some modifications have been done, check git history. - */ -public class NavigationExtensions { - private static final String TAG = NavigationExtensions.class.getSimpleName(); - private static String selectedItemTag; - private static boolean isOnFirstFragment; - - @NonNull - public static LiveData setupWithNavController(@NonNull final BottomNavigationView bottomNavigationView, - @NonNull List navGraphIds, - @NonNull final FragmentManager fragmentManager, - final int containerId, - @NonNull Intent intent, - final int firstFragmentGraphIndex) { - final SparseArray graphIdToTagMap = new SparseArray<>(); - final MutableLiveData selectedNavController = new MutableLiveData<>(); - int firstFragmentGraphId = 0; - for (int i = 0; i < navGraphIds.size(); i++) { - final int navGraphId = navGraphIds.get(i); - final String fragmentTag = getFragmentTag(navGraphId); - final NavHostFragment navHostFragment = obtainNavHostFragment(fragmentManager, fragmentTag, navGraphId, containerId); - final NavController navController = navHostFragment.getNavController(); - final int graphId = navController.getGraph().getId(); - if (i == firstFragmentGraphIndex) { - firstFragmentGraphId = graphId; - } - graphIdToTagMap.put(graphId, fragmentTag); - if (bottomNavigationView.getSelectedItemId() == graphId) { - selectedNavController.setValue(navHostFragment.getNavController()); - attachNavHostFragment(fragmentManager, navHostFragment, i == firstFragmentGraphIndex); - } else { - detachNavHostFragment(fragmentManager, navHostFragment); - } - } - selectedItemTag = graphIdToTagMap.get(bottomNavigationView.getSelectedItemId()); - final String firstFragmentTag = graphIdToTagMap.get(firstFragmentGraphId); - isOnFirstFragment = selectedItemTag != null && selectedItemTag.equals(firstFragmentTag); - bottomNavigationView.setOnItemSelectedListener(item -> { - if (fragmentManager.isStateSaved()) { - return false; - } - String newlySelectedItemTag = graphIdToTagMap.get(item.getItemId()); - String tag = selectedItemTag; - if (tag != null && !tag.equals(newlySelectedItemTag)) { - fragmentManager.popBackStack(firstFragmentTag, FragmentManager.POP_BACK_STACK_INCLUSIVE); - Fragment fragment = fragmentManager.findFragmentByTag(newlySelectedItemTag); - if (fragment == null) { - return false; - // throw new RuntimeException("null cannot be cast to non-null NavHostFragment"); - } - final NavHostFragment selectedFragment = (NavHostFragment) fragment; - if (firstFragmentTag != null && !firstFragmentTag.equals(newlySelectedItemTag)) { - FragmentTransaction fragmentTransaction = fragmentManager - .beginTransaction() - .setCustomAnimations( - R.anim.nav_default_enter_anim, - R.anim.nav_default_exit_anim, - R.anim.nav_default_pop_enter_anim, - R.anim.nav_default_pop_exit_anim - ) - .attach(selectedFragment) - .setPrimaryNavigationFragment(selectedFragment); - for (int i = 0; i < graphIdToTagMap.size(); i++) { - final int key = graphIdToTagMap.keyAt(i); - final String fragmentTagForId = graphIdToTagMap.get(key); - if (!fragmentTagForId.equals(newlySelectedItemTag)) { - final Fragment fragmentByTag = fragmentManager.findFragmentByTag(firstFragmentTag); - if (fragmentByTag == null) { - continue; - } - fragmentTransaction.detach(fragmentByTag); - } - } - fragmentTransaction.addToBackStack(firstFragmentTag) - .setReorderingAllowed(true) - .commit(); - } - selectedItemTag = newlySelectedItemTag; - isOnFirstFragment = selectedItemTag.equals(firstFragmentTag); - selectedNavController.setValue(selectedFragment.getNavController()); - return true; - } - return false; - }); - setupItemReselected(bottomNavigationView, graphIdToTagMap, fragmentManager); - setupDeepLinks(bottomNavigationView, navGraphIds, fragmentManager, containerId, intent); - final int finalFirstFragmentGraphId = firstFragmentGraphId; - fragmentManager.addOnBackStackChangedListener(() -> { - if (!isOnFirstFragment) { - if (firstFragmentTag == null) { - return; - } - if (!isOnBackStack(fragmentManager, firstFragmentTag)) { - bottomNavigationView.setSelectedItemId(finalFirstFragmentGraphId); - } - } - - final NavController navController = selectedNavController.getValue(); - if (navController != null && navController.getCurrentDestination() == null) { - final NavGraph navControllerGraph = navController.getGraph(); - navController.navigate(navControllerGraph.getId()); - } - }); - return selectedNavController; - } - - private static NavHostFragment obtainNavHostFragment(final FragmentManager fragmentManager, - final String fragmentTag, - final int navGraphId, - final int containerId) { - final NavHostFragment existingFragment = (NavHostFragment) fragmentManager.findFragmentByTag(fragmentTag); - if (existingFragment != null) { - return existingFragment; - } - final NavHostFragment navHostFragment = NavHostFragmentWithDefaultAnimations.create(navGraphId); - fragmentManager.beginTransaction() - .setReorderingAllowed(true) - .add(containerId, navHostFragment, fragmentTag) - .commitNow(); - return navHostFragment; - } - - private static void attachNavHostFragment(final FragmentManager fragmentManager, - final NavHostFragment navHostFragment, - final boolean isPrimaryNavFragment) { - final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction() - .attach(navHostFragment); - if (isPrimaryNavFragment) { - fragmentTransaction.setPrimaryNavigationFragment(navHostFragment); - } - fragmentTransaction.commitNow(); - } - - private static void detachNavHostFragment(final FragmentManager fragmentManager, final NavHostFragment navHostFragment) { - fragmentManager.beginTransaction() - .detach(navHostFragment) - .commitNow(); - } - - @SuppressLint("RestrictedApi") - private static void setupItemReselected(final BottomNavigationView bottomNavigationView, - final SparseArray graphIdToTagMap, - final FragmentManager fragmentManager) { - bottomNavigationView.setOnItemReselectedListener(item -> { - final String newlySelectedItemTag = graphIdToTagMap.get(item.getItemId()); - final Fragment fragmentByTag = fragmentManager.findFragmentByTag(newlySelectedItemTag); - if (fragmentByTag == null) { - return; - } - final NavHostFragment selectedFragment = (NavHostFragment) fragmentByTag; - final NavController navController = selectedFragment.getNavController(); - final NavGraph navControllerGraph = navController.getGraph(); - final NavDestination currentDestination = navController.getCurrentDestination(); - final int startDestination = navControllerGraph.getStartDestination(); - int backStackSize = navController.getBackStack().size(); - if (currentDestination != null && backStackSize == 2 && currentDestination.getId() == startDestination) { - // scroll to top - final List fragments = selectedFragment.getChildFragmentManager().getFragments(); - if (fragments.isEmpty()) return; - final Fragment fragment = fragments.get(0); - if (fragment instanceof FeedFragment) { - ((FeedFragment) fragment).scrollToTop(); - } - return; - } - final boolean popped = navController.popBackStack(startDestination, false); - backStackSize = navController.getBackStack().size(); - if (!popped || backStackSize > 2) { - try { - // try loop pop - do { - navController.popBackStack(); - backStackSize = navController.getBackStack().size(); - } while (backStackSize > 2); - } catch (Exception e) { - Log.e(TAG, "setupItemReselected: ", e); - } - } - }); - } - - private static void setupDeepLinks(final BottomNavigationView bottomNavigationView, - final List navGraphIds, - final FragmentManager fragmentManager, - final int containerId, - final Intent intent) { - for (int i = 0; i < navGraphIds.size(); i++) { - final int navGraphId = navGraphIds.get(i); - final String fragmentTag = getFragmentTag(navGraphId); - final NavHostFragment navHostFragment = obtainNavHostFragment(fragmentManager, fragmentTag, navGraphId, containerId); - if (navHostFragment.getNavController().handleDeepLink(intent)) { - final int selectedItemId = bottomNavigationView.getSelectedItemId(); - NavController navController = navHostFragment.getNavController(); - NavGraph graph = navController.getGraph(); - if (selectedItemId != graph.getId()) { - navController = navHostFragment.getNavController(); - graph = navController.getGraph(); - bottomNavigationView.setSelectedItemId(graph.getId()); - } - } - } - } - - private static boolean isOnBackStack(final FragmentManager fragmentManager, final String backStackName) { - int backStackCount = fragmentManager.getBackStackEntryCount(); - for (int i = 0; i < backStackCount; i++) { - final FragmentManager.BackStackEntry backStackEntry = fragmentManager.getBackStackEntryAt(i); - final String name = backStackEntry.getName(); - if (name != null && name.equals(backStackName)) { - return true; - } - } - return false; - } - - private static String getFragmentTag(final int index) { - return "bottomNavigation#" + index; - } -} +// package awais.instagrabber.utils; +// +// import android.annotation.SuppressLint; +// import android.content.Intent; +// import android.util.Log; +// import android.util.SparseArray; +// +// import androidx.annotation.NonNull; +// import androidx.fragment.app.Fragment; +// import androidx.fragment.app.FragmentManager; +// import androidx.fragment.app.FragmentTransaction; +// import androidx.lifecycle.LiveData; +// import androidx.lifecycle.MutableLiveData; +// import androidx.navigation.NavController; +// import androidx.navigation.NavDestination; +// import androidx.navigation.NavGraph; +// import androidx.navigation.fragment.NavHostFragment; +// +// import com.google.android.material.bottomnavigation.BottomNavigationView; +// +// import java.util.List; +// +// import awais.instagrabber.R; +// import awais.instagrabber.customviews.NavHostFragmentWithDefaultAnimations; +// import awais.instagrabber.fragments.main.FeedFragment; +// +// /** +// * This is a Java rewrite of NavigationExtensions +// * from architecture-components-samples. Some modifications have been done, check git history. +// */ +// public class NavigationExtensions { +// private static final String TAG = NavigationExtensions.class.getSimpleName(); +// private static String selectedItemTag; +// private static boolean isOnFirstFragment; +// +// @NonNull +// public static LiveData setupWithNavController(@NonNull final BottomNavigationView bottomNavigationView, +// @NonNull List navGraphIds, +// @NonNull final FragmentManager fragmentManager, +// final int containerId, +// @NonNull Intent intent, +// final int firstFragmentGraphIndex) { +// final SparseArray graphIdToTagMap = new SparseArray<>(); +// final MutableLiveData selectedNavController = new MutableLiveData<>(); +// int firstFragmentGraphId = 0; +// for (int i = 0; i < navGraphIds.size(); i++) { +// final int navGraphId = navGraphIds.get(i); +// final String fragmentTag = getFragmentTag(navGraphId); +// final NavHostFragment navHostFragment = obtainNavHostFragment(fragmentManager, fragmentTag, navGraphId, containerId); +// final NavController navController = navHostFragment.getNavController(); +// final int graphId = navController.getGraph().getId(); +// if (i == firstFragmentGraphIndex) { +// firstFragmentGraphId = graphId; +// } +// graphIdToTagMap.put(graphId, fragmentTag); +// if (bottomNavigationView.getSelectedItemId() == graphId) { +// selectedNavController.setValue(navHostFragment.getNavController()); +// attachNavHostFragment(fragmentManager, navHostFragment, i == firstFragmentGraphIndex); +// } else { +// detachNavHostFragment(fragmentManager, navHostFragment); +// } +// } +// selectedItemTag = graphIdToTagMap.get(bottomNavigationView.getSelectedItemId()); +// final String firstFragmentTag = graphIdToTagMap.get(firstFragmentGraphId); +// isOnFirstFragment = selectedItemTag != null && selectedItemTag.equals(firstFragmentTag); +// bottomNavigationView.setOnItemSelectedListener(item -> { +// if (fragmentManager.isStateSaved()) { +// return false; +// } +// String newlySelectedItemTag = graphIdToTagMap.get(item.getItemId()); +// String tag = selectedItemTag; +// if (tag != null && !tag.equals(newlySelectedItemTag)) { +// fragmentManager.popBackStack(firstFragmentTag, FragmentManager.POP_BACK_STACK_INCLUSIVE); +// Fragment fragment = fragmentManager.findFragmentByTag(newlySelectedItemTag); +// if (fragment == null) { +// return false; +// // throw new RuntimeException("null cannot be cast to non-null NavHostFragment"); +// } +// final NavHostFragment selectedFragment = (NavHostFragment) fragment; +// if (firstFragmentTag != null && !firstFragmentTag.equals(newlySelectedItemTag)) { +// FragmentTransaction fragmentTransaction = fragmentManager +// .beginTransaction() +// .setCustomAnimations( +// R.anim.nav_default_enter_anim, +// R.anim.nav_default_exit_anim, +// R.anim.nav_default_pop_enter_anim, +// R.anim.nav_default_pop_exit_anim +// ) +// .attach(selectedFragment) +// .setPrimaryNavigationFragment(selectedFragment); +// for (int i = 0; i < graphIdToTagMap.size(); i++) { +// final int key = graphIdToTagMap.keyAt(i); +// final String fragmentTagForId = graphIdToTagMap.get(key); +// if (!fragmentTagForId.equals(newlySelectedItemTag)) { +// final Fragment fragmentByTag = fragmentManager.findFragmentByTag(firstFragmentTag); +// if (fragmentByTag == null) { +// continue; +// } +// fragmentTransaction.detach(fragmentByTag); +// } +// } +// fragmentTransaction.addToBackStack(firstFragmentTag) +// .setReorderingAllowed(true) +// .commit(); +// } +// selectedItemTag = newlySelectedItemTag; +// isOnFirstFragment = selectedItemTag.equals(firstFragmentTag); +// selectedNavController.setValue(selectedFragment.getNavController()); +// return true; +// } +// return false; +// }); +// setupItemReselected(bottomNavigationView, graphIdToTagMap, fragmentManager); +// setupDeepLinks(bottomNavigationView, navGraphIds, fragmentManager, containerId, intent); +// final int finalFirstFragmentGraphId = firstFragmentGraphId; +// fragmentManager.addOnBackStackChangedListener(() -> { +// if (!isOnFirstFragment) { +// if (firstFragmentTag == null) { +// return; +// } +// if (!isOnBackStack(fragmentManager, firstFragmentTag)) { +// bottomNavigationView.setSelectedItemId(finalFirstFragmentGraphId); +// } +// } +// +// final NavController navController = selectedNavController.getValue(); +// if (navController != null && navController.getCurrentDestination() == null) { +// final NavGraph navControllerGraph = navController.getGraph(); +// navController.navigate(navControllerGraph.getId()); +// } +// }); +// return selectedNavController; +// } +// +// private static NavHostFragment obtainNavHostFragment(final FragmentManager fragmentManager, +// final String fragmentTag, +// final int navGraphId, +// final int containerId) { +// final NavHostFragment existingFragment = (NavHostFragment) fragmentManager.findFragmentByTag(fragmentTag); +// if (existingFragment != null) { +// return existingFragment; +// } +// final NavHostFragment navHostFragment = NavHostFragmentWithDefaultAnimations.create(navGraphId); +// fragmentManager.beginTransaction() +// .setReorderingAllowed(true) +// .add(containerId, navHostFragment, fragmentTag) +// .commitNow(); +// return navHostFragment; +// } +// +// private static void attachNavHostFragment(final FragmentManager fragmentManager, +// final NavHostFragment navHostFragment, +// final boolean isPrimaryNavFragment) { +// final FragmentTransaction fragmentTransaction = fragmentManager.beginTransaction() +// .attach(navHostFragment); +// if (isPrimaryNavFragment) { +// fragmentTransaction.setPrimaryNavigationFragment(navHostFragment); +// } +// fragmentTransaction.commitNow(); +// } +// +// private static void detachNavHostFragment(final FragmentManager fragmentManager, final NavHostFragment navHostFragment) { +// fragmentManager.beginTransaction() +// .detach(navHostFragment) +// .commitNow(); +// } +// +// @SuppressLint("RestrictedApi") +// private static void setupItemReselected(final BottomNavigationView bottomNavigationView, +// final SparseArray graphIdToTagMap, +// final FragmentManager fragmentManager) { +// bottomNavigationView.setOnItemReselectedListener(item -> { +// final String newlySelectedItemTag = graphIdToTagMap.get(item.getItemId()); +// final Fragment fragmentByTag = fragmentManager.findFragmentByTag(newlySelectedItemTag); +// if (fragmentByTag == null) { +// return; +// } +// final NavHostFragment selectedFragment = (NavHostFragment) fragmentByTag; +// final NavController navController = selectedFragment.getNavController(); +// final NavGraph navControllerGraph = navController.getGraph(); +// final NavDestination currentDestination = navController.getCurrentDestination(); +// final int startDestination = navControllerGraph.getStartDestination(); +// int backStackSize = navController.getBackStack().size(); +// if (currentDestination != null && backStackSize == 2 && currentDestination.getId() == startDestination) { +// // scroll to top +// final List fragments = selectedFragment.getChildFragmentManager().getFragments(); +// if (fragments.isEmpty()) return; +// final Fragment fragment = fragments.get(0); +// if (fragment instanceof FeedFragment) { +// ((FeedFragment) fragment).scrollToTop(); +// } +// return; +// } +// final boolean popped = navController.popBackStack(startDestination, false); +// backStackSize = navController.getBackStack().size(); +// if (!popped || backStackSize > 2) { +// try { +// // try loop pop +// do { +// navController.popBackStack(); +// backStackSize = navController.getBackStack().size(); +// } while (backStackSize > 2); +// } catch (Exception e) { +// Log.e(TAG, "setupItemReselected: ", e); +// } +// } +// }); +// } +// +// private static void setupDeepLinks(final BottomNavigationView bottomNavigationView, +// final List navGraphIds, +// final FragmentManager fragmentManager, +// final int containerId, +// final Intent intent) { +// for (int i = 0; i < navGraphIds.size(); i++) { +// final int navGraphId = navGraphIds.get(i); +// final String fragmentTag = getFragmentTag(navGraphId); +// final NavHostFragment navHostFragment = obtainNavHostFragment(fragmentManager, fragmentTag, navGraphId, containerId); +// if (navHostFragment.getNavController().handleDeepLink(intent)) { +// final int selectedItemId = bottomNavigationView.getSelectedItemId(); +// NavController navController = navHostFragment.getNavController(); +// NavGraph graph = navController.getGraph(); +// if (selectedItemId != graph.getId()) { +// navController = navHostFragment.getNavController(); +// graph = navController.getGraph(); +// bottomNavigationView.setSelectedItemId(graph.getId()); +// } +// } +// } +// } +// +// private static boolean isOnBackStack(final FragmentManager fragmentManager, final String backStackName) { +// int backStackCount = fragmentManager.getBackStackEntryCount(); +// for (int i = 0; i < backStackCount; i++) { +// final FragmentManager.BackStackEntry backStackEntry = fragmentManager.getBackStackEntryAt(i); +// final String name = backStackEntry.getName(); +// if (name != null && name.equals(backStackName)) { +// return true; +// } +// } +// return false; +// } +// +// private static String getFragmentTag(final int index) { +// return "bottomNavigation#" + index; +// } +// } diff --git a/app/src/main/java/awais/instagrabber/utils/NavigationHelper.kt b/app/src/main/java/awais/instagrabber/utils/NavigationHelper.kt new file mode 100644 index 00000000..0f474bf6 --- /dev/null +++ b/app/src/main/java/awais/instagrabber/utils/NavigationHelper.kt @@ -0,0 +1,174 @@ +package awais.instagrabber.utils + +import android.content.Context +import android.content.res.Resources +import androidx.annotation.ArrayRes +import awais.instagrabber.R +import awais.instagrabber.fragments.settings.PreferenceKeys +import awais.instagrabber.models.Tab + +var tabOrderString: String? = null + +private val NON_REMOVABLE_NAV_ROOT_IDS: List = listOf(R.id.profile_nav_graph, R.id.more_nav_graph) + + +fun getLoggedInNavTabs(context: Context): Pair, List> { + val navRootIds = getArrayResIds(context.resources, R.array.logged_in_nav_root_ids) + return getTabs(context, navRootIds) +} + +fun getAnonNavTabs(context: Context): List { + val navRootIds = getArrayResIds(context.resources, R.array.anon_nav_root_ids) + val (tabs, _) = getTabs(context, navRootIds, true) + return tabs +} + +private fun getTabs( + context: Context, + navRootIds: IntArray, + isAnon: Boolean = false, +): Pair, MutableList> { + val navGraphNames = getResIdsForNavRootIds(navRootIds, ::getNavGraphNameForNavRootId) + val navGraphResIds = getResIdsForNavRootIds(navRootIds, ::getNavGraphResIdForNavRootId) + val titleArray = getResIdsForNavRootIds(navRootIds, ::getTitleResIdForNavRootId) + val iconIds = getResIdsForNavRootIds(navRootIds, ::getIconResIdForNavRootId) + val startDestFragIds = getResIdsForNavRootIds(navRootIds, ::getStartDestFragIdForNavRootId) + val (orderedGraphNames, orderedNavRootIds) = if (isAnon) navGraphNames to navRootIds.toList() else getOrderedNavRootIdsFromPref(navGraphNames) + val tabs = mutableListOf() + val otherTabs = mutableListOf() // Will contain tabs not in current list + for (i in navRootIds.indices) { + val navRootId = navRootIds[i] + val tab = Tab( + iconIds[i], + context.getString(titleArray[i]), + if (isAnon) false else !NON_REMOVABLE_NAV_ROOT_IDS.contains(navRootId), + navGraphResIds[i], + navRootId, + startDestFragIds[i] + ) + if (!isAnon && !orderedGraphNames.contains(navGraphNames[i])) { + otherTabs.add(tab) + continue + } + tabs.add(tab) + } + val associateBy = tabs.associateBy { it.navigationRootId } + val orderedTabs = orderedNavRootIds.mapNotNull { associateBy[it] } + return orderedTabs to otherTabs +} + +private fun getArrayResIds(resources: Resources, @ArrayRes arrayRes: Int): IntArray { + val typedArray = resources.obtainTypedArray(arrayRes) + val length = typedArray.length() + val navRootIds = IntArray(length) + for (i in 0 until length) { + val resourceId = typedArray.getResourceId(i, 0) + if (resourceId == 0) continue + navRootIds[i] = resourceId + } + typedArray.recycle() + return navRootIds +} + +private fun getResIdsForNavRootIds(navRootIds: IntArray, resMapper: Function1): List = navRootIds + .asSequence() + .filterNot { it == 0 } + .map(resMapper) + .filterNot { it == 0 } + .toList() + +private fun getTitleResIdForNavRootId(id: Int): Int = when (id) { + R.id.direct_messages_nav_graph -> R.string.title_dm + R.id.feed_nav_graph -> R.string.feed + R.id.profile_nav_graph -> R.string.profile + R.id.discover_nav_graph -> R.string.title_discover + R.id.more_nav_graph -> R.string.more + R.id.favorites_nav_graph -> R.string.title_favorites + R.id.notification_viewer_nav_graph -> R.string.title_notifications + else -> 0 +} + +private fun getIconResIdForNavRootId(id: Int): Int = when (id) { + R.id.direct_messages_nav_graph -> R.drawable.ic_message_24 + R.id.feed_nav_graph -> R.drawable.ic_home_24 + R.id.profile_nav_graph -> R.drawable.ic_person_24 + R.id.discover_nav_graph -> R.drawable.ic_explore_24 + R.id.more_nav_graph -> R.drawable.ic_more_horiz_24 + R.id.favorites_nav_graph -> R.drawable.ic_star_24 + R.id.notification_viewer_nav_graph -> R.drawable.ic_not_liked + else -> 0 +} + +private fun getStartDestFragIdForNavRootId(id: Int): Int = when (id) { + R.id.direct_messages_nav_graph -> R.id.directMessagesInboxFragment + R.id.feed_nav_graph -> R.id.feedFragment + R.id.profile_nav_graph -> R.id.profileFragment + R.id.discover_nav_graph -> R.id.discoverFragment + R.id.more_nav_graph -> R.id.morePreferencesFragment + R.id.favorites_nav_graph -> R.id.favoritesFragment + R.id.notification_viewer_nav_graph -> R.id.notificationsViewer + else -> 0 +} + +fun getNavGraphNameForNavRootId(id: Int): String = when (id) { + R.id.direct_messages_nav_graph -> "direct_messages_nav_graph" + R.id.feed_nav_graph -> "feed_nav_graph" + R.id.profile_nav_graph -> "profile_nav_graph" + R.id.discover_nav_graph -> "discover_nav_graph" + R.id.more_nav_graph -> "more_nav_graph" + R.id.favorites_nav_graph -> "favorites_nav_graph" + R.id.notification_viewer_nav_graph -> "notification_viewer_nav_graph" + else -> "" +} + +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 + "feed_nav_graph" -> R.id.feed_nav_graph + "profile_nav_graph" -> R.id.profile_nav_graph + "discover_nav_graph" -> R.id.discover_nav_graph + "more_nav_graph" -> R.id.more_nav_graph + "favorites_nav_graph" -> R.id.favorites_nav_graph + "notification_viewer_nav_graph" -> R.id.notification_viewer_nav_graph + else -> 0 +} + +private fun getOrderedNavRootIdsFromPref(navGraphNames: List): Pair, List> { + tabOrderString = Utils.settingsHelper.getString(PreferenceKeys.PREF_TAB_ORDER) + if (tabOrderString.isNullOrBlank()) { + // Use top 5 entries for default list + val top5navGraphNames: List = navGraphNames.subList(0, 5) + val newOrderString = top5navGraphNames.joinToString(",") + Utils.settingsHelper.putString(PreferenceKeys.PREF_TAB_ORDER, newOrderString) + tabOrderString = newOrderString + return top5navGraphNames to top5navGraphNames.map(::getNavRootIdForGraphName) + } + 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 + val orderGraphNames = orderString + .split(",") + .asSequence() + .filter(String::isNotBlank) + .filter(navGraphNames::contains) + .toList() + val graphNames = if (orderGraphNames.isEmpty()) { + // Use top 5 entries for default list + navGraphNames.subList(0, 5) + } else orderGraphNames + return graphNames to graphNames.map(::getNavRootIdForGraphName) +} + +fun isNavRootInCurrentTabs(navRootString: String?): Boolean { + val navRoot = navRootString ?: return false + return tabOrderString?.contains(navRoot) ?: false +} \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.kt b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.kt index fdcd40b5..e59e21ca 100755 --- a/app/src/main/java/awais/instagrabber/utils/SettingsHelper.kt +++ b/app/src/main/java/awais/instagrabber/utils/SettingsHelper.kt @@ -37,7 +37,9 @@ class SettingsHelper(context: Context) { } private fun getStringDefault(@StringSettings key: String): String { - if (PreferenceKeys.DATE_TIME_FORMAT == key) return "hh:mm:ss a 'on' dd-MM-yyyy" + if (PreferenceKeys.DATE_TIME_FORMAT == key) { + return Constants.defaultDateTimeFormat + } return if (PreferenceKeys.DATE_TIME_SELECTION == key) "0;3;0" else "" } diff --git a/app/src/main/java/awais/instagrabber/utils/TextUtils.kt b/app/src/main/java/awais/instagrabber/utils/TextUtils.kt index b2a28f75..a2134179 100644 --- a/app/src/main/java/awais/instagrabber/utils/TextUtils.kt +++ b/app/src/main/java/awais/instagrabber/utils/TextUtils.kt @@ -11,11 +11,11 @@ import java.util.* import kotlin.math.absoluteValue object TextUtils { - lateinit var datetimeParser: DateTimeFormatter + var dateTimeFormatter: DateTimeFormatter = DateTimeFormatter.ofPattern(Constants.defaultDateTimeFormat) @JvmStatic fun isEmpty(charSequence: CharSequence?): Boolean { - if (charSequence == null || charSequence.length < 1) return true + if (charSequence.isNullOrBlank()) return true if (charSequence is String) { var str = charSequence if ("" == str || "null" == str || str.isEmpty()) return true @@ -78,7 +78,8 @@ object TextUtils { @JvmStatic fun setFormatter(datetimeParser: DateTimeFormatter) { - this.datetimeParser = datetimeParser + if (!DateUtils.checkFormatterValid(datetimeParser)) return + this.dateTimeFormatter = datetimeParser } @JvmStatic @@ -86,11 +87,11 @@ object TextUtils { return LocalDateTime.ofInstant( Instant.ofEpochSecond(epochSecond), ZoneId.systemDefault() - ).format(datetimeParser) + ).format(dateTimeFormatter) } @JvmStatic fun nowToString(): String { - return LocalDateTime.now().format(datetimeParser) + return LocalDateTime.now().format(dateTimeFormatter) } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/utils/Utils.java b/app/src/main/java/awais/instagrabber/utils/Utils.java index 499f8c1e..0d5cda1d 100644 --- a/app/src/main/java/awais/instagrabber/utils/Utils.java +++ b/app/src/main/java/awais/instagrabber/utils/Utils.java @@ -8,7 +8,6 @@ import android.content.ContentResolver; import android.content.Context; import android.content.Intent; import android.content.res.Resources; -import android.content.res.TypedArray; import android.graphics.Color; import android.graphics.Point; import android.graphics.Rect; @@ -46,8 +45,6 @@ import androidx.vectordrawable.graphics.drawable.AnimatedVectorDrawableCompat; import com.google.android.exoplayer2.database.ExoDatabaseProvider; import com.google.android.exoplayer2.upstream.cache.LeastRecentlyUsedCacheEvictor; import com.google.android.exoplayer2.upstream.cache.SimpleCache; -import com.google.common.collect.ImmutableList; -import com.google.common.collect.Ordering; import org.json.JSONObject; @@ -55,22 +52,15 @@ import java.io.File; import java.lang.reflect.Field; import java.lang.reflect.InvocationTargetException; import java.lang.reflect.Method; -import java.util.ArrayList; -import java.util.Arrays; import java.util.Collections; import java.util.HashMap; import java.util.List; import java.util.Map; -import java.util.stream.Collectors; import awais.instagrabber.R; -import awais.instagrabber.fragments.settings.PreferenceKeys; import awais.instagrabber.models.PostsLayoutPreferences; -import awais.instagrabber.models.Tab; import awais.instagrabber.models.enums.FavoriteType; -import static awais.instagrabber.fragments.settings.PreferenceKeys.PREF_BARINSTA_DIR_URI; - public final class Utils { private static final String TAG = "Utils"; private static final int VIDEO_CACHE_MAX_BYTES = 10 * 1024 * 1024; @@ -85,7 +75,6 @@ public final class Utils { private static int statusBarHeight; private static int actionBarHeight; public static String cacheDir; - public static String tabOrderString; private static int defaultStatusBarColor; private static Object[] volumes; @@ -421,115 +410,6 @@ public final class Utils { } } - private static final List NON_REMOVABLE_NAV_ROOT_IDS = ImmutableList.of(R.id.profile_nav_graph, R.id.more_nav_graph); - - @NonNull - public static Pair, List> getNavTabList(@NonNull final Context context) { - final Resources resources = context.getResources(); - final String[] titleArray = resources.getStringArray(R.array.main_nav_titles); - - TypedArray typedArray = resources.obtainTypedArray(R.array.main_nav_graphs); - int length = typedArray.length(); - final String[] navGraphNames = new String[length]; - final int[] navigationResIds = new int[length]; - for (int i = 0; i < length; i++) { - final int resourceId = typedArray.getResourceId(i, 0); - if (resourceId == 0) continue; - navigationResIds[i] = resourceId; - navGraphNames[i] = resources.getResourceEntryName(resourceId); - } - typedArray.recycle(); - - typedArray = resources.obtainTypedArray(R.array.main_nav_graph_root_ids); - length = typedArray.length(); - final int[] navRootIds = new int[length]; - for (int i = 0; i < length; i++) { - final int resourceId = typedArray.getResourceId(i, 0); - if (resourceId == 0) continue; - navRootIds[i] = resourceId; - } - typedArray.recycle(); - - typedArray = resources.obtainTypedArray(R.array.main_nav_drawables); - length = typedArray.length(); - final int[] iconIds = new int[length]; - for (int i = 0; i < length; i++) { - final int resourceId = typedArray.getResourceId(i, 0); - if (resourceId == 0) continue; - iconIds[i] = resourceId; - } - typedArray.recycle(); - - typedArray = resources.obtainTypedArray(R.array.main_nav_start_dest_frag_ids); - length = typedArray.length(); - final int[] startDestFragIds = new int[length]; - for (int i = 0; i < length; i++) { - final int resourceId = typedArray.getResourceId(i, 0); - if (resourceId == 0) continue; - startDestFragIds[i] = resourceId; - } - typedArray.recycle(); - - final List currentOrderGraphNames = getCurrentOrderOfGraphNamesFromPref(navGraphNames); - - if (titleArray.length != iconIds.length || titleArray.length != navGraphNames.length) { - throw new RuntimeException(String.format("Array lengths don't match!: titleArray%s, navGraphNames: %s, iconIds: %s", - Arrays.toString(titleArray), Arrays.toString(navGraphNames), Arrays.toString(iconIds))); - } - final List tabs = new ArrayList<>(); - final List otherTabs = new ArrayList<>(); // Will contain tabs not in current list - for (int i = 0; i < length; i++) { - final String navGraphName = navGraphNames[i]; - final int navRootId = navRootIds[i]; - final Tab tab = new Tab(iconIds[i], - titleArray[i], - !NON_REMOVABLE_NAV_ROOT_IDS.contains(navRootId), - navGraphName, - navigationResIds[i], - navRootId, - startDestFragIds[i]); - if (!currentOrderGraphNames.contains(navGraphName)) { - otherTabs.add(tab); - continue; - } - tabs.add(tab); - } - Collections.sort(tabs, Ordering.explicit(currentOrderGraphNames).onResultOf(tab -> { - if (tab == null) return null; - return tab.getGraphName(); - })); - return new Pair<>(tabs, otherTabs); - } - - @NonNull - private static List getCurrentOrderOfGraphNamesFromPref(@NonNull final String[] navGraphNames) { - tabOrderString = settingsHelper.getString(PreferenceKeys.PREF_TAB_ORDER); - final List navGraphNameList = Arrays.asList(navGraphNames); - if (TextUtils.isEmpty(tabOrderString)) { - // Use top 5 entries for default list - final List top5navGraphNames = navGraphNameList.subList(0, 5); - final String newOrderString = android.text.TextUtils.join(",", top5navGraphNames); - Utils.settingsHelper.putString(PreferenceKeys.PREF_TAB_ORDER, newOrderString); - tabOrderString = newOrderString; - return top5navGraphNames; - } - // Make sure that the list from preference does not contain any invalid values - final List orderGraphNames = Arrays.stream(tabOrderString.split(",")) - .filter(s -> !TextUtils.isEmpty(s)) - .filter(navGraphNameList::contains) - .collect(Collectors.toList()); - if (orderGraphNames.isEmpty()) { - // Use top 5 entries for default list - return navGraphNameList.subList(0, 5); - } - return orderGraphNames; - } - - public static boolean isNavRootInCurrentTabs(final String navRootString) { - if (navRootString == null || tabOrderString == null) return false; - return tabOrderString.contains(navRootString); - } - // public static void scanDocumentFile(@NonNull final Context context, // @NonNull final DocumentFile documentFile, // @NonNull final OnScanCompletedListener callback) { diff --git a/app/src/main/java/awais/instagrabber/viewmodels/FollowViewModel.kt b/app/src/main/java/awais/instagrabber/viewmodels/FollowViewModel.kt index f2a0a428..a0a89b72 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/FollowViewModel.kt +++ b/app/src/main/java/awais/instagrabber/viewmodels/FollowViewModel.kt @@ -1,10 +1,6 @@ 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.repositories.responses.User import awais.instagrabber.webservices.FriendshipRepository diff --git a/app/src/main/java/awais/instagrabber/viewmodels/MediaViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/MediaViewModel.java index dac55056..7cffed65 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/MediaViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/MediaViewModel.java @@ -1,19 +1,108 @@ package awais.instagrabber.viewmodels; +import android.util.Log; + +import androidx.annotation.NonNull; +import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; import androidx.lifecycle.ViewModel; +import androidx.lifecycle.ViewModelProvider; +import java.util.ArrayList; 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.utils.KeywordsFilterUtilsKt; + +import static awais.instagrabber.utils.Utils.settingsHelper; public class MediaViewModel extends ViewModel { - private MutableLiveData> list; + private static final String TAG = MediaViewModel.class.getSimpleName(); - public MutableLiveData> getList() { - if (list == null) { - list = new MutableLiveData<>(); + private boolean refresh = true; + + private final PostFetcher postFetcher; + private final MutableLiveData> list = new MutableLiveData<>(); + + public MediaViewModel(@NonNull final PostFetcher.PostFetchService postFetchService) { + final FetchListener> fetchListener = new FetchListener>() { + @Override + public void onResult(final List 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 filterResult(final List result, final boolean isRefresh) { + final List models = list.getValue(); + final List modelsCopy = models == null || isRefresh ? new ArrayList<>() : new ArrayList<>(models); + if (settingsHelper.getBoolean(PreferenceKeys.TOGGLE_KEYWORD_FILTER)) { + final List keywords = new ArrayList<>(settingsHelper.getStringSet(PreferenceKeys.KEYWORD_FILTERS)); + final List filter = KeywordsFilterUtilsKt.filter(keywords, result); + if (filter != null) { + modelsCopy.addAll(filter); + } + return modelsCopy; } + modelsCopy.addAll(result); + return modelsCopy; + } + + public LiveData> getList() { 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 create(@NonNull final Class modelClass) { + //noinspection unchecked + return (T) new MediaViewModel(postFetchService); + } + } } \ No newline at end of file diff --git a/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt b/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt index 5cc85b29..b3a75279 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/ProfileFragmentViewModel.kt +++ b/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.directmessages.RankedRecipient import awais.instagrabber.repositories.responses.stories.Story -import awais.instagrabber.utils.Constants import awais.instagrabber.utils.ControlledRunner import awais.instagrabber.utils.Event -import awais.instagrabber.utils.getCsrfTokenFromCookie import awais.instagrabber.utils.SingleRunner -import awais.instagrabber.utils.Utils import awais.instagrabber.utils.extensions.TAG import awais.instagrabber.utils.extensions.isReallyPrivate import awais.instagrabber.viewmodels.ProfileFragmentViewModel.ProfileAction.* @@ -29,30 +26,31 @@ import awais.instagrabber.viewmodels.ProfileFragmentViewModel.ProfileEvent.* import awais.instagrabber.webservices.* import kotlinx.coroutines.CoroutineDispatcher import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import java.time.LocalDateTime class ProfileFragmentViewModel( 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?, ioDispatcher: CoroutineDispatcher, ) : 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.loading(null)) private val _isFavorite = MutableLiveData(false) private val profileAction = MutableLiveData(INIT) private val _eventLiveData = MutableLiveData?>() + private var previousUsername: String? = null + enum class ProfileAction { INIT, REFRESH, @@ -102,8 +100,9 @@ class ProfileFragmentViewModel( val profile: LiveData> = currentUserStateUsernameActionLiveData.switchMap { val (currentUserResource, stateUsernameResource, action) = it liveData>(context = viewModelScope.coroutineContext + ioDispatcher) { + if (action == INIT && previousUsername != null && stateUsernameResource.data == previousUsername) return@liveData if (currentUserResource.status == Resource.Status.LOADING || stateUsernameResource.status == Resource.Status.LOADING) { - emit(Resource.loading(null)) + emit(Resource.loading(profileCopy.value?.data)) return@liveData } val currentUser = currentUserResource.data @@ -115,6 +114,7 @@ class ProfileFragmentViewModel( try { when (action) { INIT, REFRESH -> { + previousUsername = stateUsername val fetchedUser = profileFetchControlledRunner.cancelPreviousThenRun { fetchUser(currentUser, stateUsername) } emit(Resource.success(fetchedUser)) if (fetchedUser != null) { @@ -243,7 +243,7 @@ class ProfileFragmentViewModel( private suspend fun checkAndUpdateFavorite(fetchedUser: User) { try { - val favorite = favoriteRepository!!.getFavorite(fetchedUser.username, FavoriteType.USER) + val favorite = favoriteRepository.getFavorite(fetchedUser.username, FavoriteType.USER) if (favorite == null) { _isFavorite.postValue(false) return @@ -291,7 +291,7 @@ class ProfileFragmentViewModel( viewModelScope.launch(Dispatchers.IO) { toggleFavoriteControlledRunner.afterPrevious { try { - val favorite = favoriteRepository!!.getFavorite(username, FavoriteType.USER) + val favorite = favoriteRepository.getFavorite(username, FavoriteType.USER) if (favorite == null) { // insert favoriteRepository.insertOrUpdateFavorite( @@ -326,7 +326,7 @@ class ProfileFragmentViewModel( val currentUserId = currentUser.value?.data?.pk ?: return@afterPrevious val targetUserId = profile.value?.data?.pk ?: return@afterPrevious val csrfToken = csrfToken ?: return@afterPrevious - val deviceUuid = deviceUuid + val deviceUuid = deviceUuid ?: return@afterPrevious if (following) { if (!confirmed) { _eventLiveData.postValue(Event(ShowConfirmUnfollowDialog)) @@ -365,7 +365,7 @@ class ProfileFragmentViewModel( val currentUserId = currentUser.value?.data?.pk ?: return@afterPrevious val targetUserId = profile.value?.data?.pk ?: return@afterPrevious val csrfToken = csrfToken ?: return@afterPrevious - val deviceUuid = deviceUuid + val deviceUuid = deviceUuid ?: return@afterPrevious val username = profile.value?.data?.username ?: return@afterPrevious val thread = directMessagesRepository.createThread( csrfToken, @@ -381,6 +381,7 @@ class ProfileFragmentViewModel( } val threadId = thread.threadId ?: return@afterPrevious _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) { Log.e(TAG, "sendDm: ", e) } finally { @@ -399,7 +400,7 @@ class ProfileFragmentViewModel( val profile = profile.value?.data ?: return@afterPrevious friendshipRepository.toggleRestrict( csrfToken ?: return@afterPrevious, - deviceUuid, + deviceUuid ?: return@afterPrevious, profile.pk, !(profile.friendshipStatus?.isRestricted ?: false), ) @@ -421,7 +422,7 @@ class ProfileFragmentViewModel( friendshipRepository.changeBlock( csrfToken ?: return@afterPrevious, currentUser.value?.data?.pk ?: return@afterPrevious, - deviceUuid, + deviceUuid ?: return@afterPrevious, profile.friendshipStatus?.blocking ?: return@afterPrevious, profile.pk ) @@ -443,7 +444,7 @@ class ProfileFragmentViewModel( friendshipRepository.changeMute( csrfToken ?: return@afterPrevious, currentUser.value?.data?.pk ?: return@afterPrevious, - deviceUuid, + deviceUuid ?: return@afterPrevious, profile.friendshipStatus?.isMutingReel ?: return@afterPrevious, profile.pk, true @@ -466,7 +467,7 @@ class ProfileFragmentViewModel( friendshipRepository.changeMute( csrfToken ?: return@afterPrevious, currentUser.value?.data?.pk ?: return@afterPrevious, - deviceUuid, + deviceUuid ?: return@afterPrevious, profile.friendshipStatus?.muting ?: return@afterPrevious, profile.pk, false @@ -488,7 +489,7 @@ class ProfileFragmentViewModel( friendshipRepository.removeFollower( csrfToken ?: return@afterPrevious, currentUser.value?.data?.pk ?: return@afterPrevious, - deviceUuid, + deviceUuid ?: return@afterPrevious, profile.value?.data?.pk ?: return@afterPrevious ) profileAction.postValue(REFRESH_FRIENDSHIP) @@ -597,7 +598,15 @@ class ProfileFragmentViewModel( @Suppress("UNCHECKED_CAST") 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?, owner: SavedStateRegistryOwner, defaultArgs: Bundle? = null, @@ -609,7 +618,15 @@ class ProfileFragmentViewModelFactory( ): T { return ProfileFragmentViewModel( handle, + csrfToken, + deviceUuid, + userRepository, + friendshipRepository, + storiesRepository, + mediaRepository, + graphQLRepository, favoriteRepository, + directMessagesRepository, messageManager, Dispatchers.IO, ) as T diff --git a/app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt b/app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt index 00717239..ca4f3c6f 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt +++ b/app/src/main/java/awais/instagrabber/viewmodels/StoryFragmentViewModel.kt @@ -7,19 +7,22 @@ import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import awais.instagrabber.R 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.Companion.error import awais.instagrabber.models.Resource.Companion.loading import awais.instagrabber.models.Resource.Companion.success 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.responses.Media import awais.instagrabber.repositories.responses.directmessages.RankedRecipient 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.StoriesRepository import com.google.common.collect.ImmutableList diff --git a/app/src/main/java/awais/instagrabber/viewmodels/UserSearchViewModel.java b/app/src/main/java/awais/instagrabber/viewmodels/UserSearchViewModel.java index c0ecd0b8..c9bac545 100644 --- a/app/src/main/java/awais/instagrabber/viewmodels/UserSearchViewModel.java +++ b/app/src/main/java/awais/instagrabber/viewmodels/UserSearchViewModel.java @@ -21,7 +21,7 @@ import java.util.Set; import java.util.stream.Collectors; import awais.instagrabber.R; -import awais.instagrabber.fragments.UserSearchFragment; +import awais.instagrabber.fragments.UserSearchMode; import awais.instagrabber.models.Resource; import awais.instagrabber.repositories.responses.User; import awais.instagrabber.repositories.responses.directmessages.RankedRecipient; @@ -49,7 +49,7 @@ public class UserSearchViewModel extends ViewModel { private Call searchRequest; private long[] hideUserIds; private String[] hideThreadIds; - private UserSearchFragment.SearchMode searchMode; + private UserSearchMode searchMode; private boolean showGroups; private boolean waitingForCache; private boolean showCachedResults; @@ -192,7 +192,7 @@ public class UserSearchViewModel extends ViewModel { private void rankedRecipientSearch() { directMessagesRepository.rankedRecipients( - searchMode.getName(), + searchMode.getMode(), showGroups, currentQuery, CoroutineUtilsKt.getContinuation((response, throwable) -> { @@ -290,7 +290,7 @@ public class UserSearchViewModel extends ViewModel { return showAction; } - public void setSearchMode(final UserSearchFragment.SearchMode searchMode) { + public void setSearchMode(final UserSearchMode searchMode) { this.searchMode = searchMode; } diff --git a/app/src/main/java/thoughtbot/expandableadapter/ExpandableGroup.java b/app/src/main/java/thoughtbot/expandableadapter/ExpandableGroup.java index f1163550..495f2db7 100755 --- a/app/src/main/java/thoughtbot/expandableadapter/ExpandableGroup.java +++ b/app/src/main/java/thoughtbot/expandableadapter/ExpandableGroup.java @@ -1,6 +1,5 @@ package thoughtbot.expandableadapter; -import java.util.ArrayList; import java.util.List; import awais.instagrabber.repositories.responses.User; diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml index 9d1e880a..a56989a4 100755 --- a/app/src/main/res/layout/activity_main.xml +++ b/app/src/main/res/layout/activity_main.xml @@ -60,9 +60,11 @@ @@ -71,5 +73,6 @@ android:layout_width="match_parent" android:layout_height="wrap_content" android:layout_gravity="bottom" - app:labelVisibilityMode="auto" /> + app:labelVisibilityMode="auto" + tools:menu="@menu/bottom_nav_menu" /> \ No newline at end of file diff --git a/app/src/main/res/layout/dialog_post_layout_preferences.xml b/app/src/main/res/layout/dialog_post_layout_preferences.xml index b53b0f78..57e1f201 100644 --- a/app/src/main/res/layout/dialog_post_layout_preferences.xml +++ b/app/src/main/res/layout/dialog_post_layout_preferences.xml @@ -23,8 +23,8 @@ android:id="@+id/label_layout" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginRight="6dp" android:layout_marginTop="4dp" + android:layout_marginRight="6dp" android:layout_marginBottom="4dp" android:gravity="right" android:text="@string/layout_style" @@ -74,14 +74,14 @@ android:id="@+id/label_col_count" android:layout_width="0dp" android:layout_height="wrap_content" - android:layout_marginRight="6dp" android:layout_marginTop="4dp" + android:layout_marginRight="6dp" android:layout_marginBottom="4dp" android:gravity="right" android:text="@string/column_count" - app:layout_constraintStart_toStartOf="parent" app:layout_constraintBottom_toBottomOf="@id/col_count_toggle" app:layout_constraintEnd_toStartOf="@id/guideline" + app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="@id/col_count_toggle" /> - - - - - + android:visibility="gone" + app:counterEnabled="false" + app:counterMaxLength="50" + app:endIconDrawable="@drawable/ic_outline_info_24" + app:endIconMode="custom" + app:hintEnabled="false" + tools:visibility="visible"> - + android:autofillHints="no" + android:inputType="text" + android:maxLength="50" + android:padding="16dp" + tools:text="test" /> - - + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/comments_nav_graph.xml b/app/src/main/res/navigation/comments_nav_graph.xml deleted file mode 100644 index 0cec9091..00000000 --- a/app/src/main/res/navigation/comments_nav_graph.xml +++ /dev/null @@ -1,78 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/navigation/direct_messages_nav_graph.xml b/app/src/main/res/navigation/direct_messages_nav_graph.xml index 547ea1ad..62d459f3 100644 --- a/app/src/main/res/navigation/direct_messages_nav_graph.xml +++ b/app/src/main/res/navigation/direct_messages_nav_graph.xml @@ -5,149 +5,487 @@ android:id="@+id/direct_messages_nav_graph" app:startDestination="@id/directMessagesInboxFragment"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - - - + + - + tools:layout="@layout/fragment_user_search"> + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/discover_nav_graph.xml b/app/src/main/res/navigation/discover_nav_graph.xml index d63d7a7c..cd2c844e 100644 --- a/app/src/main/res/navigation/discover_nav_graph.xml +++ b/app/src/main/res/navigation/discover_nav_graph.xml @@ -5,131 +5,352 @@ android:id="@+id/discover_nav_graph" app:startDestination="@id/discoverFragment"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -141,15 +362,157 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + - + tools:layout="@layout/fragment_user_search"> + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/favorites_nav_graph.xml b/app/src/main/res/navigation/favorites_nav_graph.xml index 63ae746e..32ee328f 100644 --- a/app/src/main/res/navigation/favorites_nav_graph.xml +++ b/app/src/main/res/navigation/favorites_nav_graph.xml @@ -5,66 +5,480 @@ android:id="@+id/favorites_nav_graph" app:startDestination="@id/favoritesFragment"> - - - - - - - - - - - - - - - - - - - - - - - - - + tools:layout="@layout/fragment_favorites"> + + + + + + + + + android:id="@+id/storyViewerFragment" + android:name="awais.instagrabber.fragments.StoryViewerFragment" + android:label="StoryViewerFragment" + tools:layout="@layout/fragment_story_viewer"> + + + + + + + + + + + + + + + android:label="@string/post" + tools:layout="@layout/dialog_post_view"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/feed_nav_graph.xml b/app/src/main/res/navigation/feed_nav_graph.xml index ddd05514..5eaa30ca 100644 --- a/app/src/main/res/navigation/feed_nav_graph.xml +++ b/app/src/main/res/navigation/feed_nav_graph.xml @@ -5,154 +5,516 @@ android:id="@+id/feed_nav_graph" app:startDestination="@id/feedFragment"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + - + + tools:layout="@layout/dialog_post_view"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/hashtag_nav_graph.xml b/app/src/main/res/navigation/hashtag_nav_graph.xml deleted file mode 100644 index c237271b..00000000 --- a/app/src/main/res/navigation/hashtag_nav_graph.xml +++ /dev/null @@ -1,129 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/navigation/likes_nav_graph.xml b/app/src/main/res/navigation/likes_nav_graph.xml deleted file mode 100644 index 0157f71a..00000000 --- a/app/src/main/res/navigation/likes_nav_graph.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/navigation/location_nav_graph.xml b/app/src/main/res/navigation/location_nav_graph.xml deleted file mode 100644 index f8a33957..00000000 --- a/app/src/main/res/navigation/location_nav_graph.xml +++ /dev/null @@ -1,128 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/navigation/more_nav_graph.xml b/app/src/main/res/navigation/more_nav_graph.xml index f4e99e1e..61d6f0ef 100644 --- a/app/src/main/res/navigation/more_nav_graph.xml +++ b/app/src/main/res/navigation/more_nav_graph.xml @@ -1,205 +1,539 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + android:id="@+id/action_to_settings" + app:destination="@id/settings_nav_graph" /> + + + android:id="@+id/action_to_favorites" + app:destination="@id/favorites_non_top" /> + + + + + + + android:id="@+id/storyViewerFragment" + android:name="awais.instagrabber.fragments.StoryViewerFragment" + android:label="StoryViewerFragment" + tools:layout="@layout/fragment_story_viewer"> + + + + android:id="@+id/action_to_post" + app:destination="@id/postViewFragment" /> + + android:id="@+id/action_to_profile" + app:destination="@id/profile_non_top" /> + + android:id="@+id/action_to_hashtag" + app:destination="@id/hashTagFragment" /> + + android:id="@+id/action_to_location" + app:destination="@id/locationFragment" /> + - - - + android:id="@+id/action_to_user_search" + app:destination="@id/user_search" /> - - - - - - - - - - - + + android:label="@string/post" + tools:layout="@layout/dialog_post_view"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/notification_viewer_nav_graph.xml b/app/src/main/res/navigation/notification_viewer_nav_graph.xml index e814058e..edd216d6 100644 --- a/app/src/main/res/navigation/notification_viewer_nav_graph.xml +++ b/app/src/main/res/navigation/notification_viewer_nav_graph.xml @@ -5,121 +5,491 @@ android:id="@+id/notification_viewer_nav_graph" app:startDestination="@id/notificationsViewer"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + app:nullable="false" /> + + + + + + + + + + + + + + + + + + + + + + + tools:layout="@layout/dialog_post_view"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/profile_nav_graph.xml b/app/src/main/res/navigation/profile_nav_graph.xml index 8147a1d6..c71167e7 100644 --- a/app/src/main/res/navigation/profile_nav_graph.xml +++ b/app/src/main/res/navigation/profile_nav_graph.xml @@ -5,218 +5,563 @@ android:id="@+id/profile_nav_graph" app:startDestination="@id/profileFragment"> - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + - - - - - - - - - - + android:id="@+id/action_to_post" + app:destination="@id/postViewFragment" /> + - - + android:id="@+id/action_to_comments" + app:destination="@id/commentsViewerFragment" /> + + + + + + + + + + + + + + + + + + + + + + + + + + + + tools:layout="@layout/fragment_search"> + + + + + + + + + + + tools:layout="@layout/dialog_post_view"> + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/root_nav_graph.xml b/app/src/main/res/navigation/root_nav_graph.xml new file mode 100644 index 00000000..39619106 --- /dev/null +++ b/app/src/main/res/navigation/root_nav_graph.xml @@ -0,0 +1,12 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/saved_nav_graph.xml b/app/src/main/res/navigation/saved_nav_graph.xml deleted file mode 100644 index dba1cb54..00000000 --- a/app/src/main/res/navigation/saved_nav_graph.xml +++ /dev/null @@ -1,119 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/navigation/settings_nav_graph.xml b/app/src/main/res/navigation/settings_nav_graph.xml new file mode 100644 index 00000000..caed28fa --- /dev/null +++ b/app/src/main/res/navigation/settings_nav_graph.xml @@ -0,0 +1,80 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/navigation/story_list_nav_graph.xml b/app/src/main/res/navigation/story_list_nav_graph.xml deleted file mode 100644 index f2f3895d..00000000 --- a/app/src/main/res/navigation/story_list_nav_graph.xml +++ /dev/null @@ -1,48 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/navigation/user_search_nav_graph.xml b/app/src/main/res/navigation/user_search_nav_graph.xml deleted file mode 100644 index e8667732..00000000 --- a/app/src/main/res/navigation/user_search_nav_graph.xml +++ /dev/null @@ -1,80 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/app/src/main/res/values/arrays.xml b/app/src/main/res/values/arrays.xml index 7b1723ae..d70b4757 100755 --- a/app/src/main/res/values/arrays.xml +++ b/app/src/main/res/values/arrays.xml @@ -91,61 +91,23 @@ HH:mm:ss H:mm:ss - + + @id/direct_messages_nav_graph @id/feed_nav_graph @id/profile_nav_graph @id/discover_nav_graph @id/more_nav_graph - @id/favorites_nav_graph @id/notification_viewer_nav_graph - - - @navigation/direct_messages_nav_graph - @navigation/feed_nav_graph - @navigation/profile_nav_graph - @navigation/discover_nav_graph - @navigation/more_nav_graph - @navigation/favorites_nav_graph - @navigation/notification_viewer_nav_graph + + + @id/favorites_nav_graph + @id/profile_nav_graph + @id/more_nav_graph - - - @string/title_dm - @string/feed - @string/profile - @string/title_discover - @string/more - @string/title_favorites - @string/title_notifications - - - - @drawable/ic_message_24 - @drawable/ic_home_24 - @drawable/ic_person_24 - @drawable/ic_explore_24 - @drawable/ic_more_horiz_24 - @drawable/ic_star_24 - @drawable/ic_not_liked - - - - - @id/directMessagesInboxFragment - @id/feedFragment - @id/profileFragment - @id/discoverFragment - @id/morePreferencesFragment - @id/favoritesFragment - @id/notificationsViewer - - - - - + @string/light_white_theme @string/light_barinsta_theme diff --git a/app/src/main/res/values/ids.xml b/app/src/main/res/values/ids.xml index ab11fbe3..66f1464c 100644 --- a/app/src/main/res/values/ids.xml +++ b/app/src/main/res/values/ids.xml @@ -8,6 +8,9 @@ + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ece231cb..79b4a834 100755 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -520,4 +520,7 @@ Share via DM Share linkā€¦ Slide to Cancel + Disable screen transitions + Invalid format + No activity found to select directory diff --git a/app/src/test/java/awais/instagrabber/common/Adapters.kt b/app/src/test/java/awais/instagrabber/common/Adapters.kt index 5735e342..f0892232 100644 --- a/app/src/test/java/awais/instagrabber/common/Adapters.kt +++ b/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.responses.* 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 { override suspend fun getUserInfo(uid: Long): WrappedUser { @@ -47,7 +45,7 @@ open class FriendshipServiceAdapter : FriendshipService { } open class StoriesServiceAdapter : StoriesService { - override suspend fun fetch(mediaId: Long): String { + override suspend fun fetch(mediaId: Long): StoryMediaResponse { TODO("Not yet implemented") } @@ -63,11 +61,19 @@ open class StoriesServiceAdapter : StoriesService { TODO("Not yet implemented") } - override suspend fun getUserStory(url: String): String { + override suspend fun getReelsMedia(id: String): ReelsMediaResponse { TODO("Not yet implemented") } - override suspend fun respondToSticker(storyId: String, stickerId: String, action: String, form: Map): 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): StoryStickerResponse { TODO("Not yet implemented") } diff --git a/app/src/test/java/awais/instagrabber/viewmodels/ProfileFragmentViewModelTest.kt b/app/src/test/java/awais/instagrabber/viewmodels/ProfileFragmentViewModelTest.kt index cb3344d8..3977eee9 100644 --- a/app/src/test/java/awais/instagrabber/viewmodels/ProfileFragmentViewModelTest.kt +++ b/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.User import awais.instagrabber.repositories.responses.stories.Story -import awais.instagrabber.repositories.responses.stories.StoryMedia import awais.instagrabber.webservices.* import kotlinx.coroutines.ExperimentalCoroutinesApi import org.json.JSONException @@ -320,13 +319,13 @@ internal class ProfileFragmentViewModelTest { "username" to testPublicUser.username ) ) - val testUserStories = listOf(StoryMedia()) + val testUserStories = Story() val testUserHighlights = listOf(Story()) val userRepository = object : UserRepository(UserServiceAdapter()) { override suspend fun getUsernameInfo(username: String): User = testPublicUser } val storiesRepository = object : StoriesRepository(StoriesServiceAdapter()) { - override suspend fun getStories(options: StoryViewerOptions): List = testUserStories + override suspend fun getStories(options: StoryViewerOptions): Story = testUserStories override suspend fun fetchHighlights(profileId: Long): List = testUserHighlights } val viewModel = ProfileFragmentViewModel( diff --git a/build.gradle b/build.gradle index c4c69bbd..b0e1280f 100755 --- a/build.gradle +++ b/build.gradle @@ -1,5 +1,8 @@ buildscript { - ext.kotlin_version = '1.5.20' + ext{ + kotlin_version = '1.5.20' + nav_version = "2.4.0-alpha04" + } repositories { google() @@ -9,7 +12,6 @@ buildscript { dependencies { classpath 'com.android.tools.build:gradle:4.2.2' classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version" - def nav_version = "2.3.5" classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version" } } diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg new file mode 100644 index 00000000..547d124c Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/1.jpg differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg new file mode 100644 index 00000000..547d124c Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/2.jpg differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg new file mode 100644 index 00000000..547d124c Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/3.jpg differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg new file mode 100644 index 00000000..547d124c Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/4.jpg differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/5.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.jpg new file mode 100644 index 00000000..547d124c Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/5.jpg differ diff --git a/fastlane/metadata/android/en-US/images/phoneScreenshots/6.jpg b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.jpg new file mode 100644 index 00000000..547d124c Binary files /dev/null and b/fastlane/metadata/android/en-US/images/phoneScreenshots/6.jpg differ