How to check, from the tabs root, if current tab can go back - ionic2

TLDR:
I have the ref of my tabs
#ViewChild('tabsPage') tabRef: Tabs;
I need to get the nav stack of the current tab, check if it can go back and do go back if needed.
Full story:
I have a tabs page which I arrive from another page with a custom animation.
The behavior of my tabs when the user presses the hardware back button must be as follow:
If the user is in the tab 0, go back with custom animation;
If the user isn't in the tab 0, go to tab 0;
If the user has navigated in the current tab, go back in the nav stack of that tab.
Trying to achieve that behavior, I used this code in my tab root page, inside of ionViewDidEnter():
if (this.tabRef.getSelected().index == 0) {
this.navCtrl.pop({
animation: 'nav-shrink'
, direction: 'back'
})
} else {
this.tabRef.select(0);
}
The problem is: when I have navigated in another tab, the back button won't go back in the tab (from the detail to the list, for instance), it will go back to the tab 0 at once.
ionViewWillLeave() isn't called when I navigate further in any tab, so i can't deregister the backButtonAction.
The solution would be to check if the current tab can go back, so, instead of going to tab 0 or going back from the tabs page with a custom animation, I would just nav.pop() the current tab.

https://ionicframework.com/docs/v2/api/navigation/NavController/
canGoBack()
Returns true if there’s a valid previous page that we can pop back to. Otherwise returns false.

Apparently, the Tab itself is the NavController. So I solved my problem this way:
if (this.tabRef.getSelected().canGoBack()) {
this.tabRef.getSelected().pop();
} else if (this.tabRef.getSelected().index == 0) {
this.navCtrl.pop({
animation: 'nav-shrink'
, direction: 'back'
})
} else {
this.tabRef.select(0);
}

Related

How to check if element is available on a page, if not, go back and click on next element, if expected element is there then click on that element

I have a task where home page contains twenty ad titles (elements).
I want to click on an ad tile that opens the detail page which is working.
On detail page I have an element for CHAT button but that is not visible on every detail page. (reason: some user do not enable CHAT option on their ads)
Here's what I want to achieve,
If Chat element is found, then click on Chat element and come out of loop
If chat element is not found, then go back and click on next ad tile and repeat until detail page with chat is opened
Here's what I have tried but it is not working:
it('should send chats', () => {
const adTiles = 20;
cy.loginWithApi() // custom command that logs in user and land hompage
for (let i = 0; i < adTiles; i++) { //loop to iterate between ad tiles
cy.get('._459428ad').eq(i).click(); // click on ad tile on homepage
cy.wait(5000) //custom wait to load detail page properly
if (cy.get('._5fd7b300.f3d05709').should('exist')){
//if chat btn is these then click on chat
cy.get('._5fd7b300.f3d05709').click()
}
else{
cy.go('back') // else go back to home page if chat not enabled
}
}
});
If the element is not found in IF condition it does not go to else part to go back on home page
I want:
If the "IF" part is failing, i.e, if chat element is not found then it should go back and click on next ad tile on home page.
You can try something like this:
it('should send chats', () => {
const adTiles = 20;
cy.loginWithApi() // custom command that logs in user and land hompage
for (let i = 0; i < adTiles; i++) { //loop to iterate between ad tiles
cy.get('._459428ad').eq(i).click(); // click on ad tile on homepage
cy.wait(5000) //custom wait to load detail page properly
if (cy.get('._5fd7b300.f3d05709').should('exist')){
//if chat btn is there then click on chat
cy.get('._5fd7b300.f3d05709').click()
**// add a go back command here if chat is opened, to return to ad detail page**
}
**// go back as a general command without else command**
cy.go('back') // else go back to home page if chat not enabled
}
});
consider your scenario like this:
if there is a chat button then a click should be performed on chat and do the required operation and then return back to detail page
and after that going back to main page should be a general step

How can I change the animation between tab switches with a bottomNavigationView?

I have just updated my code to use the latest 2.4.0-alpha05 for the navigation component and I have custom navigation between the multiple stacks and my main nav graph is like the documentation I found.
<navigation xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
app:startDestination="#+id/accounts"
android:id="#+id/bottom_nav">
<inclue app:graph="#navigation/accounts_tab_nav"/>
<include app:graph="#navigation/contact_tab_nav" />
<include app:graph="#navigation/profile_tab_nav" />
</navigation>
Most of my stacks animate with a slide from right to left. It looks like that when I am on the second screen, in let's say the profile screen, and then switch to the first tab it triggers the popEnter en popExitAnim that are defined in the action that leads to the second screen in the profile tab. Like so:
<fragment
android:id="#+id/profileMain"
android:name="com.app.ProfileFragment"
tools:layout="#layout/fragment_profile">
<action
android:id="#+id/action_profileMain_to_secondFragment"
app:destination="#id/secondFragment"
app:enterAnim="#anim/slide_in_right"
app:exitAnim="#anim/slide_out_left"
app:popEnterAnim="#anim/slide_in_left"
app:popExitAnim="#anim/slide_out_right" />
</fragment>
But obviously I want tho use the (default) fade animation when switching tabs. So how should I do that?
And I would like to pop to the root of the stack when reselecting a tab. But I probably have to do that myself?
I came up with a solution that seems to work for me, but I have to admit it feels a little bit hacky.
I have a public flag in my MainActivity:
var tabWasSelected = false
Then I remember if a tab item was selected in setOnItemSelectedListener
// always show selected Bottom Navigation item as selected (return true)
bottomNavigationView.setOnItemSelectedListener { item ->
// In order to get the expected behavior, you have to call default Navigation method manually
NavigationUI.onNavDestinationSelected(item, navController)
// set flag so that the fragment can call the correct animation on tab change in onCreateAnimation
tabWasSelected = true
return#setOnItemSelectedListener true
}
This will always select the item and navigate to the associated destination while maintaining multiple back stacks.
I then have created a Fragment class which all my other fragments inherit from. It simply overrides onCreateAnimation to set the correct animation. Here is what it looks like:
open class MyFragment: Fragment() {
override fun onCreateAnimation(transit: Int, enter: Boolean, nextAnim: Int): Animation? {
val activity = requireActivity()
if (activity is MainActivity) {
if (activity.tabWasSelected) {
if (enter) {
//flow is exit first, then enter, so we have to reset the flag on enter
//in order that following animations will run as defined in the nav graph
activity.tabWasSelected = false
return AnimationUtils.loadAnimation(requireContext(), R.anim.nav_default_pop_enter_anim)
} else {
return AnimationUtils.loadAnimation(requireContext(), R.anim.nav_default_pop_exit_anim)
}
}
}
//no tab was selected, so run the defined animation
return super.onCreateAnimation(transit, enter, nextAnim)
}
}
Instead of using setupWithNavController function, follow this way.
First, create your NavOptions which include animation shown below.
val options = NavOptions.Builder()
.setLaunchSingleTop(true)
.setEnterAnim(R.anim.enter_from_bottom)
.setExitAnim(R.anim.exit_to_top)
.setPopEnterAnim(R.anim.enter_from_top)
.setPopExitAnim(R.anim.exit_to_bottom)
.setPopUpTo(navController.graph.startDestination,false)
.build();
Then use setOnNavigationItemSelectedListener to navigate with animation like that.
bottomNavigationView.setOnNavigationItemSelectedListener
{ item ->
when(item.itemId) {
R.id.fragmentFirst -> {
navController.navigate(R.id.fragmentFirst,null,options)
}
R.id.fragmentSecond -> {
navigate(R.id.fragmentSecond,null,options)
}
R.id.fragmentThird -> {
navController.navigate(R.id.fragmentThird,null,options)
}
}
}
Finally, you should prevent same item selection case so you can add below code.
bottomNavigationView.setOnNavigationItemReselectedListener
{ item ->
return#setOnNavigationItemReselectedListener
}
I used bottomNavigation like that in my project to add animation for page transitions. I hope it helped.

SwiftUI: append menu entry to group "View" after "Enter Full Screen"

I'd like to append a new menu entry right below "Enter Full Screen", but I am failing to find the right CommandGroupPlacement property.
CommandGroup(after: .<what needs to be put here??>) {
//my buttons here
}
Attempting to override "View" results in just another group with the same name (see image).
CommandMenu("View") {
//add button here
}
Shoutout to Majid Jabrayilov for his blogpost on this: https://swiftwithmajid.com/2020/11/24/commands-in-swiftui/
To find a solution to my above issue though I still had to think a bit around the corner–what does work is this:
CommandGroup(before: .toolbar) {
Button("Foo") {
}
}
This works, because the toolbar menu entry is located within "View" (even thought I don't have a toolbar in my app, the placement still works nonetheless...)
You said you were looking for the proper CommandGroupPlacement so for your case (relative to full screen mode), you would technically want CommandGroupPlacement.sidebar. CommandGroupPlacement.toolbar works because toolbar options live in the View menu, though you have none set.
/// Example in a CommandsBuilder
CommandGroup(after: CommandGroupPlacement.sidebar) {
Button("New View Action", action: { print("After sidebar menu options") }).keyboardShortcut("s", modifiers: [.command, .option, .control, .function]
}
This should also come in handy for others. The Apple documentation that corresponds to this: CommandGroupPlacement

Ionic 2/3 :Dynamically set RootPage

I have a scenario where I have 2 profiles (user and admin) and I have a selection page once a person logs in. On the selection page, there are 2 radio buttons for (User and Admin profiles), what I am trying to achieve is, the person can choose only 1 profile (this is achieved by the radio button), now assuming I have saved the selected value, I want to set the rootPage to AdminPage or UserPage, but I don't want to immediately navigate, I just want to update/set the path so that when the person goes back (by pressing the back key or previous button) it will take the person to the desired page. Please let me know your thoughts.
Maby this will put you in the right direction:
https://ionicframework.com/docs/api/navigation/NavController/#insert
do not set adminPage or userPage as rootPage but insert the page on the stack
The trick here is that you want to set the root to a new page after the user tries to go back, the easiest way to achieve this is using the NavController navguards to execute a code before leaving the page, this way it'll check wich page the user has selected and then set the root.
Since the select can be easy impemented following the docs i'll leave that aside. Let's just say you have a property userType that is a string and can be 'user' or 'admin', in your select page you'll do the following:
public canLeave: boolean = false; //this'll controll if the user can leave the page or not
public userType: string = 'user'; // just setting a default value to your select
// this is a navguard
ionViewCanLeave() {
if(!this.canLeave){
if(this.userType == 'user'){
this.canLeave = true; // you'll need to set canLeave to true so when setting the rootpage it doesn't enters the if statemente again
this.navCtrl.setRoot('UserPage');
} else {
this.canLeave = true;
this.navCtrl.setRoot('AdminPage');
}
}
return true;
}
Hope this helps.
I got the solution to the error,
ionViewCanLeave() {
if(!this.canLeave){
if(this.userType == 'user'){
this.canLeave = true; // you'll need to set canLeave to true so when setting the rootpage it doesn't enters the if statemente again
this.navCtrl.setRoot('UserPage'); // <-- this should be UserPage instead of 'UserPage'
} else {
this.canLeave = true;
this.navCtrl.setRoot('AdminPage'); // <-- this should be AdminPage instead of 'AdminPage'
}
}
return true;
}

Interrupt CSS transition by jquery event click and cancel reversing

.trans{
-webkit-transition:background-position 0.2s;
}
I have nav menu items and added the transition above to each of them.
Also JQuery hover functions as:
$(".menuUL li").mouseover(function () {
$(this).css({'background-position':'center top','color':'white'});});
$(".menuUL li").mouseout(function () {
if(this.id == currentTab)
{$(this).css({'background-position':'center top','color':'white'});}
else
{$(this).css({'background-position':'center bottom','color':'#0196e3'});}});
And click function to chance content:
` $(".menuUL li").click(function(){
$(".menuUL li").not(this).css({'background-position':'center bottom','background-color':'#666666','color':'#0196e3'});
$(this).css({'background-position':'center top','background-color':'#CCCCCC','color':'white'});});
`
The problem is when I click on a menu item and suddenly(in <200ms) leave the element(mouseout) before the transition is completed. Reverse transition occurs and the clicked menu element's background position goes back to the initial position.
I need a code like $(this).endtransition()
Could you help pls?