fragments = new ArrayList<>(12);
62 |
63 | fragments.add(RecentsFragment.newInstance(0));
64 | fragments.add(FavoritesFragment.newInstance(0));
65 | fragments.add(NearbyFragment.newInstance(0));
66 | fragments.add(FriendsFragment.newInstance(0));
67 | fragments.add(FoodFragment.newInstance(0));
68 | fragments.add(RecentsFragment.newInstance(0));
69 | fragments.add(FavoritesFragment.newInstance(0));
70 | fragments.add(NearbyFragment.newInstance(0));
71 | fragments.add(FriendsFragment.newInstance(0));
72 | fragments.add(FoodFragment.newInstance(0));
73 | fragments.add(RecentsFragment.newInstance(0));
74 | fragments.add(FavoritesFragment.newInstance(0));
75 |
76 | mNavController =
77 | FragNavController.newBuilder(savedInstanceState, getSupportFragmentManager(), R.id.container)
78 | .rootFragments(fragments)
79 | .defaultTransactionOptions(FragNavTransactionOptions.newBuilder().customAnimations(R.anim.slide_in_from_right, R.anim.slide_out_to_left, R.anim.slide_in_from_left, R.anim.slide_out_to_right).build())
80 | .build();
81 |
82 | }
83 |
84 | @Override
85 | public void onBackPressed() {
86 | DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
87 | if (drawer.isDrawerOpen(GravityCompat.START)) {
88 | drawer.closeDrawer(GravityCompat.START);
89 | } else if (mNavController.getCurrentStack().size() > 1) {
90 | mNavController.popFragment();
91 | } else {
92 | super.onBackPressed();
93 | }
94 | }
95 |
96 | @Override
97 | protected void onSaveInstanceState(Bundle outState) {
98 | super.onSaveInstanceState(outState);
99 | mNavController.onSaveInstanceState(outState);
100 | }
101 |
102 | @Override
103 | public boolean onNavigationItemSelected(MenuItem item) {
104 | switch (item.getItemId()) {
105 | case R.id.bb_menu_recents:
106 | mNavController.switchTab(INDEX_RECENTS);
107 | break;
108 | case R.id.bb_menu_favorites:
109 | mNavController.switchTab(INDEX_FAVORITES);
110 | break;
111 | case R.id.bb_menu_nearby:
112 | mNavController.switchTab(INDEX_NEARBY);
113 | break;
114 | case R.id.bb_menu_friends:
115 | mNavController.switchTab(INDEX_FRIENDS);
116 | break;
117 | case R.id.bb_menu_food:
118 | mNavController.switchTab(INDEX_FOOD);
119 | break;
120 | case R.id.bb_menu_recents2:
121 | mNavController.switchTab(INDEX_RECENTS2);
122 | break;
123 | case R.id.bb_menu_favorites2:
124 | mNavController.switchTab(INDEX_FAVORITES2);
125 | break;
126 | case R.id.bb_menu_nearby2:
127 | mNavController.switchTab(INDEX_NEARBY2);
128 | break;
129 | case R.id.bb_menu_friends2:
130 | mNavController.switchTab(INDEX_FRIENDS2);
131 | break;
132 | case R.id.bb_menu_food2:
133 | mNavController.switchTab(INDEX_FOOD2);
134 | break;
135 | case R.id.bb_menu_recents3:
136 | mNavController.switchTab(INDEX_RECENTS3);
137 | break;
138 | case R.id.bb_menu_favorites3:
139 | mNavController.switchTab(INDEX_FAVORITES3);
140 | break;
141 | }
142 | DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
143 | drawer.closeDrawer(GravityCompat.START);
144 | return true;
145 | }
146 |
147 | @Override
148 | public void pushFragment(Fragment fragment) {
149 | mNavController.pushFragment(fragment);
150 | }
151 | }
152 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ncapdevi/sample/fragments/BaseFragment.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.sample.fragments;
2 |
3 | import android.content.Context;
4 | import android.os.Bundle;
5 | import android.support.annotation.Nullable;
6 | import android.support.v4.app.Fragment;
7 | import android.view.LayoutInflater;
8 | import android.view.View;
9 | import android.view.ViewGroup;
10 | import android.widget.Button;
11 |
12 | import com.ncapdevi.sample.R;
13 |
14 | /**
15 | * Created by niccapdevila on 3/26/16.
16 | */
17 | public class BaseFragment extends Fragment {
18 | public static final String ARGS_INSTANCE = "com.ncapdevi.sample.argsInstance";
19 |
20 | Button btn;
21 | FragmentNavigation mFragmentNavigation;
22 | int mInt = 0;
23 | private View cachedView;
24 |
25 | @Override
26 | public void onCreate(@Nullable Bundle savedInstanceState) {
27 | super.onCreate(savedInstanceState);
28 | Bundle args = getArguments();
29 | if (args != null) {
30 | mInt = args.getInt(ARGS_INSTANCE);
31 | }
32 | }
33 |
34 |
35 | @Override
36 | public View onCreateView(LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
37 | if (cachedView == null) {
38 | cachedView = inflater.inflate(R.layout.fragment_main, container, false);
39 | btn = cachedView.findViewById(R.id.button);
40 | }
41 | return cachedView;
42 | }
43 | @Override
44 | public void onAttach(Context context) {
45 | super.onAttach(context);
46 | if (context instanceof FragmentNavigation) {
47 | mFragmentNavigation = (FragmentNavigation) context;
48 | }
49 | }
50 |
51 | public interface FragmentNavigation {
52 | void pushFragment(Fragment fragment);
53 | }
54 | }
55 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ncapdevi/sample/fragments/FavoritesFragment.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.sample.fragments;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.NonNull;
5 | import android.support.annotation.Nullable;
6 | import android.view.View;
7 |
8 | /**
9 | * Created by niccapdevila on 3/26/16.
10 | */
11 | public class FavoritesFragment extends BaseFragment {
12 |
13 | public static FavoritesFragment newInstance(int instance) {
14 | Bundle args = new Bundle();
15 | args.putInt(ARGS_INSTANCE, instance);
16 | FavoritesFragment fragment = new FavoritesFragment();
17 | fragment.setArguments(args);
18 | return fragment;
19 | }
20 |
21 | @Override
22 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
23 | super.onViewCreated(view, savedInstanceState);
24 | if (btn != null) {
25 | btn.setOnClickListener(new View.OnClickListener() {
26 | @Override
27 | public void onClick(View v) {
28 | if (mFragmentNavigation != null) {
29 | mFragmentNavigation.pushFragment(FavoritesFragment.newInstance(mInt+1));
30 | }
31 | }
32 | });
33 | btn.setText(getClass().getSimpleName() + " " + mInt);
34 | }
35 | }
36 | }
37 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ncapdevi/sample/fragments/FoodFragment.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.sample.fragments;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.NonNull;
5 | import android.support.annotation.Nullable;
6 | import android.view.View;
7 |
8 | /**
9 | * Created by niccapdevila on 3/26/16.
10 | */
11 | public class FoodFragment extends BaseFragment {
12 |
13 | public static FoodFragment newInstance(int instance) {
14 | Bundle args = new Bundle();
15 | args.putInt(ARGS_INSTANCE, instance);
16 | FoodFragment fragment = new FoodFragment();
17 | fragment.setArguments(args);
18 | return fragment;
19 | }
20 |
21 | @Override
22 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
23 | super.onViewCreated(view, savedInstanceState);
24 | btn.setOnClickListener(new View.OnClickListener() {
25 | @Override
26 | public void onClick(View v) {
27 | if (mFragmentNavigation != null) {
28 | mFragmentNavigation.pushFragment(FoodFragment.newInstance(mInt+1));
29 | }
30 | }
31 | });
32 | btn.setText(getClass().getSimpleName() + " " + mInt);
33 | }
34 |
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ncapdevi/sample/fragments/FriendsFragment.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.sample.fragments;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.NonNull;
5 | import android.support.annotation.Nullable;
6 | import android.view.View;
7 |
8 | /**
9 | * Created by niccapdevila on 3/26/16.
10 | */
11 | public class FriendsFragment extends BaseFragment {
12 |
13 | public static FriendsFragment newInstance(int instance) {
14 | Bundle args = new Bundle();
15 | args.putInt(ARGS_INSTANCE, instance);
16 | FriendsFragment fragment = new FriendsFragment();
17 | fragment.setArguments(args);
18 | return fragment;
19 | }
20 |
21 |
22 | @Override
23 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
24 | super.onViewCreated(view, savedInstanceState);
25 | btn.setOnClickListener(new View.OnClickListener() {
26 | @Override
27 | public void onClick(View v) {
28 | if (mFragmentNavigation != null) {
29 | mFragmentNavigation.pushFragment(FriendsFragment.newInstance(mInt + 1));
30 | }
31 | }
32 | });
33 | btn.setText(getClass().getSimpleName() + " " + mInt);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ncapdevi/sample/fragments/NearbyFragment.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.sample.fragments;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.NonNull;
5 | import android.support.annotation.Nullable;
6 | import android.view.View;
7 |
8 | /**
9 | * Created by niccapdevila on 3/26/16.
10 | */
11 | public class NearbyFragment extends BaseFragment {
12 |
13 | public static NearbyFragment newInstance(int instance) {
14 | Bundle args = new Bundle();
15 | args.putInt(ARGS_INSTANCE, instance);
16 | NearbyFragment fragment = new NearbyFragment();
17 | fragment.setArguments(args);
18 | return fragment;
19 | }
20 |
21 | @Override
22 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
23 | super.onViewCreated(view, savedInstanceState);
24 | btn.setOnClickListener(new View.OnClickListener() {
25 | @Override
26 | public void onClick(View v) {
27 | if (mFragmentNavigation != null) {
28 | mFragmentNavigation.pushFragment(NearbyFragment.newInstance(mInt+1));
29 | }
30 | }
31 | });
32 | btn.setText(getClass().getSimpleName() + " " + mInt);
33 | }
34 |
35 | @Override
36 | public void onViewStateRestored(@Nullable Bundle savedInstanceState) {
37 | super.onViewStateRestored(savedInstanceState);
38 | }
39 | }
40 |
--------------------------------------------------------------------------------
/app/src/main/java/com/ncapdevi/sample/fragments/RecentsFragment.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.sample.fragments;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.NonNull;
5 | import android.support.annotation.Nullable;
6 | import android.view.View;
7 |
8 | /**
9 | * Created by niccapdevila on 3/26/16.
10 | */
11 | public class RecentsFragment extends BaseFragment {
12 |
13 | public static RecentsFragment newInstance(int instance) {
14 | Bundle args = new Bundle();
15 | args.putInt(ARGS_INSTANCE, instance);
16 | RecentsFragment fragment = new RecentsFragment();
17 | fragment.setArguments(args);
18 | return fragment;
19 | }
20 |
21 |
22 | @Override
23 | public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
24 | super.onViewCreated(view, savedInstanceState);
25 | btn.setOnClickListener(new View.OnClickListener() {
26 | @Override
27 | public void onClick(View v) {
28 | if (mFragmentNavigation != null) {
29 | mFragmentNavigation.pushFragment(RecentsFragment.newInstance(mInt + 1));
30 | }
31 | }
32 | });
33 | btn.setText(getClass().getSimpleName() + " " + mInt);
34 | }
35 | }
36 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_in_from_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_in_from_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_out_to_left.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/anim/slide_out_to_right.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_favorites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-hdpi/ic_favorites.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_friends.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-hdpi/ic_friends.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_nearby.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-hdpi/ic_nearby.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_recents.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-hdpi/ic_recents.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-hdpi/ic_restaurants.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-hdpi/ic_restaurants.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_favorites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-mdpi/ic_favorites.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_friends.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-mdpi/ic_friends.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_nearby.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-mdpi/ic_nearby.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_recents.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-mdpi/ic_recents.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-mdpi/ic_restaurants.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-mdpi/ic_restaurants.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_favorites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-xhdpi/ic_favorites.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_friends.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-xhdpi/ic_friends.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_nearby.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-xhdpi/ic_nearby.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_recents.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-xhdpi/ic_recents.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xhdpi/ic_restaurants.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-xhdpi/ic_restaurants.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_favorites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-xxhdpi/ic_favorites.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_friends.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-xxhdpi/ic_friends.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_nearby.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-xxhdpi/ic_nearby.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_recents.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-xxhdpi/ic_recents.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxhdpi/ic_restaurants.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-xxhdpi/ic_restaurants.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_favorites.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-xxxhdpi/ic_favorites.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_friends.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-xxxhdpi/ic_friends.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_nearby.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-xxxhdpi/ic_nearby.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_recents.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-xxxhdpi/ic_recents.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable-xxxhdpi/ic_restaurants.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/drawable-xxxhdpi/ic_restaurants.png
--------------------------------------------------------------------------------
/app/src/main/res/drawable/side_nav_bar.xml:
--------------------------------------------------------------------------------
1 |
3 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_bottom_tabs.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
14 |
15 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
9 |
14 |
19 |
20 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/activity_nav_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
11 |
12 |
16 |
17 |
25 |
26 |
27 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/app_bar_nav_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
10 |
11 |
15 |
16 |
22 |
23 |
24 |
25 |
26 |
27 |
30 |
31 |
32 |
33 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/content_nav_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
15 |
16 |
17 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/fragment_main.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
12 |
13 |
17 |
21 |
--------------------------------------------------------------------------------
/app/src/main/res/layout/nav_header_nav_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
13 |
14 |
20 |
21 |
26 |
27 |
28 |
--------------------------------------------------------------------------------
/app/src/main/res/menu/activity_nav_drawer_drawer.xml:
--------------------------------------------------------------------------------
1 |
2 |
57 |
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-hdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/mipmap-hdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-mdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/mipmap-mdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/mipmap-xhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
--------------------------------------------------------------------------------
/app/src/main/res/values-v21/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
8 |
9 |
--------------------------------------------------------------------------------
/app/src/main/res/values-w820dp/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
5 | 64dp
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/colors.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | #3F51B5
4 | #303F9F
5 | #FF4081
6 |
7 |
--------------------------------------------------------------------------------
/app/src/main/res/values/dimens.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 | 16dp
4 | 16dp
5 |
6 | 16dp
7 | 120dp
8 | 16dp
9 |
10 |
--------------------------------------------------------------------------------
/app/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | FragNav
3 | Main2Activity
4 |
5 | Open navigation drawer
6 | Close navigation drawer
7 |
8 | Settings
9 | NavDrawerActivity
10 |
11 |
--------------------------------------------------------------------------------
/app/src/main/res/values/styles.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
10 |
14 |
15 |
16 |
17 |
18 |
--------------------------------------------------------------------------------
/app/src/main/res/xml/menu_bottombar.xml:
--------------------------------------------------------------------------------
1 |
2 |
3 |
8 |
13 |
18 |
23 |
28 |
29 |
--------------------------------------------------------------------------------
/app/src/test/java/com/ncapdevi/sample/AppUnitTests.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.sample;
2 |
3 | /**
4 | * To work on unit tests, switch the Test Artifact in the Build Variants view.
5 | */
6 | public class AppUnitTests {
7 |
8 | //TODO Add Unit Tests
9 | }
--------------------------------------------------------------------------------
/build.gradle:
--------------------------------------------------------------------------------
1 | buildscript {
2 | repositories {
3 | jcenter()
4 | google()
5 |
6 | }
7 | dependencies {
8 | classpath 'com.android.tools.build:gradle:3.0.1'
9 | }
10 | }
11 |
12 | allprojects {
13 | repositories {
14 | jcenter()
15 | google()
16 | }
17 | }
18 |
19 | task clean(type: Delete) {
20 | delete rootProject.buildDir
21 | }
22 |
23 | ext {
24 | // App dependencies
25 |
26 | junitVersion = '4.12'
27 | supportVersion = '27.0.2'
28 | mockitoVersion = '2.13.0'
29 | buildToolsVersion = '27.0.3'
30 | minSdkVersion = 14
31 | targetSdkVersion = 27
32 | compileSdkVersion = 27
33 | }
--------------------------------------------------------------------------------
/frag-nav/.gitignore:
--------------------------------------------------------------------------------
1 | /build
2 |
--------------------------------------------------------------------------------
/frag-nav/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.jfrog.bintray" version "1.8.0"
3 | id "com.github.dcendents.android-maven" version "2.0"
4 | id 'com.github.kt3k.coveralls' version '2.8.2'
5 | }
6 |
7 | apply plugin: 'com.android.library'
8 | apply plugin: 'maven'
9 | apply plugin: 'com.github.kt3k.coveralls'
10 |
11 | ext {
12 | libraryVersionCode = 23
13 | libraryVersionName = '2.4.0'
14 |
15 | //Bintray and Maven
16 | bintrayRepo = 'maven'
17 | bintrayName = 'frag-nav'
18 |
19 | publishedGroupId = 'com.ncapdevi'
20 | libraryName = 'FragNav'
21 | artifact = 'frag-nav'
22 |
23 | libraryDescription = 'A library to help manage multiple fragment stacks'
24 |
25 | siteUrl = 'https://github.com/ncapdevi/FragNav'
26 | gitUrl = 'https://github.com/ncapdevi/FragNav.git'
27 |
28 | developerId = 'ncapdevi'
29 | developerName = 'Nic Capdevila'
30 | developerEmail = 'ncapdevi@gmail.com'
31 |
32 | licenseName = 'The Apache Software License, Version 2.0'
33 | licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
34 | allLicenses = ["Apache-2.0"]
35 | }
36 |
37 |
38 | android {
39 | compileSdkVersion rootProject.ext.compileSdkVersion
40 | buildToolsVersion rootProject.ext.buildToolsVersion
41 |
42 | lintOptions{
43 | abortOnError false
44 | }
45 | defaultConfig {
46 | minSdkVersion rootProject.ext.minSdkVersion
47 | targetSdkVersion rootProject.ext.compileSdkVersion
48 | versionCode libraryVersionCode
49 | versionName libraryVersionName
50 | }
51 | buildTypes {
52 | release {
53 | minifyEnabled false
54 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
55 | }
56 | debug {
57 | testCoverageEnabled true
58 | }
59 | }
60 | }
61 |
62 | dependencies {
63 | implementation "com.android.support:support-fragment:$rootProject.ext.supportVersion"
64 | implementation "com.android.support:support-annotations:$rootProject.ext.supportVersion"
65 | testImplementation "org.mockito:mockito-core:$rootProject.ext.mockitoVersion"
66 | testImplementation "junit:junit:$rootProject.ext.junitVersion"
67 | }
68 |
69 | group = publishedGroupId // Maven Group ID for the artifact
70 |
71 | install {
72 | repositories.mavenInstaller {
73 | // This generates POM.xml with proper parameters
74 | pom {
75 | project {
76 | packaging 'aar'
77 | groupId publishedGroupId
78 | artifactId artifact
79 |
80 | // Add your description here
81 | name libraryName
82 | description libraryDescription
83 | url siteUrl
84 |
85 | // Set your license
86 | licenses {
87 | license {
88 | name licenseName
89 | url licenseUrl
90 | }
91 | }
92 | developers {
93 | developer {
94 | id developerId
95 | name developerName
96 | email developerEmail
97 | }
98 | }
99 | scm {
100 | connection gitUrl
101 | developerConnection gitUrl
102 | url siteUrl
103 |
104 | }
105 | }
106 | }
107 | }
108 | }
109 |
110 |
111 | version = libraryVersionName
112 |
113 | if (project.hasProperty("android")) { // Android libraries
114 | task sourcesJar(type: Jar) {
115 | classifier = 'sources'
116 | from android.sourceSets.main.java.srcDirs
117 | }
118 |
119 | task javadoc(type: Javadoc) {
120 | source = android.sourceSets.main.java.srcDirs
121 | classpath += project.files(android.getBootClasspath().join(File.pathSeparator))
122 | }
123 | } else { // Java libraries
124 | task sourcesJar(type: Jar, dependsOn: classes) {
125 | classifier = 'sources'
126 | from sourceSets.main.allSource
127 | }
128 | }
129 |
130 | task javadocJar(type: Jar, dependsOn: javadoc) {
131 | classifier = 'javadoc'
132 | from javadoc.destinationDir
133 | }
134 |
135 | artifacts {
136 | archives javadocJar
137 | archives sourcesJar
138 | }
139 |
140 | // Bintray
141 | bintray {
142 | Properties properties = new Properties()
143 | if (project.rootProject.file('local.properties').exists()) {
144 | properties.load(project.rootProject.file('local.properties').newDataInputStream())
145 |
146 | user = properties.getProperty("bintray.user")
147 | key = properties.getProperty("bintray.apikey")
148 |
149 | configurations = ['archives']
150 | pkg {
151 | repo = bintrayRepo
152 | name = bintrayName
153 | desc = libraryDescription
154 | websiteUrl = siteUrl
155 | vcsUrl = gitUrl
156 | licenses = allLicenses
157 | publish = true
158 | publicDownloadNumbers = true
159 | version {
160 | desc = libraryDescription
161 | }
162 | }
163 | }
164 | }
165 |
166 | coveralls {
167 | jacocoReportPath = "${buildDir}/reports/coverage/debug/report.xml"
168 | }
169 |
170 | tasks.coveralls {
171 | dependsOn 'connectedAndroidTest'
172 | onlyIf { System.env.'CI' }
173 | }
174 | task createPom {
175 | pom {
176 | project {
177 | packaging 'aar'
178 |
179 | name project.name
180 | description libraryDescription
181 | url siteUrl
182 | inceptionYear '2016'
183 |
184 | licenses {
185 | license {
186 | name 'The Apache Software License, Version 2.0'
187 | url 'http://www.apache.org/licenses/LICENSE-2.0.txt'
188 | }
189 | }
190 | scm {
191 | connection gitUrl
192 | developerConnection gitUrl
193 | url siteUrl
194 | }
195 | developers {
196 | developer {
197 | id 'ncapdevi'
198 | name 'Nic Capdevila'
199 | email 'ncapdevi@gmail.com'
200 | }
201 | }
202 | }
203 | }.writeTo("$buildDir/poms/pom-default.xml").writeTo("pom.xml")
204 | }
205 | build.dependsOn createPom
--------------------------------------------------------------------------------
/frag-nav/pom.xml:
--------------------------------------------------------------------------------
1 |
2 |
4 | 4.0.0
5 | com.ncapdevi
6 | frag-nav
7 | 2.4.0
8 | aar
9 | frag-nav
10 | A library to help manage multiple fragment stacks
11 | https://github.com/ncapdevi/FragNav
12 | 2016
13 |
14 |
15 | The Apache Software License, Version 2.0
16 | http://www.apache.org/licenses/LICENSE-2.0.txt
17 |
18 |
19 |
20 |
21 | ncapdevi
22 | Nic Capdevila
23 | ncapdevi@gmail.com
24 |
25 |
26 |
27 | https://github.com/ncapdevi/FragNav.git
28 | https://github.com/ncapdevi/FragNav.git
29 | https://github.com/ncapdevi/FragNav
30 |
31 |
32 |
--------------------------------------------------------------------------------
/frag-nav/proguard-rules.pro:
--------------------------------------------------------------------------------
1 | # Add project specific ProGuard rules here.
2 | # By default, the flags in this file are appended to flags specified
3 | # in /Users/niccapdevila/.android-sdk/tools/proguard/proguard-android.txt
4 | # You can edit the include path and order by changing the proguardFiles
5 | # directive in build.gradle.
6 | #
7 | # For more details, see
8 | # http://developer.android.com/guide/developing/tools/proguard.html
9 |
10 | # Add any project specific keep options here:
11 |
12 | # If your project uses WebView with JS, uncomment the following
13 | # and specify the fully qualified class name to the JavaScript interface
14 | # class:
15 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
16 | # public *;
17 | #}
18 |
--------------------------------------------------------------------------------
/frag-nav/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
--------------------------------------------------------------------------------
/frag-nav/src/main/java/com/ncapdevi/fragnav/FragNavController.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.fragnav;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.CheckResult;
5 | import android.support.annotation.IdRes;
6 | import android.support.annotation.IntDef;
7 | import android.support.annotation.NonNull;
8 | import android.support.annotation.Nullable;
9 | import android.support.v4.app.DialogFragment;
10 | import android.support.v4.app.Fragment;
11 | import android.support.v4.app.FragmentManager;
12 | import android.support.v4.app.FragmentTransaction;
13 | import android.support.v4.util.Pair;
14 | import android.view.View;
15 |
16 | import com.ncapdevi.fragnav.tabhistory.CurrentTabHistoryController;
17 | import com.ncapdevi.fragnav.tabhistory.FragNavTabHistoryController;
18 | import com.ncapdevi.fragnav.tabhistory.UniqueTabHistoryController;
19 | import com.ncapdevi.fragnav.tabhistory.UnlimitedTabHistoryController;
20 |
21 | import org.json.JSONArray;
22 |
23 | import java.lang.annotation.Retention;
24 | import java.lang.annotation.RetentionPolicy;
25 | import java.util.ArrayList;
26 | import java.util.List;
27 | import java.util.Stack;
28 |
29 | import static com.ncapdevi.fragnav.tabhistory.FragNavTabHistoryController.CURRENT_TAB;
30 | import static com.ncapdevi.fragnav.tabhistory.FragNavTabHistoryController.UNIQUE_TAB_HISTORY;
31 | import static com.ncapdevi.fragnav.tabhistory.FragNavTabHistoryController.UNLIMITED_TAB_HISTORY;
32 |
33 | /**
34 | * The class is used to manage navigation through multiple stacks of fragments, as well as coordinate
35 | * fragments that may appear on screen
36 | *
37 | * https://github.com/ncapdevi/FragNav
38 | * Nic Capdevila
39 | * Nic.Capdevila@gmail.com
40 | *
41 | * Originally Created March 2016
42 | */
43 | @SuppressWarnings("RestrictedApi")
44 | public class FragNavController {
45 | // Declare the constants. A maximum of 5 tabs is recommended for bottom navigation, this is per Material Design's Bottom Navigation's design spec.
46 | public static final int NO_TAB = -1;
47 | public static final int TAB1 = 0;
48 | public static final int TAB2 = 1;
49 | public static final int TAB3 = 2;
50 | public static final int TAB4 = 3;
51 | public static final int TAB5 = 4;
52 | public static final int TAB6 = 5;
53 | public static final int TAB7 = 6;
54 | public static final int TAB8 = 7;
55 | public static final int TAB9 = 8;
56 | public static final int TAB10 = 9;
57 | public static final int TAB11 = 10;
58 | public static final int TAB12 = 11;
59 | public static final int TAB13 = 12;
60 | public static final int TAB14 = 13;
61 | public static final int TAB15 = 14;
62 | public static final int TAB16 = 15;
63 | public static final int TAB17 = 16;
64 | public static final int TAB18 = 17;
65 | public static final int TAB19 = 18;
66 | public static final int TAB20 = 19;
67 |
68 | private static final int MAX_NUM_TABS = 20;
69 |
70 | // Extras used to store savedInstanceState
71 | private static final String EXTRA_TAG_COUNT = FragNavController.class.getName() + ":EXTRA_TAG_COUNT";
72 | private static final String EXTRA_SELECTED_TAB_INDEX = FragNavController.class.getName() + ":EXTRA_SELECTED_TAB_INDEX";
73 | private static final String EXTRA_CURRENT_FRAGMENT = FragNavController.class.getName() + ":EXTRA_CURRENT_FRAGMENT";
74 | private static final String EXTRA_FRAGMENT_STACK = FragNavController.class.getName() + ":EXTRA_FRAGMENT_STACK";
75 |
76 | @IdRes
77 | private final int mContainerId;
78 | @NonNull
79 | private final List> mFragmentStacks;
80 | @NonNull
81 | private final FragmentManager mFragmentManager;
82 | private final FragNavTransactionOptions mDefaultTransactionOptions;
83 | @TabIndex
84 | private int mSelectedTabIndex;
85 | private int mTagCount;
86 | @Nullable
87 | private Fragment mCurrentFrag;
88 | @Nullable
89 | private DialogFragment mCurrentDialogFrag;
90 | @Nullable
91 | private RootFragmentListener mRootFragmentListener;
92 | @Nullable
93 | private TransactionListener mTransactionListener;
94 | private boolean mExecutingTransaction;
95 | private FragNavTabHistoryController mFragNavTabHistoryController;
96 | @FragNavTabHistoryController.PopStrategy
97 | private final int mPopStrategy;
98 | private final FragNavLogger mFragNavLogger;
99 |
100 | //region Construction and setup
101 |
102 | private FragNavController(Builder builder, @Nullable Bundle savedInstanceState) {
103 | mFragmentManager = builder.mFragmentManager;
104 | mContainerId = builder.mContainerId;
105 | mFragmentStacks = new ArrayList<>(builder.mNumberOfTabs);
106 | mRootFragmentListener = builder.mRootFragmentListener;
107 | mTransactionListener = builder.mTransactionListener;
108 | mDefaultTransactionOptions = builder.mDefaultTransactionOptions;
109 | mSelectedTabIndex = builder.mSelectedTabIndex;
110 | mPopStrategy = builder.mPopStrategy;
111 | mFragNavLogger = builder.mFragNavLogger;
112 |
113 | DefaultFragNavPopController fragNavPopController = new DefaultFragNavPopController();
114 | switch (mPopStrategy) {
115 | case CURRENT_TAB:
116 | mFragNavTabHistoryController = new CurrentTabHistoryController(fragNavPopController);
117 | break;
118 | case UNIQUE_TAB_HISTORY:
119 | mFragNavTabHistoryController = new UniqueTabHistoryController(fragNavPopController,
120 | builder.fragNavSwitchController);
121 | break;
122 | case UNLIMITED_TAB_HISTORY:
123 | mFragNavTabHistoryController = new UnlimitedTabHistoryController(fragNavPopController,
124 | builder.fragNavSwitchController);
125 | break;
126 | }
127 |
128 | mFragNavTabHistoryController.switchTab(mSelectedTabIndex);
129 |
130 | //Attempt to restore from bundle, if not, initialize
131 | if (!restoreFromBundle(savedInstanceState, builder.mRootFragments)) {
132 | for (int i = 0; i < builder.mNumberOfTabs; i++) {
133 | Stack stack = new Stack<>();
134 | if (builder.mRootFragments != null) {
135 | stack.add(builder.mRootFragments.get(i));
136 | }
137 | mFragmentStacks.add(stack);
138 | }
139 |
140 | initialize(builder.mSelectedTabIndex);
141 | } else {
142 | mFragNavTabHistoryController.restoreFromBundle(savedInstanceState);
143 | }
144 | }
145 |
146 | public static Builder newBuilder(@Nullable Bundle savedInstanceState, FragmentManager fragmentManager, int containerId) {
147 | return new Builder(savedInstanceState, fragmentManager, containerId);
148 | }
149 |
150 | /**
151 | * Helper function to make sure that we are starting with a clean slate and to perform our first fragment interaction.
152 | *
153 | * @param index the tab index to initialize to
154 | */
155 | public void initialize(@TabIndex int index) {
156 | mSelectedTabIndex = index;
157 | if (mSelectedTabIndex > mFragmentStacks.size()) {
158 | throw new IndexOutOfBoundsException("Starting index cannot be larger than the number of stacks");
159 | }
160 |
161 | mSelectedTabIndex = index;
162 | clearFragmentManager();
163 | clearDialogFragment();
164 |
165 | if (index == NO_TAB) {
166 | return;
167 | }
168 |
169 | FragmentTransaction ft = createTransactionWithOptions(null, false);
170 |
171 | Fragment fragment = getRootFragment(index);
172 | ft.add(mContainerId, fragment, generateTag(fragment));
173 |
174 | commitTransaction(ft, null);
175 |
176 | mCurrentFrag = fragment;
177 | if (mTransactionListener != null) {
178 | mTransactionListener.onTabTransaction(getCurrentFrag(), mSelectedTabIndex);
179 | }
180 | }
181 | //endregion
182 |
183 | //region Transactions
184 |
185 | /**
186 | * Function used to switch to the specified fragment stack
187 | *
188 | * @param index The given index to switch to
189 | * @param transactionOptions Transaction options to be displayed
190 | * @throws IndexOutOfBoundsException Thrown if trying to switch to an index outside given range
191 | */
192 | public void switchTab(@TabIndex int index, @Nullable FragNavTransactionOptions transactionOptions) throws IndexOutOfBoundsException {
193 | switchTabInternal(index, transactionOptions);
194 | }
195 |
196 | private void switchTabInternal(@TabIndex int index, @Nullable FragNavTransactionOptions transactionOptions) throws IndexOutOfBoundsException {
197 | //Check to make sure the tab is within range
198 | if (index >= mFragmentStacks.size()) {
199 | throw new IndexOutOfBoundsException("Can't switch to a tab that hasn't been initialized, " +
200 | "Index : " + index + ", current stack size : " + mFragmentStacks.size() +
201 | ". Make sure to create all of the tabs you need in the Constructor or provide a way for them to be created via RootFragmentListener.");
202 | }
203 | if (mSelectedTabIndex != index) {
204 | mSelectedTabIndex = index;
205 | mFragNavTabHistoryController.switchTab(index);
206 |
207 | FragmentTransaction ft = createTransactionWithOptions(transactionOptions, false);
208 |
209 | detachCurrentFragment(ft);
210 |
211 | Fragment fragment = null;
212 | if (index == NO_TAB) {
213 | commitTransaction(ft, transactionOptions);
214 | } else {
215 | //Attempt to reattach previous fragment
216 | fragment = reattachPreviousFragment(ft);
217 | if (fragment != null) {
218 | commitTransaction(ft, transactionOptions);
219 | } else {
220 | fragment = getRootFragment(mSelectedTabIndex);
221 | ft.add(mContainerId, fragment, generateTag(fragment));
222 | commitTransaction(ft, transactionOptions);
223 | }
224 | }
225 |
226 |
227 | mCurrentFrag = fragment;
228 | if (mTransactionListener != null) {
229 | mTransactionListener.onTabTransaction(getCurrentFrag(), mSelectedTabIndex);
230 | }
231 | }
232 | }
233 |
234 | /**
235 | * Function used to switch to the specified fragment stack
236 | *
237 | * @param index The given index to switch to
238 | * @throws IndexOutOfBoundsException Thrown if trying to switch to an index outside given range
239 | */
240 | public void switchTab(@TabIndex int index) throws IndexOutOfBoundsException {
241 | switchTab(index, null);
242 | }
243 |
244 | /**
245 | * Push a fragment onto the current stack
246 | *
247 | * @param fragment The fragment that is to be pushed
248 | * @param transactionOptions Transaction options to be displayed
249 | */
250 | public void pushFragment(@Nullable Fragment fragment, @Nullable FragNavTransactionOptions transactionOptions) {
251 | if (fragment != null && mSelectedTabIndex != NO_TAB) {
252 | FragmentTransaction ft = createTransactionWithOptions(transactionOptions, false);
253 |
254 | detachCurrentFragment(ft);
255 | ft.add(mContainerId, fragment, generateTag(fragment));
256 |
257 | commitTransaction(ft, transactionOptions);
258 |
259 | mFragmentStacks.get(mSelectedTabIndex).push(fragment);
260 |
261 | mCurrentFrag = fragment;
262 | if (mTransactionListener != null) {
263 | mTransactionListener.onFragmentTransaction(getCurrentFrag(), TransactionType.PUSH);
264 | }
265 | }
266 | }
267 |
268 | /**
269 | * Push a fragment onto the current stack
270 | *
271 | * @param fragment The fragment that is to be pushed
272 | */
273 | public void pushFragment(@Nullable Fragment fragment) {
274 | pushFragment(fragment, null);
275 | }
276 |
277 | /**
278 | * Pop the current fragment from the current tab
279 | *
280 | * @param transactionOptions Transaction options to be displayed
281 | */
282 | public boolean popFragment(@Nullable FragNavTransactionOptions transactionOptions) throws UnsupportedOperationException {
283 | return popFragments(1, transactionOptions);
284 | }
285 |
286 | /**
287 | * Pop the current fragment from the current tab
288 | */
289 | public boolean popFragment() throws UnsupportedOperationException {
290 | return popFragment(null);
291 | }
292 |
293 | /**
294 | * Pop the current stack until a given tag is found. If the tag is not found, the stack will popFragment until it is at
295 | * the root fragment
296 | *
297 | * @param transactionOptions Transaction options to be displayed
298 | * @return true if any any fragment has been popped
299 | */
300 | public boolean popFragments(int popDepth, @Nullable FragNavTransactionOptions transactionOptions) throws UnsupportedOperationException {
301 | return mFragNavTabHistoryController.popFragments(popDepth, transactionOptions);
302 | }
303 |
304 | private int tryPopFragmentsFromCurrentStack(int popDepth, @Nullable FragNavTransactionOptions transactionOptions) throws UnsupportedOperationException {
305 | if (mPopStrategy == CURRENT_TAB && isRootFragment()) {
306 | throw new UnsupportedOperationException(
307 | "You can not popFragment the rootFragment. If you need to change this fragment, use replaceFragment(fragment)");
308 | } else if (popDepth < 1) {
309 | throw new UnsupportedOperationException("popFragments parameter needs to be greater than 0");
310 | } else if (mSelectedTabIndex == NO_TAB) {
311 | throw new UnsupportedOperationException("You can not pop fragments when no tab is selected");
312 | }
313 |
314 | //If our popDepth is big enough that it would just clear the stack, then call that.
315 | int poppableSize = mFragmentStacks.get(mSelectedTabIndex).size() - 1;
316 | if (popDepth >= poppableSize) {
317 | clearStack(transactionOptions);
318 | return poppableSize;
319 | }
320 |
321 | Fragment fragment;
322 | FragmentTransaction ft = createTransactionWithOptions(transactionOptions, true);
323 |
324 | //Pop the number of the fragments on the stack and remove them from the FragmentManager
325 | for (int i = 0; i < popDepth; i++) {
326 | fragment = mFragmentManager.findFragmentByTag(mFragmentStacks.get(mSelectedTabIndex).pop().getTag());
327 | if (fragment != null) {
328 | ft.remove(fragment);
329 | }
330 | }
331 |
332 | //Attempt to reattach previous fragment
333 | fragment = reattachPreviousFragment(ft);
334 |
335 | boolean bShouldPush = false;
336 | //If we can't reattach, either pull from the stack, or create a new root fragment
337 | if (fragment != null) {
338 | commitTransaction(ft, transactionOptions);
339 | } else {
340 | if (!mFragmentStacks.get(mSelectedTabIndex).isEmpty()) {
341 | fragment = mFragmentStacks.get(mSelectedTabIndex).peek();
342 | ft.add(mContainerId, fragment, fragment.getTag());
343 | commitTransaction(ft, transactionOptions);
344 | } else {
345 | fragment = getRootFragment(mSelectedTabIndex);
346 | ft.add(mContainerId, fragment, generateTag(fragment));
347 | commitTransaction(ft, transactionOptions);
348 |
349 | bShouldPush = true;
350 | }
351 | }
352 |
353 | //Need to have this down here so that that tag has been
354 | // committed to the fragment before we add to the stack
355 | if (bShouldPush) {
356 | mFragmentStacks.get(mSelectedTabIndex).push(fragment);
357 | }
358 |
359 | mCurrentFrag = fragment;
360 | if (mTransactionListener != null) {
361 | mTransactionListener.onFragmentTransaction(getCurrentFrag(), TransactionType.POP);
362 | }
363 | return popDepth;
364 | }
365 |
366 | /**
367 | * Pop the current fragment from the current tab
368 | */
369 | public void popFragments(int popDepth) throws UnsupportedOperationException {
370 | popFragments(popDepth, null);
371 | }
372 |
373 | /**
374 | * Clears the current tab's stack to get to just the bottom Fragment. This will reveal the root fragment
375 | *
376 | * @param transactionOptions Transaction options to be displayed
377 | */
378 | public void clearStack(@Nullable FragNavTransactionOptions transactionOptions) {
379 | if (mSelectedTabIndex == NO_TAB) {
380 | return;
381 | }
382 |
383 | //Grab Current stack
384 | Stack fragmentStack = mFragmentStacks.get(mSelectedTabIndex);
385 |
386 | // Only need to start popping and reattach if the stack is greater than 1
387 | if (fragmentStack.size() > 1) {
388 | Fragment fragment;
389 | FragmentTransaction ft = createTransactionWithOptions(transactionOptions, true);
390 |
391 | //Pop all of the fragments on the stack and remove them from the FragmentManager
392 | while (fragmentStack.size() > 1) {
393 | fragment = mFragmentManager.findFragmentByTag(fragmentStack.pop().getTag());
394 | if (fragment != null) {
395 | ft.remove(fragment);
396 | }
397 | }
398 |
399 | //Attempt to reattach previous fragment
400 | fragment = reattachPreviousFragment(ft);
401 |
402 | boolean bShouldPush = false;
403 | //If we can't reattach, either pull from the stack, or create a new root fragment
404 | if (fragment != null) {
405 | commitTransaction(ft, transactionOptions);
406 | } else {
407 | if (!fragmentStack.isEmpty()) {
408 | fragment = fragmentStack.peek();
409 | ft.add(mContainerId, fragment, fragment.getTag());
410 | commitTransaction(ft, transactionOptions);
411 | } else {
412 | fragment = getRootFragment(mSelectedTabIndex);
413 | ft.add(mContainerId, fragment, generateTag(fragment));
414 | commitTransaction(ft, transactionOptions);
415 |
416 | bShouldPush = true;
417 | }
418 | }
419 |
420 | if (bShouldPush) {
421 | mFragmentStacks.get(mSelectedTabIndex).push(fragment);
422 | }
423 |
424 | //Update the stored version we have in the list
425 | mFragmentStacks.set(mSelectedTabIndex, fragmentStack);
426 |
427 | mCurrentFrag = fragment;
428 | if (mTransactionListener != null) {
429 | mTransactionListener.onFragmentTransaction(getCurrentFrag(), TransactionType.POP);
430 | }
431 | }
432 | }
433 |
434 | /**
435 | * Clears the current tab's stack to get to just the bottom Fragment. This will reveal the root fragment.
436 | */
437 | public void clearStack() {
438 | clearStack(null);
439 | }
440 |
441 | /**
442 | * Replace the current fragment
443 | *
444 | * @param fragment the fragment to be shown instead
445 | * @param transactionOptions Transaction options to be displayed
446 | */
447 | public void replaceFragment(@NonNull Fragment fragment, @Nullable FragNavTransactionOptions transactionOptions) {
448 | Fragment poppingFrag = getCurrentFrag();
449 |
450 | if (poppingFrag != null) {
451 | FragmentTransaction ft = createTransactionWithOptions(transactionOptions, false);
452 |
453 | //overly cautious fragment popFragment
454 | Stack fragmentStack = mFragmentStacks.get(mSelectedTabIndex);
455 | if (!fragmentStack.isEmpty()) {
456 | fragmentStack.pop();
457 | }
458 |
459 | String tag = generateTag(fragment);
460 | ft.replace(mContainerId, fragment, tag);
461 |
462 | //Commit our transactions
463 | commitTransaction(ft, transactionOptions);
464 |
465 | fragmentStack.push(fragment);
466 | mCurrentFrag = fragment;
467 |
468 | if (mTransactionListener != null) {
469 | mTransactionListener.onFragmentTransaction(getCurrentFrag(), TransactionType.REPLACE);
470 | }
471 | }
472 | }
473 |
474 | /**
475 | * Replace the current fragment
476 | *
477 | * @param fragment the fragment to be shown instead
478 | */
479 | public void replaceFragment(@NonNull Fragment fragment) {
480 | replaceFragment(fragment, null);
481 | }
482 |
483 | /**
484 | * @return Current DialogFragment being displayed. Null if none
485 | */
486 | @Nullable
487 | @CheckResult
488 | public DialogFragment getCurrentDialogFrag() {
489 | if (mCurrentDialogFrag != null) {
490 | return mCurrentDialogFrag;
491 | }
492 | //Else try to find one in the FragmentManager
493 | else {
494 | FragmentManager fragmentManager;
495 | Fragment currentFrag = getCurrentFrag();
496 | if (currentFrag != null) {
497 | fragmentManager = currentFrag.getChildFragmentManager();
498 | } else {
499 | fragmentManager = mFragmentManager;
500 | }
501 | if (fragmentManager.getFragments() != null) {
502 | for (Fragment fragment : fragmentManager.getFragments()) {
503 | if (fragment instanceof DialogFragment) {
504 | mCurrentDialogFrag = (DialogFragment) fragment;
505 | break;
506 | }
507 | }
508 | }
509 | }
510 | return mCurrentDialogFrag;
511 | }
512 |
513 | /**
514 | * Clear any DialogFragments that may be shown
515 | */
516 | public void clearDialogFragment() {
517 | if (mCurrentDialogFrag != null) {
518 | mCurrentDialogFrag.dismiss();
519 | mCurrentDialogFrag = null;
520 | }
521 | // If we don't have the current dialog, try to find and dismiss it
522 | else {
523 | FragmentManager fragmentManager;
524 | Fragment currentFrag = getCurrentFrag();
525 | if (currentFrag != null) {
526 | fragmentManager = currentFrag.getChildFragmentManager();
527 | } else {
528 | fragmentManager = mFragmentManager;
529 | }
530 |
531 | if (fragmentManager.getFragments() != null) {
532 | for (Fragment fragment : fragmentManager.getFragments()) {
533 | if (fragment instanceof DialogFragment) {
534 | ((DialogFragment) fragment).dismiss();
535 | }
536 | }
537 | }
538 | }
539 | }
540 |
541 | /**
542 | * Display a DialogFragment on the screen
543 | *
544 | * @param dialogFragment The Fragment to be Displayed
545 | */
546 | public void showDialogFragment(@Nullable DialogFragment dialogFragment) {
547 | if (dialogFragment != null) {
548 | FragmentManager fragmentManager;
549 | Fragment currentFrag = getCurrentFrag();
550 | if (currentFrag != null) {
551 | fragmentManager = currentFrag.getChildFragmentManager();
552 | } else {
553 | fragmentManager = mFragmentManager;
554 | }
555 |
556 | //Clear any current dialog fragments
557 | if (fragmentManager.getFragments() != null) {
558 | for (Fragment fragment : fragmentManager.getFragments()) {
559 | if (fragment instanceof DialogFragment) {
560 | ((DialogFragment) fragment).dismiss();
561 | mCurrentDialogFrag = null;
562 | }
563 | }
564 | }
565 |
566 | mCurrentDialogFrag = dialogFragment;
567 | try {
568 | dialogFragment.show(fragmentManager, dialogFragment.getClass().getName());
569 | } catch (IllegalStateException e) {
570 | logError("Could not show dialog", e);
571 | // Activity was likely destroyed before we had a chance to show, nothing can be done here.
572 | }
573 | }
574 | }
575 |
576 | //endregion
577 |
578 | //region Private helper functions
579 |
580 | /**
581 | * Helper function to get the root fragment for a given index. This is done by either passing them in the constructor, or dynamically via NavListener.
582 | *
583 | * @param index The tab index to get this fragment from
584 | * @return The root fragment at this index
585 | * @throws IllegalStateException This will be thrown if we can't find a rootFragment for this index. Either because you didn't provide it in the
586 | * constructor, or because your RootFragmentListener.getRootFragment(index) isn't returning a fragment for this index.
587 | */
588 | @NonNull
589 | @CheckResult
590 | private Fragment getRootFragment(int index) throws IllegalStateException {
591 | Fragment fragment = null;
592 | if (!mFragmentStacks.get(index).isEmpty()) {
593 | fragment = mFragmentStacks.get(index).peek();
594 | } else if (mRootFragmentListener != null) {
595 | fragment = mRootFragmentListener.getRootFragment(index);
596 |
597 | if (mSelectedTabIndex != NO_TAB) {
598 | mFragmentStacks.get(mSelectedTabIndex).push(fragment);
599 | }
600 | }
601 | if (fragment == null) {
602 | throw new IllegalStateException("Either you haven't past in a fragment at this index in your constructor, or you haven't " +
603 | "provided a way to create it while via your RootFragmentListener.getRootFragment(index)");
604 | }
605 |
606 | return fragment;
607 | }
608 |
609 | /**
610 | * Will attempt to reattach a previous fragment in the FragmentManager, or return null if not able to.
611 | *
612 | * @param ft current fragment transaction
613 | * @return Fragment if we were able to find and reattach it
614 | */
615 | @Nullable
616 | private Fragment reattachPreviousFragment(@NonNull FragmentTransaction ft) {
617 | Stack fragmentStack = mFragmentStacks.get(mSelectedTabIndex);
618 | Fragment fragment = null;
619 | if (!fragmentStack.isEmpty()) {
620 | fragment = mFragmentManager.findFragmentByTag(fragmentStack.peek().getTag());
621 | if (fragment != null) {
622 | ft.attach(fragment);
623 | }
624 | }
625 | return fragment;
626 | }
627 |
628 | /**
629 | * Attempts to detach any current fragment if it exists, and if none is found, returns.
630 | *
631 | * @param ft the current transaction being performed
632 | */
633 | private void detachCurrentFragment(@NonNull FragmentTransaction ft) {
634 | Fragment oldFrag = getCurrentFrag();
635 | if (oldFrag != null) {
636 | ft.detach(oldFrag);
637 | }
638 | }
639 |
640 | /**
641 | * Helper function to attempt to get current fragment
642 | *
643 | * @return Fragment the current frag to be returned
644 | */
645 | @Nullable
646 | @CheckResult
647 | public Fragment getCurrentFrag() {
648 | //Attempt to used stored current fragment
649 | if (mCurrentFrag != null && mCurrentFrag.isAdded() && !mCurrentFrag.isDetached()) {
650 | return mCurrentFrag;
651 | } else if (mSelectedTabIndex == NO_TAB) {
652 | return null;
653 | }
654 | //if not, try to pull it from the stack
655 | Stack fragmentStack = mFragmentStacks.get(mSelectedTabIndex);
656 | if (!fragmentStack.isEmpty()) {
657 | Fragment fragmentByTag = mFragmentManager.findFragmentByTag(mFragmentStacks.get(mSelectedTabIndex).peek().getTag());
658 | if (fragmentByTag != null) {
659 | mCurrentFrag = fragmentByTag;
660 | }
661 | }
662 |
663 |
664 | return mCurrentFrag;
665 | }
666 |
667 | /**
668 | * Create a unique fragment tag so that we can grab the fragment later from the FragmentManger
669 | *
670 | * @param fragment The fragment that we're creating a unique tag for
671 | * @return a unique tag using the fragment's class name
672 | */
673 | @NonNull
674 | @CheckResult
675 | private String generateTag(@NonNull Fragment fragment) {
676 | return fragment.getClass().getName() + ++mTagCount;
677 | }
678 |
679 |
680 | /**
681 | * Private helper function to clear out the fragment manager on initialization. All fragment management should be done via FragNav.
682 | */
683 | private void clearFragmentManager() {
684 | if (mFragmentManager.getFragments() != null) {
685 | FragmentTransaction ft = createTransactionWithOptions(null, false);
686 | for (Fragment fragment : mFragmentManager.getFragments()) {
687 | if (fragment != null) {
688 | ft.remove(fragment);
689 | }
690 | }
691 | commitTransaction(ft, null);
692 | }
693 | }
694 |
695 | /**
696 | * Setup a fragment transaction with the given option
697 | *
698 | * @param transactionOptions The options that will be set for this transaction
699 | * @param isPopping
700 | */
701 | @CheckResult
702 | private FragmentTransaction createTransactionWithOptions(@Nullable FragNavTransactionOptions transactionOptions, boolean isPopping) {
703 | FragmentTransaction ft = mFragmentManager.beginTransaction();
704 | if (transactionOptions == null) {
705 | transactionOptions = mDefaultTransactionOptions;
706 | }
707 | if (transactionOptions != null) {
708 | if (isPopping) {
709 | ft.setCustomAnimations(transactionOptions.popEnterAnimation, transactionOptions.popExitAnimation);
710 | } else {
711 | ft.setCustomAnimations(transactionOptions.enterAnimation, transactionOptions.exitAnimation);
712 | }
713 | ft.setTransitionStyle(transactionOptions.transitionStyle);
714 |
715 | ft.setTransition(transactionOptions.transition);
716 |
717 | if (transactionOptions.sharedElements != null) {
718 | for (Pair sharedElement : transactionOptions.sharedElements) {
719 | ft.addSharedElement(sharedElement.first, sharedElement.second);
720 | }
721 | }
722 |
723 | if (transactionOptions.breadCrumbTitle != null) {
724 | ft.setBreadCrumbTitle(transactionOptions.breadCrumbTitle);
725 | }
726 |
727 | if (transactionOptions.breadCrumbShortTitle != null) {
728 | ft.setBreadCrumbShortTitle(transactionOptions.breadCrumbShortTitle);
729 | }
730 | }
731 | return ft;
732 | }
733 |
734 | /**
735 | * Helper function to commit fragment transaction with transaction option - allowStateLoss
736 | *
737 | * @param fragmentTransaction
738 | * @param transactionOptions
739 | */
740 | private void commitTransaction(FragmentTransaction fragmentTransaction, @Nullable FragNavTransactionOptions transactionOptions) {
741 | if (transactionOptions != null && transactionOptions.allowStateLoss) {
742 | fragmentTransaction.commitAllowingStateLoss();
743 | } else {
744 | fragmentTransaction.commit();
745 | }
746 | }
747 |
748 | private void logError(String message, Throwable throwable) {
749 | if (mFragNavLogger != null) {
750 | mFragNavLogger.error(message, throwable);
751 | }
752 | }
753 |
754 | //endregion
755 |
756 | //region Public helper functions
757 |
758 | /**
759 | * Get the number of fragment stacks
760 | *
761 | * @return the number of fragment stacks
762 | */
763 | @CheckResult
764 | public int getSize() {
765 | return mFragmentStacks.size();
766 | }
767 |
768 | /**
769 | * Get a copy of the stack at a given index
770 | *
771 | * @return requested stack
772 | */
773 | @SuppressWarnings("unchecked")
774 | @CheckResult
775 | @Nullable
776 | public Stack getStack(@TabIndex int index) {
777 | if (index == NO_TAB) {
778 | return null;
779 | }
780 | if (index >= mFragmentStacks.size()) {
781 | throw new IndexOutOfBoundsException("Can't get an index that's larger than we've setup");
782 | }
783 | return (Stack) mFragmentStacks.get(index).clone();
784 | }
785 |
786 | /**
787 | * Get a copy of the current stack that is being displayed
788 | *
789 | * @return Current stack
790 | */
791 | @SuppressWarnings("unchecked")
792 | @CheckResult
793 | @Nullable
794 | public Stack getCurrentStack() {
795 | return getStack(mSelectedTabIndex);
796 | }
797 |
798 | /**
799 | * Get the index of the current stack that is being displayed
800 | *
801 | * @return Current stack index
802 | */
803 | @CheckResult
804 | @TabIndex
805 | public int getCurrentStackIndex() {
806 | return mSelectedTabIndex;
807 | }
808 |
809 | /**
810 | * @return If true, you are at the bottom of the stack
811 | * (Consider using replaceFragment if you need to change the root fragment for some reason)
812 | * else you can popFragment as needed as your are not at the root
813 | */
814 | @CheckResult
815 | public boolean isRootFragment() {
816 | Stack stack = getCurrentStack();
817 |
818 | return stack == null || stack.size() == 1;
819 | }
820 |
821 | /**
822 | * Helper function to get wether the fragmentManger has gone through a stateSave, if this is true, you probably want to commit allowing stateloss
823 | *
824 | * @return if fragmentManger isStateSaved
825 | */
826 | public boolean isStateSaved() {
827 | return mFragmentManager.isStateSaved();
828 | }
829 |
830 | /**
831 | * Use this if you need to make sure that pending transactions occur immediately. This call is safe to
832 | * call as often as you want as there's a check to prevent multiple executePendingTransactions at once
833 | *
834 | */
835 | public void executePendingTransactions() {
836 | if (!mExecutingTransaction) {
837 | mExecutingTransaction = true;
838 | mFragmentManager.executePendingTransactions();
839 | mExecutingTransaction = false;
840 | }
841 | }
842 |
843 | //endregion
844 |
845 | //region SavedInstanceState
846 |
847 | /**
848 | * Call this in your Activity's onSaveInstanceState(Bundle outState) method to save the instance's state.
849 | *
850 | * @param outState The Bundle to save state information to
851 | */
852 | public void onSaveInstanceState(@NonNull Bundle outState) {
853 |
854 | // Write tag count
855 | outState.putInt(EXTRA_TAG_COUNT, mTagCount);
856 |
857 | // Write select tab
858 | outState.putInt(EXTRA_SELECTED_TAB_INDEX, mSelectedTabIndex);
859 |
860 | // Write current fragment
861 | Fragment currentFrag = getCurrentFrag();
862 | if (currentFrag != null) {
863 | outState.putString(EXTRA_CURRENT_FRAGMENT, currentFrag.getTag());
864 | }
865 |
866 | // Write stacks
867 | try {
868 | final JSONArray stackArrays = new JSONArray();
869 |
870 | for (Stack stack : mFragmentStacks) {
871 | final JSONArray stackArray = new JSONArray();
872 |
873 | for (Fragment fragment : stack) {
874 | stackArray.put(fragment.getTag());
875 | }
876 |
877 | stackArrays.put(stackArray);
878 | }
879 |
880 | outState.putString(EXTRA_FRAGMENT_STACK, stackArrays.toString());
881 | } catch (Throwable t) {
882 | logError("Could not save fragment stack", t);
883 | // Nothing we can do
884 | }
885 |
886 | mFragNavTabHistoryController.onSaveInstanceState(outState);
887 | }
888 |
889 | /**
890 | * Restores this instance to the state specified by the contents of savedInstanceState
891 | *
892 | * @param savedInstanceState The bundle to restore from
893 | * @param rootFragments List of root fragments from which to initialize empty stacks. If null, pull fragments from RootFragmentListener.
894 | * @return true if successful, false if not
895 | */
896 | private boolean restoreFromBundle(@Nullable Bundle savedInstanceState, @Nullable List rootFragments) {
897 | if (savedInstanceState == null) {
898 | return false;
899 | }
900 |
901 | // Restore tag count
902 | mTagCount = savedInstanceState.getInt(EXTRA_TAG_COUNT, 0);
903 |
904 | // Restore current fragment
905 | mCurrentFrag = mFragmentManager.findFragmentByTag(savedInstanceState.getString(EXTRA_CURRENT_FRAGMENT));
906 |
907 | // Restore fragment stacks
908 | try {
909 | final JSONArray stackArrays = new JSONArray(savedInstanceState.getString(EXTRA_FRAGMENT_STACK));
910 |
911 | for (int x = 0; x < stackArrays.length(); x++) {
912 | final JSONArray stackArray = stackArrays.getJSONArray(x);
913 | final Stack stack = new Stack<>();
914 |
915 | if (stackArray.length() == 1) {
916 | final String tag = stackArray.getString(0);
917 | final Fragment fragment;
918 |
919 | if (tag == null || "null".equalsIgnoreCase(tag)) {
920 | if (rootFragments != null) {
921 | fragment = rootFragments.get(x);
922 | } else {
923 | fragment = getRootFragment(x);
924 | }
925 | } else {
926 | fragment = mFragmentManager.findFragmentByTag(tag);
927 | }
928 |
929 | if (fragment != null) {
930 | stack.add(fragment);
931 | }
932 | } else {
933 | for (int y = 0; y < stackArray.length(); y++) {
934 | final String tag = stackArray.getString(y);
935 |
936 | if (tag != null && !"null".equalsIgnoreCase(tag)) {
937 | final Fragment fragment = mFragmentManager.findFragmentByTag(tag);
938 |
939 | if (fragment != null) {
940 | stack.add(fragment);
941 | }
942 | }
943 | }
944 | }
945 |
946 | mFragmentStacks.add(stack);
947 | }
948 | // Restore selected tab if we have one
949 | int selectedTabIndex = savedInstanceState.getInt(EXTRA_SELECTED_TAB_INDEX);
950 | if (selectedTabIndex >= 0 && selectedTabIndex < MAX_NUM_TABS) {
951 | switchTab(selectedTabIndex, FragNavTransactionOptions.newBuilder().build());
952 | }
953 |
954 | //Successfully restored state
955 | return true;
956 | } catch (Throwable ex) {
957 | mTagCount = 0;
958 | mCurrentFrag = null;
959 | mFragmentStacks.clear();
960 | logError("Could not restore fragment state", ex);
961 | return false;
962 | }
963 | }
964 | //endregion
965 |
966 | public enum TransactionType {
967 | PUSH,
968 | POP,
969 | REPLACE
970 | }
971 |
972 | //Declare the TabIndex annotation
973 | @IntDef({NO_TAB, TAB1, TAB2, TAB3, TAB4, TAB5, TAB6, TAB7, TAB8, TAB9, TAB10, TAB11, TAB12,
974 | TAB13, TAB14, TAB15, TAB16, TAB17, TAB18, TAB19, TAB20})
975 | @Retention(RetentionPolicy.SOURCE)
976 | public @interface TabIndex {
977 | }
978 |
979 | // Declare Transit Styles
980 | @IntDef({FragmentTransaction.TRANSIT_NONE, FragmentTransaction.TRANSIT_FRAGMENT_OPEN, FragmentTransaction.TRANSIT_FRAGMENT_CLOSE, FragmentTransaction.TRANSIT_FRAGMENT_FADE})
981 | @Retention(RetentionPolicy.SOURCE)
982 | @interface Transit {
983 | }
984 |
985 | public interface RootFragmentListener {
986 | /**
987 | * Dynamically create the Fragment that will go on the bottom of the stack
988 | *
989 | * @param index the index that the root of the stack Fragment needs to go
990 | * @return the new Fragment
991 | */
992 | Fragment getRootFragment(int index);
993 | }
994 |
995 | public interface TransactionListener {
996 |
997 | void onTabTransaction(@Nullable Fragment fragment, int index);
998 |
999 | void onFragmentTransaction(Fragment fragment, TransactionType transactionType);
1000 | }
1001 |
1002 | public class DefaultFragNavPopController implements com.ncapdevi.fragnav.FragNavPopController {
1003 | @Override
1004 | public int tryPopFragments(int popDepth, FragNavTransactionOptions transactionOptions) throws UnsupportedOperationException {
1005 | return FragNavController.this.tryPopFragmentsFromCurrentStack(popDepth, transactionOptions);
1006 | }
1007 | }
1008 |
1009 | public static final class Builder {
1010 | private final int mContainerId;
1011 | private FragmentManager mFragmentManager;
1012 | private RootFragmentListener mRootFragmentListener;
1013 | @TabIndex
1014 | private int mSelectedTabIndex = TAB1;
1015 | private TransactionListener mTransactionListener;
1016 | private FragNavTransactionOptions mDefaultTransactionOptions;
1017 | private int mNumberOfTabs = 0;
1018 |
1019 | @FragNavTabHistoryController.PopStrategy
1020 | private int mPopStrategy = CURRENT_TAB;
1021 | private List mRootFragments;
1022 | private Bundle mSavedInstanceState;
1023 |
1024 | @Nullable
1025 | private FragNavSwitchController fragNavSwitchController;
1026 |
1027 | @Nullable
1028 | private FragNavLogger mFragNavLogger;
1029 |
1030 | public Builder(@Nullable Bundle savedInstanceState, FragmentManager mFragmentManager, int mContainerId) {
1031 | this.mSavedInstanceState = savedInstanceState;
1032 | this.mFragmentManager = mFragmentManager;
1033 | this.mContainerId = mContainerId;
1034 | }
1035 |
1036 | /**
1037 | * @param selectedTabIndex The initial tab index to be used must be in range of rootFragments size
1038 | */
1039 | public Builder selectedTabIndex(@TabIndex int selectedTabIndex) {
1040 | mSelectedTabIndex = selectedTabIndex;
1041 | if (mRootFragments != null && mSelectedTabIndex > mNumberOfTabs) {
1042 | throw new IndexOutOfBoundsException("Starting index cannot be larger than the number of stacks");
1043 | }
1044 | return this;
1045 | }
1046 |
1047 | /**
1048 | * @param rootFragment A single root fragment. This library can still be helpful when managing a single stack of fragments
1049 | */
1050 | public Builder rootFragment(Fragment rootFragment) {
1051 | mRootFragments = new ArrayList<>(1);
1052 | mRootFragments.add(rootFragment);
1053 | mNumberOfTabs = 1;
1054 | return rootFragments(mRootFragments);
1055 | }
1056 |
1057 | /**
1058 | * @param rootFragments a list of root fragments. root Fragments are the root fragments that exist on any tab structure. If only one fragment is sent in, fragnav will still manage
1059 | * transactions
1060 | */
1061 | public Builder rootFragments(@NonNull List rootFragments) {
1062 | mRootFragments = rootFragments;
1063 | mNumberOfTabs = rootFragments.size();
1064 | if (mNumberOfTabs > MAX_NUM_TABS) {
1065 | throw new IllegalArgumentException("Number of root fragments cannot be greater than " + MAX_NUM_TABS);
1066 | }
1067 | return this;
1068 | }
1069 |
1070 | /**
1071 | * @param transactionOptions The default transaction options to be used unless otherwise defined.
1072 | */
1073 | public Builder defaultTransactionOptions(@NonNull FragNavTransactionOptions transactionOptions) {
1074 | mDefaultTransactionOptions = transactionOptions;
1075 | return this;
1076 | }
1077 |
1078 | /**
1079 | * @param rootFragmentListener a listener that allows for dynamically creating root fragments
1080 | * @param numberOfTabs the number of tabs that will be switched between
1081 | */
1082 | public Builder rootFragmentListener(RootFragmentListener rootFragmentListener, int numberOfTabs) {
1083 | mRootFragmentListener = rootFragmentListener;
1084 | mNumberOfTabs = numberOfTabs;
1085 | if (mNumberOfTabs > MAX_NUM_TABS) {
1086 | throw new IllegalArgumentException("Number of tabs cannot be greater than " + MAX_NUM_TABS);
1087 | }
1088 | return this;
1089 | }
1090 |
1091 | /**
1092 | * @param val A listener to be implemented (typically within the main activity) to fragment transactions (including tab switches)
1093 | */
1094 | public Builder transactionListener(TransactionListener val) {
1095 | mTransactionListener = val;
1096 | return this;
1097 | }
1098 |
1099 | /**
1100 | * @param popStrategy Switch between different approaches of handling tab history while popping fragments on current tab
1101 | */
1102 | public Builder popStrategy(@FragNavTabHistoryController.PopStrategy int popStrategy) {
1103 | mPopStrategy = popStrategy;
1104 | return this;
1105 | }
1106 |
1107 | /**
1108 | * @param fragNavSwitchController Handles switch requests
1109 | */
1110 | public Builder switchController(FragNavSwitchController fragNavSwitchController) {
1111 | this.fragNavSwitchController = fragNavSwitchController;
1112 | return this;
1113 | }
1114 |
1115 | /**
1116 | * @param fragNavLogger Reports errors for the client
1117 | */
1118 | public Builder logger(FragNavLogger fragNavLogger) {
1119 | this.mFragNavLogger = fragNavLogger;
1120 | return this;
1121 | }
1122 |
1123 | public FragNavController build() {
1124 | if (mRootFragmentListener == null && mRootFragments == null) {
1125 | throw new IndexOutOfBoundsException("Either a root fragment(s) needs to be set, or a fragment listener");
1126 | }
1127 | if ((mPopStrategy == UNIQUE_TAB_HISTORY || mPopStrategy == UNLIMITED_TAB_HISTORY) && fragNavSwitchController == null) {
1128 | throw new IllegalStateException(
1129 | "Switch handler needs to be set for unique or unlimited tab history strategy");
1130 | }
1131 | return new FragNavController(this, mSavedInstanceState);
1132 | }
1133 | }
1134 | }
1135 |
--------------------------------------------------------------------------------
/frag-nav/src/main/java/com/ncapdevi/fragnav/FragNavLogger.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.fragnav;
2 |
3 | public interface FragNavLogger {
4 | void error(String message, Throwable throwable);
5 | }
6 |
--------------------------------------------------------------------------------
/frag-nav/src/main/java/com/ncapdevi/fragnav/FragNavPopController.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.fragnav;
2 |
3 | public interface FragNavPopController {
4 | int tryPopFragments(int popDepth, FragNavTransactionOptions transactionOptions) throws UnsupportedOperationException;
5 | }
6 |
--------------------------------------------------------------------------------
/frag-nav/src/main/java/com/ncapdevi/fragnav/FragNavSwitchController.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.fragnav;
2 |
3 | public interface FragNavSwitchController {
4 | void switchTab(@FragNavController.TabIndex int index, FragNavTransactionOptions transactionOptions);
5 | }
6 |
--------------------------------------------------------------------------------
/frag-nav/src/main/java/com/ncapdevi/fragnav/FragNavTransactionOptions.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.fragnav;
2 |
3 | import android.support.annotation.AnimRes;
4 | import android.support.annotation.StyleRes;
5 | import android.support.v4.app.FragmentTransaction;
6 | import android.support.v4.util.Pair;
7 | import android.view.View;
8 |
9 | import java.util.ArrayList;
10 | import java.util.List;
11 |
12 | /**
13 | *
14 | */
15 |
16 |
17 | public class FragNavTransactionOptions {
18 | List> sharedElements;
19 | @FragNavController.Transit
20 | int transition = FragmentTransaction.TRANSIT_NONE;
21 | @AnimRes
22 | int enterAnimation = 0;
23 | @AnimRes
24 | int exitAnimation = 0;
25 | @AnimRes
26 | int popEnterAnimation = 0;
27 | @AnimRes
28 | int popExitAnimation = 0;
29 | @StyleRes
30 | int transitionStyle = 0;
31 | String breadCrumbTitle;
32 | String breadCrumbShortTitle;
33 | boolean allowStateLoss;
34 |
35 | private FragNavTransactionOptions(Builder builder) {
36 | sharedElements = builder.sharedElements;
37 | transition = builder.transition;
38 | enterAnimation = builder.enterAnimation;
39 | exitAnimation = builder.exitAnimation;
40 | transitionStyle = builder.transitionStyle;
41 | popEnterAnimation = builder.popEnterAnimation;
42 | popExitAnimation = builder.popExitAnimation;
43 | breadCrumbTitle = builder.breadCrumbTitle;
44 | breadCrumbShortTitle = builder.breadCrumbShortTitle;
45 | allowStateLoss = builder.allowStateLoss;
46 | }
47 |
48 | public static Builder newBuilder() {
49 | return new Builder();
50 | }
51 |
52 | public static final class Builder {
53 | private List> sharedElements;
54 | private int transition;
55 | private int enterAnimation;
56 | private int exitAnimation;
57 | private int transitionStyle;
58 | private int popEnterAnimation;
59 | private int popExitAnimation;
60 | private String breadCrumbTitle;
61 | private String breadCrumbShortTitle;
62 | private boolean allowStateLoss = false;
63 |
64 | private Builder() {
65 | }
66 |
67 | public Builder addSharedElement(Pair val) {
68 | if (sharedElements == null) {
69 | sharedElements = new ArrayList<>(3);
70 | }
71 | sharedElements.add(val);
72 | return this;
73 | }
74 |
75 | public Builder sharedElements(List> val) {
76 | sharedElements = val;
77 | return this;
78 | }
79 |
80 | public Builder transition(@FragNavController.Transit int val) {
81 | transition = val;
82 | return this;
83 | }
84 |
85 | public Builder customAnimations(@AnimRes int enterAnimation, @AnimRes int exitAnimation) {
86 | this.enterAnimation = enterAnimation;
87 | this.exitAnimation = exitAnimation;
88 | return this;
89 | }
90 |
91 | public Builder customAnimations(@AnimRes int enterAnimation, @AnimRes int exitAnimation, @AnimRes int popEnterAnimation, @AnimRes int popExitAnimation) {
92 | this.popEnterAnimation = popEnterAnimation;
93 | this.popExitAnimation = popExitAnimation;
94 | return customAnimations(enterAnimation, exitAnimation);
95 | }
96 |
97 |
98 | public Builder transitionStyle(@StyleRes int val) {
99 | transitionStyle = val;
100 | return this;
101 | }
102 |
103 | public Builder breadCrumbTitle(String val) {
104 | breadCrumbTitle = val;
105 | return this;
106 | }
107 |
108 | public Builder breadCrumbShortTitle(String val) {
109 | breadCrumbShortTitle = val;
110 | return this;
111 | }
112 |
113 | public Builder allowStateLoss(boolean allow) {
114 | allowStateLoss = allow;
115 | return this;
116 | }
117 |
118 | public FragNavTransactionOptions build() {
119 | return new FragNavTransactionOptions(this);
120 | }
121 | }
122 | }
123 |
124 |
--------------------------------------------------------------------------------
/frag-nav/src/main/java/com/ncapdevi/fragnav/tabhistory/BaseFragNavTabHistoryController.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.fragnav.tabhistory;
2 |
3 | import com.ncapdevi.fragnav.FragNavPopController;
4 |
5 | abstract class BaseFragNavTabHistoryController implements FragNavTabHistoryController {
6 | FragNavPopController fragNavPopController;
7 |
8 | BaseFragNavTabHistoryController(FragNavPopController fragNavPopController) {
9 | this.fragNavPopController = fragNavPopController;
10 | }
11 | }
12 |
--------------------------------------------------------------------------------
/frag-nav/src/main/java/com/ncapdevi/fragnav/tabhistory/CollectionFragNavTabHistoryController.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.fragnav.tabhistory;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.NonNull;
5 | import android.support.annotation.Nullable;
6 |
7 | import com.ncapdevi.fragnav.FragNavPopController;
8 | import com.ncapdevi.fragnav.FragNavSwitchController;
9 | import com.ncapdevi.fragnav.FragNavTransactionOptions;
10 |
11 | import java.util.ArrayList;
12 |
13 | abstract class CollectionFragNavTabHistoryController extends BaseFragNavTabHistoryController {
14 | private static final String EXTRA_STACK_HISTORY = "EXTRA_STACK_HISTORY";
15 |
16 | private FragNavSwitchController fragNavSwitchController;
17 |
18 | CollectionFragNavTabHistoryController(FragNavPopController fragNavPopController,
19 | FragNavSwitchController fragNavSwitchController) {
20 | super(fragNavPopController);
21 | this.fragNavSwitchController = fragNavSwitchController;
22 | }
23 |
24 | @Override
25 | public boolean popFragments(int popDepth,
26 | FragNavTransactionOptions transactionOptions) throws UnsupportedOperationException {
27 | boolean changed = false;
28 | boolean switched;
29 | do {
30 | switched = false;
31 | int count = fragNavPopController.tryPopFragments(popDepth, transactionOptions);
32 | if (count > 0) {
33 | changed = true;
34 | switched = true;
35 | popDepth -= count;
36 | } else if (getCollectionSize() > 1) {
37 | fragNavSwitchController.switchTab(getAndRemoveIndex(), transactionOptions);
38 | popDepth--;
39 | changed = true;
40 | switched = true;
41 | }
42 | } while (popDepth > 0 && switched);
43 | return changed;
44 | }
45 |
46 | abstract int getCollectionSize();
47 |
48 | abstract int getAndRemoveIndex();
49 |
50 | @NonNull
51 | abstract ArrayList getHistory();
52 |
53 | abstract void setHistory(@NonNull ArrayList history);
54 |
55 | @Override
56 | public void restoreFromBundle(@Nullable Bundle savedInstanceState) {
57 | if (savedInstanceState == null) {
58 | return;
59 | }
60 | ArrayList arrayList = savedInstanceState.getIntegerArrayList(EXTRA_STACK_HISTORY);
61 | if (arrayList != null) {
62 | setHistory(arrayList);
63 | }
64 | }
65 |
66 | @Override
67 | public void onSaveInstanceState(@NonNull Bundle outState) {
68 | ArrayList history = getHistory();
69 | if (history.isEmpty()) {
70 | return;
71 | }
72 | outState.putIntegerArrayList(EXTRA_STACK_HISTORY, history);
73 | }
74 | }
75 |
--------------------------------------------------------------------------------
/frag-nav/src/main/java/com/ncapdevi/fragnav/tabhistory/CurrentTabHistoryController.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.fragnav.tabhistory;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.NonNull;
5 | import android.support.annotation.Nullable;
6 |
7 | import com.ncapdevi.fragnav.FragNavPopController;
8 | import com.ncapdevi.fragnav.FragNavTransactionOptions;
9 |
10 | public class CurrentTabHistoryController extends BaseFragNavTabHistoryController {
11 | public CurrentTabHistoryController(FragNavPopController fragNavPopController) {
12 | super(fragNavPopController);
13 | }
14 |
15 | @Override
16 | public boolean popFragments(int popDepth,
17 | FragNavTransactionOptions transactionOptions) throws UnsupportedOperationException {
18 | return fragNavPopController.tryPopFragments(popDepth, transactionOptions) > 0;
19 | }
20 |
21 | @Override
22 | public void switchTab(int index) {
23 | }
24 |
25 | @Override
26 | public void onSaveInstanceState(@NonNull Bundle outState) {
27 | }
28 |
29 | @Override
30 | public void restoreFromBundle(@Nullable Bundle savedInstanceState) {
31 | }
32 | }
33 |
--------------------------------------------------------------------------------
/frag-nav/src/main/java/com/ncapdevi/fragnav/tabhistory/FragNavTabHistoryController.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.fragnav.tabhistory;
2 |
3 | import android.os.Bundle;
4 | import android.support.annotation.IntDef;
5 | import android.support.annotation.NonNull;
6 | import android.support.annotation.Nullable;
7 |
8 | import com.ncapdevi.fragnav.FragNavTransactionOptions;
9 |
10 | import java.lang.annotation.Retention;
11 |
12 | import static java.lang.annotation.RetentionPolicy.SOURCE;
13 |
14 | public interface FragNavTabHistoryController {
15 | /**
16 | * Define what happens when we try to pop on a tab where root fragment is at the top
17 | */
18 | @Retention(SOURCE)
19 | @IntDef({CURRENT_TAB, UNIQUE_TAB_HISTORY, UNLIMITED_TAB_HISTORY})
20 | @interface PopStrategy {
21 | }
22 |
23 | /**
24 | * We only pop fragments from current tab, don't switch between tabs
25 | */
26 | int CURRENT_TAB = 0;
27 |
28 | /**
29 | * We keep a history of tabs (each tab is present only once) and we switch to previous tab in history when we pop on root fragment
30 | */
31 | int UNIQUE_TAB_HISTORY = 1;
32 |
33 | /**
34 | * We keep an uncapped history of tabs and we switch to previous tab in history when we pop on root fragment
35 | */
36 | int UNLIMITED_TAB_HISTORY = 2;
37 |
38 | boolean popFragments(int popDepth, FragNavTransactionOptions transactionOptions);
39 |
40 | void switchTab(int index);
41 |
42 | void onSaveInstanceState(@NonNull Bundle outState);
43 |
44 | void restoreFromBundle(@Nullable Bundle savedInstanceState);
45 | }
46 |
--------------------------------------------------------------------------------
/frag-nav/src/main/java/com/ncapdevi/fragnav/tabhistory/UniqueTabHistoryController.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.fragnav.tabhistory;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import com.ncapdevi.fragnav.FragNavPopController;
6 | import com.ncapdevi.fragnav.FragNavSwitchController;
7 |
8 | import java.util.ArrayList;
9 | import java.util.LinkedHashSet;
10 | import java.util.Set;
11 |
12 | public class UniqueTabHistoryController extends CollectionFragNavTabHistoryController {
13 | private Set tabHistory = new LinkedHashSet<>();
14 |
15 | public UniqueTabHistoryController(FragNavPopController fragNavPopController,
16 | FragNavSwitchController fragNavSwitchController) {
17 | super(fragNavPopController, fragNavSwitchController);
18 | }
19 |
20 | @Override
21 | int getCollectionSize() {
22 | return tabHistory.size();
23 | }
24 |
25 | @Override
26 | int getAndRemoveIndex() {
27 | ArrayList tabList = getHistory();
28 | int currentPage = tabList.get(tabHistory.size() - 1);
29 | int targetPage = tabList.get(tabHistory.size() - 2);
30 | tabHistory.remove(currentPage);
31 | tabHistory.remove(targetPage);
32 | return targetPage;
33 | }
34 |
35 | @Override
36 | public void switchTab(int index) {
37 | tabHistory.remove(index);
38 | tabHistory.add(index);
39 | }
40 |
41 | @NonNull
42 | @Override
43 | ArrayList getHistory() {
44 | return new ArrayList<>(tabHistory);
45 | }
46 |
47 | @Override
48 | void setHistory(@NonNull ArrayList history) {
49 | tabHistory.clear();
50 | tabHistory.addAll(history);
51 | }
52 | }
53 |
--------------------------------------------------------------------------------
/frag-nav/src/main/java/com/ncapdevi/fragnav/tabhistory/UnlimitedTabHistoryController.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.fragnav.tabhistory;
2 |
3 | import android.support.annotation.NonNull;
4 |
5 | import com.ncapdevi.fragnav.FragNavPopController;
6 | import com.ncapdevi.fragnav.FragNavSwitchController;
7 |
8 | import java.util.ArrayList;
9 | import java.util.Stack;
10 |
11 | public class UnlimitedTabHistoryController extends CollectionFragNavTabHistoryController {
12 | private Stack tabHistory = new Stack<>();
13 |
14 | public UnlimitedTabHistoryController(FragNavPopController fragNavPopController,
15 | FragNavSwitchController fragNavSwitchController) {
16 | super(fragNavPopController, fragNavSwitchController);
17 | }
18 |
19 | @Override
20 | int getCollectionSize() {
21 | return tabHistory.size();
22 | }
23 |
24 | @Override
25 | int getAndRemoveIndex() {
26 | tabHistory.pop();
27 | return tabHistory.pop();
28 | }
29 |
30 | @Override
31 | public void switchTab(int index) {
32 | tabHistory.push(index);
33 | }
34 |
35 | @NonNull
36 | @Override
37 | ArrayList getHistory() {
38 | return new ArrayList<>(tabHistory);
39 | }
40 |
41 | @Override
42 | void setHistory(@NonNull ArrayList history) {
43 | tabHistory.clear();
44 | tabHistory.addAll(history);
45 | }
46 | }
47 |
--------------------------------------------------------------------------------
/frag-nav/src/main/res/values/strings.xml:
--------------------------------------------------------------------------------
1 |
2 | FragNav
3 |
4 |
--------------------------------------------------------------------------------
/frag-nav/src/test/java/com/ncapdevi/fragnav/FragNavTransactionOptionsTest.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.fragnav;
2 |
3 | import static org.junit.Assert.assertTrue;
4 |
5 | import android.support.annotation.AnimRes;
6 | import android.support.annotation.StyleRes;
7 | import android.support.v4.app.FragmentTransaction;
8 | import android.support.v4.util.Pair;
9 | import android.view.View;
10 |
11 | import org.junit.Test;
12 |
13 | /**
14 | * Created by niccapdevila on 2/15/17.
15 | */
16 |
17 | public class FragNavTransactionOptionsTest {
18 |
19 | @Test
20 | public void buildTransactionOptions() {
21 | String breadCrumbShortTitle = "Short Title";
22 | String breadCrumbTitle = "Long Title";
23 |
24 | @AnimRes
25 | int enterAnim = 1;
26 | @AnimRes
27 | int exitAnim = 2;
28 | @AnimRes
29 | int popEnterAnim = 3;
30 | @AnimRes
31 | int popExitAnim = 4;
32 |
33 | @StyleRes
34 | int transitionStyle = 5;
35 |
36 | FragNavTransactionOptions fragNavTransactionOptions = FragNavTransactionOptions.newBuilder()
37 | .breadCrumbShortTitle(breadCrumbShortTitle)
38 | .breadCrumbTitle(breadCrumbTitle)
39 | .transition(FragmentTransaction.TRANSIT_FRAGMENT_FADE)
40 | .customAnimations(enterAnim, exitAnim, popEnterAnim, popExitAnim)
41 | .transitionStyle(transitionStyle)
42 | .addSharedElement(new Pair(null, "test"))
43 | .addSharedElement(new Pair(null, "test2")).build();
44 |
45 | assertTrue(breadCrumbShortTitle.equalsIgnoreCase(fragNavTransactionOptions.breadCrumbShortTitle));
46 | assertTrue(breadCrumbTitle.equalsIgnoreCase(fragNavTransactionOptions.breadCrumbTitle));
47 |
48 | assertTrue(transitionStyle == fragNavTransactionOptions.transitionStyle);
49 |
50 | assertTrue(FragmentTransaction.TRANSIT_FRAGMENT_FADE == fragNavTransactionOptions.transition);
51 |
52 |
53 | assertTrue(enterAnim == fragNavTransactionOptions.enterAnimation);
54 | assertTrue(exitAnim == fragNavTransactionOptions.exitAnimation);
55 | assertTrue(popEnterAnim == fragNavTransactionOptions.popEnterAnimation);
56 | assertTrue(popExitAnim == fragNavTransactionOptions.popExitAnimation);
57 |
58 |
59 | assertTrue(fragNavTransactionOptions.sharedElements.size() == 2);
60 |
61 |
62 | }
63 | }
64 |
--------------------------------------------------------------------------------
/frag-nav/src/test/java/com/ncapdevi/fragnav/MockTest.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.fragnav;
2 |
3 | import android.annotation.SuppressLint;
4 | import android.os.Bundle;
5 | import android.support.v4.app.Fragment;
6 | import android.support.v4.app.FragmentManager;
7 | import android.support.v4.app.FragmentTransaction;
8 |
9 | import org.junit.Before;
10 | import org.junit.Ignore;
11 | import org.junit.Test;
12 | import org.junit.runner.RunWith;
13 | import org.mockito.Mock;
14 | import org.mockito.invocation.InvocationOnMock;
15 | import org.mockito.junit.MockitoJUnitRunner;
16 | import org.mockito.stubbing.Answer;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 | import static org.junit.Assert.assertEquals;
22 | import static org.junit.Assert.assertNotNull;
23 | import static org.junit.Assert.assertNull;
24 | import static org.junit.Assert.assertTrue;
25 | import static org.mockito.Matchers.any;
26 | import static org.mockito.Matchers.anyInt;
27 | import static org.mockito.Matchers.anyString;
28 | import static org.mockito.Mockito.doReturn;
29 | import static org.mockito.Mockito.mock;
30 | import static org.mockito.Mockito.when;
31 |
32 |
33 | @SuppressWarnings("ResourceType")
34 | @RunWith(MockitoJUnitRunner.class)
35 | public class MockTest implements FragNavController.TransactionListener {
36 |
37 | @Mock
38 | private FragmentManager mFragmentManager;
39 |
40 | @Mock
41 | private Bundle mBundle;
42 |
43 | @Mock
44 | private FragmentTransaction mFragmentTransaction;
45 |
46 | private List mFragmentList = new ArrayList<>(5);
47 | private FragNavController mFragNavController;
48 |
49 | @Before
50 | public void initMocks() {
51 | mockFragmentManager();
52 | mockFragmentTransaction();
53 | mFragNavController = FragNavController.newBuilder(mBundle, mFragmentManager, 1)
54 | .transactionListener(this)
55 | .rootFragment(mock(Fragment.class))
56 | .build();
57 |
58 | assertEquals(FragNavController.TAB1, mFragNavController.getCurrentStackIndex());
59 | assertNotNull(mFragNavController.getCurrentStack());
60 | }
61 |
62 | private void mockFragmentTransaction() {
63 | when(mFragmentTransaction.add(anyInt(), any(Fragment.class), anyString())).then(new Answer() {
64 | @Override
65 | public Object answer(InvocationOnMock invocation) throws Throwable {
66 | Object[] args = invocation.getArguments();
67 | mFragmentList.add((Fragment) args[1]);
68 | return mFragmentTransaction;
69 | }
70 | });
71 | }
72 |
73 | @SuppressLint("CommitTransaction")
74 | private void mockFragmentManager() {
75 | when(mFragmentManager.getFragments()).thenReturn(mFragmentList);
76 |
77 | when(mFragmentManager.beginTransaction()).thenReturn(mFragmentTransaction);
78 | }
79 |
80 | @Test
81 | public void testConstructionWhenMultipleFragments() {
82 | List rootFragments = new ArrayList<>();
83 | rootFragments.add(new Fragment());
84 | rootFragments.add(new Fragment());
85 |
86 | mFragNavController = FragNavController.newBuilder(null, mFragmentManager, 1)
87 | .rootFragments(rootFragments)
88 | .build();
89 |
90 | assertEquals(FragNavController.TAB1, mFragNavController.getCurrentStackIndex());
91 | assertNotNull(mFragNavController.getCurrentStack());
92 | }
93 |
94 | @Test(expected = IllegalArgumentException.class)
95 | public void testConstructionWhenTooManyRootFragments() {
96 | List rootFragments = new ArrayList<>();
97 |
98 | for (int i = 0; i < 21; i++) {
99 | rootFragments.add(new Fragment());
100 | }
101 |
102 | mFragNavController = FragNavController.newBuilder(null, mFragmentManager, 1)
103 | .rootFragments(rootFragments)
104 | .build();
105 | }
106 |
107 | @Test
108 | public void testConstructionWhenMultipleFragmentsAndNoTabSelected() {
109 | List rootFragments = new ArrayList<>();
110 | rootFragments.add(new Fragment());
111 | rootFragments.add(new Fragment());
112 |
113 | mFragNavController = FragNavController.newBuilder(null, mFragmentManager, 1)
114 | .rootFragments(rootFragments)
115 | .selectedTabIndex(FragNavController.NO_TAB)
116 | .build();
117 |
118 | assertEquals(FragNavController.NO_TAB, mFragNavController.getCurrentStackIndex());
119 | assertNull(mFragNavController.getCurrentStack());
120 | }
121 |
122 | @Test
123 | public void testConstructionWhenRootFragmentListenerAndTabSelected() {
124 | FragNavController.RootFragmentListener rootFragmentListener = mock(FragNavController.RootFragmentListener.class);
125 | doReturn(new Fragment()).when(rootFragmentListener).getRootFragment(anyInt());
126 |
127 | mFragNavController = FragNavController.newBuilder(null, mFragmentManager, 1)
128 | .rootFragmentListener(rootFragmentListener, 5)
129 | .selectedTabIndex(FragNavController.TAB3)
130 | .build();
131 |
132 | assertEquals(FragNavController.TAB3, mFragNavController.getCurrentStackIndex());
133 | assertNotNull(mFragNavController.getCurrentStack());
134 | }
135 |
136 | @Test(expected = IllegalArgumentException.class)
137 | public void testConstructionWhenRootFragmentListenerAndTooManyTabs() {
138 | FragNavController.RootFragmentListener rootFragmentListener = mock(FragNavController.RootFragmentListener.class);
139 |
140 | mFragNavController = FragNavController.newBuilder(null, mFragmentManager, 1)
141 | .rootFragmentListener(rootFragmentListener, 21)
142 | .selectedTabIndex(FragNavController.TAB3)
143 | .build();
144 | }
145 |
146 | @Test
147 | @Ignore // Install Robolectric in order to test restoring from Bundle.
148 | @SuppressWarnings("ConstantConditions")
149 | public void testConstructionWhenRestoringFromBundle() {
150 | List rootFragments = new ArrayList<>();
151 | rootFragments.add(new Fragment());
152 | rootFragments.add(new Fragment());
153 |
154 | mFragNavController = FragNavController.newBuilder(null, mFragmentManager, 1)
155 | .rootFragments(rootFragments)
156 | .selectedTabIndex(FragNavController.TAB1)
157 | .build();
158 |
159 | mFragNavController.switchTab(FragNavController.TAB2);
160 | mFragNavController.pushFragment(new Fragment());
161 | mFragNavController.pushFragment(new Fragment());
162 | mFragNavController.pushFragment(new Fragment());
163 |
164 | Fragment currentFragment = mFragNavController.getCurrentFrag();
165 |
166 | Bundle bundle = new Bundle();
167 |
168 | mFragNavController.onSaveInstanceState(bundle);
169 |
170 | mFragNavController = FragNavController.newBuilder(bundle, mFragmentManager, 1)
171 | .rootFragments(rootFragments)
172 | .selectedTabIndex(FragNavController.TAB1)
173 | .build();
174 |
175 | assertEquals(FragNavController.TAB2, mFragNavController.getCurrentStackIndex());
176 | assertEquals(4, mFragNavController.getCurrentStack().size());
177 | assertEquals(currentFragment, mFragNavController.getCurrentFrag());
178 | }
179 |
180 | @Test
181 | @SuppressWarnings("ConstantConditions")
182 | public void pushPopClear() {
183 |
184 | int size = mFragNavController.getCurrentStack().size();
185 |
186 | mFragNavController.pushFragment(mock(Fragment.class));
187 | assertTrue(mFragNavController.getCurrentStack().size() == ++size);
188 |
189 | mFragNavController.pushFragment(mock(Fragment.class));
190 | assertTrue(mFragNavController.getCurrentStack().size() == ++size);
191 |
192 | mFragNavController.pushFragment(mock(Fragment.class));
193 | assertTrue(mFragNavController.getCurrentStack().size() == ++size);
194 |
195 | mFragNavController.popFragment();
196 | assertTrue(mFragNavController.getCurrentStack().size() == --size);
197 |
198 | mFragNavController.clearStack();
199 | assertTrue(mFragNavController.getCurrentStack().size() == 1);
200 | assertTrue(mFragNavController.isRootFragment());
201 | }
202 |
203 | @Override
204 | public void onTabTransaction(Fragment fragment, int index) {
205 | assertNotNull(fragment);
206 | }
207 |
208 | @Override
209 | public void onFragmentTransaction(Fragment fragment, FragNavController.TransactionType transactionType) {
210 | assertNotNull(mFragNavController);
211 |
212 | }
213 | }
--------------------------------------------------------------------------------
/frag-nav/src/test/java/com/ncapdevi/fragnav/tabhistory/CurrentTabHistoryControllerTest.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.fragnav.tabhistory;
2 |
3 | import com.ncapdevi.fragnav.FragNavPopController;
4 | import com.ncapdevi.fragnav.FragNavTransactionOptions;
5 |
6 | import org.junit.Test;
7 | import org.junit.runner.RunWith;
8 | import org.mockito.Mock;
9 | import org.mockito.junit.MockitoJUnitRunner;
10 |
11 | import static org.mockito.ArgumentMatchers.eq;
12 | import static org.mockito.ArgumentMatchers.isNull;
13 | import static org.mockito.Mockito.times;
14 | import static org.mockito.Mockito.verify;
15 |
16 | @RunWith(MockitoJUnitRunner.class)
17 | public class CurrentTabHistoryControllerTest {
18 | @Mock
19 | private FragNavPopController mockFragNavPopController;
20 |
21 | @Test
22 | public void testPopDelegatedWhenPopCalled() {
23 | // Given
24 | CurrentTabHistoryController currentTabHistoryController = new CurrentTabHistoryController(
25 | mockFragNavPopController);
26 |
27 | // When
28 | currentTabHistoryController.popFragments(1, null);
29 |
30 | // Then
31 | verify(mockFragNavPopController, times(1)).tryPopFragments(eq(1), isNull(FragNavTransactionOptions.class));
32 | }
33 | }
--------------------------------------------------------------------------------
/frag-nav/src/test/java/com/ncapdevi/fragnav/tabhistory/UniqueTabHistoryControllerTest.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.fragnav.tabhistory;
2 |
3 | import android.os.Bundle;
4 |
5 | import com.ncapdevi.fragnav.FragNavPopController;
6 | import com.ncapdevi.fragnav.FragNavSwitchController;
7 | import com.ncapdevi.fragnav.FragNavTransactionOptions;
8 |
9 | import org.junit.Before;
10 | import org.junit.Test;
11 | import org.junit.runner.RunWith;
12 | import org.mockito.ArgumentCaptor;
13 | import org.mockito.Mock;
14 | import org.mockito.invocation.InvocationOnMock;
15 | import org.mockito.junit.MockitoJUnitRunner;
16 | import org.mockito.stubbing.Answer;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 | import static junit.framework.Assert.assertEquals;
22 | import static junit.framework.Assert.assertFalse;
23 | import static junit.framework.Assert.assertTrue;
24 | import static org.mockito.ArgumentMatchers.any;
25 | import static org.mockito.ArgumentMatchers.anyInt;
26 | import static org.mockito.ArgumentMatchers.anyString;
27 | import static org.mockito.ArgumentMatchers.eq;
28 | import static org.mockito.ArgumentMatchers.nullable;
29 | import static org.mockito.Mockito.doAnswer;
30 | import static org.mockito.Mockito.never;
31 | import static org.mockito.Mockito.times;
32 | import static org.mockito.Mockito.verify;
33 | import static org.mockito.Mockito.when;
34 |
35 | @RunWith(MockitoJUnitRunner.class)
36 | public class UniqueTabHistoryControllerTest {
37 | @Mock
38 | private FragNavPopController mockFragNavPopController;
39 |
40 | @Mock
41 | private FragNavSwitchController mockFragNavSwitchController;
42 |
43 | @Mock
44 | private Bundle mockBundle;
45 |
46 | @Mock
47 | private FragNavTransactionOptions mockTransactionOptions;
48 |
49 | private UniqueTabHistoryController uniqueTabHistoryController;
50 |
51 | @Before
52 | public void setUp() {
53 | uniqueTabHistoryController = new UniqueTabHistoryController(mockFragNavPopController,
54 | mockFragNavSwitchController);
55 | mockNavSwitchController(uniqueTabHistoryController);
56 | mockBundle();
57 | }
58 |
59 | @Test
60 | public void testNoSwitchWhenCurrentStackIsLargerThanPopCount() {
61 | // Given
62 | uniqueTabHistoryController.switchTab(1);
63 | uniqueTabHistoryController.switchTab(2);
64 | when(mockFragNavPopController.tryPopFragments(eq(1), eq(mockTransactionOptions))).thenReturn(1);
65 |
66 | // When
67 | boolean result = uniqueTabHistoryController.popFragments(1, mockTransactionOptions);
68 |
69 | // Then
70 | assertTrue(result);
71 | verify(mockFragNavSwitchController, never()).switchTab(anyInt(), nullable(FragNavTransactionOptions.class));
72 | }
73 |
74 | @Test
75 | public void testPopDoesNothingWhenPopIsCalledWithNothingToPopWithNoHistory() {
76 | // Given
77 | uniqueTabHistoryController.switchTab(1);
78 |
79 | // When
80 | boolean result = uniqueTabHistoryController.popFragments(1, mockTransactionOptions);
81 |
82 | // Then
83 | assertFalse(result);
84 | verify(mockFragNavSwitchController, never()).switchTab(anyInt(), nullable(FragNavTransactionOptions.class));
85 | }
86 |
87 | @Test
88 | public void testPopSwitchesTabWhenPopIsCalledWithNothingToPopAndHasHistory() {
89 | // Given
90 | uniqueTabHistoryController.switchTab(1);
91 | uniqueTabHistoryController.switchTab(2);
92 |
93 | // When
94 | boolean result = uniqueTabHistoryController.popFragments(1, mockTransactionOptions);
95 |
96 | // Then
97 | assertTrue(result);
98 | verify(mockFragNavSwitchController, times(1)).switchTab(eq(1), eq(mockTransactionOptions));
99 | }
100 |
101 | @Test
102 | public void testSwitchWhenCurrentStackIsNotLargerThanPopCount() {
103 | // Given
104 | uniqueTabHistoryController.switchTab(1);
105 | uniqueTabHistoryController.switchTab(2);
106 | when(mockFragNavPopController.tryPopFragments(eq(2), eq(mockTransactionOptions))).thenReturn(1);
107 |
108 | // When
109 | boolean result = uniqueTabHistoryController.popFragments(2, mockTransactionOptions);
110 |
111 | // Then
112 | assertTrue(result);
113 | verify(mockFragNavSwitchController, times(1)).switchTab(eq(1), eq(mockTransactionOptions));
114 | }
115 |
116 | @Test
117 | public void testTabsUniquelyRollbackWhenPopAllAvailableItemsInOneStep() {
118 | // Given
119 |
120 | // Navigating ahead through tabs
121 | for (int i = 1; i < 6; i++) {
122 | uniqueTabHistoryController.switchTab(i);
123 | }
124 |
125 | // Navigating backwards through tabs
126 | for (int i = 5; i > 0; i--) {
127 | uniqueTabHistoryController.switchTab(i);
128 | }
129 |
130 | // Every tab contains 1 item
131 | for (int i = 7; i < 16; i += 2) {
132 | when(mockFragNavPopController.tryPopFragments(eq(i), eq(mockTransactionOptions))).thenReturn(1);
133 | }
134 |
135 | // When
136 | boolean result = uniqueTabHistoryController.popFragments(15, mockTransactionOptions);
137 |
138 | // Then
139 | assertTrue(result);
140 | ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class);
141 | verify(mockFragNavSwitchController, times(4)).switchTab(argumentCaptor.capture(), eq(mockTransactionOptions));
142 | List allValues = argumentCaptor.getAllValues();
143 |
144 | // The history is [2, 3, 4, 5]
145 | for (int i = 1; i < 5; i++) {
146 | assertEquals((int) allValues.get(i - 1), i + 1);
147 | }
148 | }
149 |
150 | @Test
151 | public void testTabsUniquelyRollbackWhenPopAllAvailableItemsInDifferentSteps() {
152 | // Given
153 |
154 | // Navigating ahead through tabs
155 | for (int i = 1; i < 6; i++) {
156 | uniqueTabHistoryController.switchTab(i);
157 | }
158 |
159 | // Navigating backwards through tabs
160 | for (int i = 5; i > 0; i--) {
161 | uniqueTabHistoryController.switchTab(i);
162 | }
163 |
164 | // When
165 | for (int i = 0; i < 4; i++) {
166 | assertTrue(uniqueTabHistoryController.popFragments(1, mockTransactionOptions));
167 | }
168 | assertFalse(uniqueTabHistoryController.popFragments(1, mockTransactionOptions));
169 |
170 | // Then
171 | ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class);
172 | verify(mockFragNavSwitchController, times(4)).switchTab(argumentCaptor.capture(), eq(mockTransactionOptions));
173 | List allValues = argumentCaptor.getAllValues();
174 |
175 | // The history is [2, 3, 4, 5]
176 | for (int i = 1; i < 5; i++) {
177 | assertEquals((int) allValues.get(i - 1), i + 1);
178 | }
179 | }
180 |
181 | @Test
182 | public void testHistoryIsSavedAndRestoredWhenSaveCalledNewInstanceCreatedRestoreCalled() {
183 | // Given
184 | for (int i = 5; i > 0; i--) {
185 | uniqueTabHistoryController.switchTab(i);
186 | }
187 |
188 | // When
189 | uniqueTabHistoryController.onSaveInstanceState(mockBundle);
190 | UniqueTabHistoryController newUniqueTabHistoryController = new UniqueTabHistoryController(
191 | mockFragNavPopController,
192 | mockFragNavSwitchController);
193 | mockNavSwitchController(newUniqueTabHistoryController);
194 | newUniqueTabHistoryController.restoreFromBundle(mockBundle);
195 | for (int i = 0; i < 4; i++) {
196 | assertTrue(newUniqueTabHistoryController.popFragments(1, mockTransactionOptions));
197 | }
198 |
199 | // Then
200 | ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class);
201 | verify(mockFragNavSwitchController, times(4)).switchTab(argumentCaptor.capture(), eq(mockTransactionOptions));
202 | List allValues = argumentCaptor.getAllValues();
203 |
204 | // The history is [2, 3, 4, 5]
205 | for (int i = 1; i < 5; i++) {
206 | assertEquals((int) allValues.get(i - 1), i + 1);
207 | }
208 | }
209 |
210 | private void mockNavSwitchController(final UniqueTabHistoryController uniqueTabHistoryController) {
211 | doAnswer(new Answer() {
212 | @Override
213 | public Object answer(InvocationOnMock invocation) throws Throwable {
214 | uniqueTabHistoryController.switchTab((Integer) invocation.getArgument(0));
215 | return null;
216 | }
217 | }).when(mockFragNavSwitchController).switchTab(anyInt(), nullable(FragNavTransactionOptions.class));
218 | }
219 |
220 | @SuppressWarnings("unchecked")
221 | private void mockBundle() {
222 | final ArrayList storage = new ArrayList<>();
223 | doAnswer(new Answer() {
224 | @Override
225 | public Object answer(InvocationOnMock invocation) throws Throwable {
226 | storage.clear();
227 | storage.addAll(((ArrayList) invocation.getArgument(1)));
228 | return null;
229 | }
230 | }).when(mockBundle).putIntegerArrayList(anyString(), any(ArrayList.class));
231 | doAnswer(new Answer() {
232 | @Override
233 | public Object answer(InvocationOnMock invocation) throws Throwable {
234 | if (storage.size() > 0) {
235 | return storage;
236 | }
237 | return null;
238 | }
239 | }).when(mockBundle).getIntegerArrayList(anyString());
240 | }
241 | }
--------------------------------------------------------------------------------
/frag-nav/src/test/java/com/ncapdevi/fragnav/tabhistory/UnlimitedTabHistoryControllerTest.java:
--------------------------------------------------------------------------------
1 | package com.ncapdevi.fragnav.tabhistory;
2 |
3 | import android.os.Bundle;
4 |
5 | import com.ncapdevi.fragnav.FragNavPopController;
6 | import com.ncapdevi.fragnav.FragNavSwitchController;
7 | import com.ncapdevi.fragnav.FragNavTransactionOptions;
8 |
9 | import org.junit.Before;
10 | import org.junit.Test;
11 | import org.junit.runner.RunWith;
12 | import org.mockito.ArgumentCaptor;
13 | import org.mockito.Mock;
14 | import org.mockito.invocation.InvocationOnMock;
15 | import org.mockito.junit.MockitoJUnitRunner;
16 | import org.mockito.stubbing.Answer;
17 |
18 | import java.util.ArrayList;
19 | import java.util.List;
20 |
21 | import static junit.framework.Assert.assertEquals;
22 | import static junit.framework.Assert.assertFalse;
23 | import static junit.framework.Assert.assertTrue;
24 | import static org.mockito.ArgumentMatchers.any;
25 | import static org.mockito.ArgumentMatchers.anyInt;
26 | import static org.mockito.ArgumentMatchers.anyString;
27 | import static org.mockito.ArgumentMatchers.eq;
28 | import static org.mockito.ArgumentMatchers.nullable;
29 | import static org.mockito.Mockito.doAnswer;
30 | import static org.mockito.Mockito.never;
31 | import static org.mockito.Mockito.times;
32 | import static org.mockito.Mockito.verify;
33 | import static org.mockito.Mockito.when;
34 |
35 | @RunWith(MockitoJUnitRunner.class)
36 | public class UnlimitedTabHistoryControllerTest {
37 | @Mock
38 | private FragNavPopController mockFragNavPopController;
39 |
40 | @Mock
41 | private FragNavSwitchController mockFragNavSwitchController;
42 |
43 | @Mock
44 | private Bundle mockBundle;
45 |
46 | @Mock
47 | private FragNavTransactionOptions mockTransactionOptions;
48 |
49 | private UnlimitedTabHistoryController unlimitedTabHistoryController;
50 |
51 | @Before
52 | public void setUp() {
53 | unlimitedTabHistoryController = new UnlimitedTabHistoryController(mockFragNavPopController,
54 | mockFragNavSwitchController);
55 | mockNavSwitchController(unlimitedTabHistoryController);
56 | mockBundle();
57 | }
58 |
59 | @Test
60 | public void testNoSwitchWhenCurrentStackIsLargerThanPopCount() {
61 | // Given
62 | unlimitedTabHistoryController.switchTab(1);
63 | unlimitedTabHistoryController.switchTab(2);
64 | when(mockFragNavPopController.tryPopFragments(eq(1), eq(mockTransactionOptions))).thenReturn(1);
65 |
66 | // When
67 | boolean result = unlimitedTabHistoryController.popFragments(1, mockTransactionOptions);
68 |
69 | // Then
70 | assertTrue(result);
71 | verify(mockFragNavSwitchController, never()).switchTab(anyInt(), nullable(FragNavTransactionOptions.class));
72 | }
73 |
74 | @Test
75 | public void testPopDoesNothingWhenPopIsCalledWithNothingToPopWithNoHistory() {
76 | // Given
77 | unlimitedTabHistoryController.switchTab(1);
78 |
79 | // When
80 | boolean result = unlimitedTabHistoryController.popFragments(1, mockTransactionOptions);
81 |
82 | // Then
83 | assertFalse(result);
84 | verify(mockFragNavSwitchController, never()).switchTab(anyInt(), nullable(FragNavTransactionOptions.class));
85 | }
86 |
87 | @Test
88 | public void testPopSwitchesTabWhenPopIsCalledWithNothingToPopAndHasHistory() {
89 | // Given
90 | unlimitedTabHistoryController.switchTab(1);
91 | unlimitedTabHistoryController.switchTab(2);
92 |
93 | // When
94 | boolean result = unlimitedTabHistoryController.popFragments(1, mockTransactionOptions);
95 |
96 | // Then
97 | assertTrue(result);
98 | verify(mockFragNavSwitchController, times(1)).switchTab(eq(1), eq(mockTransactionOptions));
99 | }
100 |
101 | @Test
102 | public void testSwitchWhenCurrentStackIsNotLargerThanPopCount() {
103 | // Given
104 | unlimitedTabHistoryController.switchTab(1);
105 | unlimitedTabHistoryController.switchTab(2);
106 | when(mockFragNavPopController.tryPopFragments(eq(2), eq(mockTransactionOptions))).thenReturn(1);
107 |
108 | // When
109 | boolean result = unlimitedTabHistoryController.popFragments(2, mockTransactionOptions);
110 |
111 | // Then
112 | assertTrue(result);
113 | verify(mockFragNavSwitchController, times(1)).switchTab(eq(1), eq(mockTransactionOptions));
114 | }
115 |
116 | @Test
117 | public void testTabsUnlimitedRollbackWhenPopAllAvailableItemsInOneStep() {
118 | // Given
119 |
120 | // Navigating ahead through tabs
121 | for (int i = 1; i < 6; i++) {
122 | unlimitedTabHistoryController.switchTab(i);
123 | }
124 |
125 | // Navigating backwards through tabs
126 | for (int i = 5; i > 0; i--) {
127 | unlimitedTabHistoryController.switchTab(i);
128 | }
129 |
130 | // Every tab contains 1 item
131 | for (int i = 7; i < 16; i += 2) {
132 | when(mockFragNavPopController.tryPopFragments(eq(i), eq(mockTransactionOptions))).thenReturn(1);
133 | }
134 |
135 | // When
136 | boolean result = unlimitedTabHistoryController.popFragments(15, mockTransactionOptions);
137 |
138 | // Then
139 | assertTrue(result);
140 | ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class);
141 | verify(mockFragNavSwitchController, times(9)).switchTab(argumentCaptor.capture(), eq(mockTransactionOptions));
142 | List allValues = argumentCaptor.getAllValues();
143 |
144 | // The history is [2, 3, 4, 5, 5, 4, 3, 2, 1]
145 | for (int i = 1; i < 5; i++) {
146 | assertEquals((int) allValues.get(i - 1), i + 1);
147 | }
148 | for (int i = 4; i < 9; i++) {
149 | assertEquals((int) allValues.get(i), 9 - i);
150 | }
151 | }
152 |
153 | @Test
154 | public void testTabsUnlimitedRollbackWhenPopAllAvailableItemsInDifferentSteps() {
155 | // Given
156 |
157 | // Navigating ahead through tabs
158 | for (int i = 1; i < 6; i++) {
159 | unlimitedTabHistoryController.switchTab(i);
160 | }
161 |
162 | // Navigating backwards through tabs
163 | for (int i = 5; i > 0; i--) {
164 | unlimitedTabHistoryController.switchTab(i);
165 | }
166 |
167 | // When
168 | for (int i = 0; i < 9; i++) {
169 | assertTrue(unlimitedTabHistoryController.popFragments(1, mockTransactionOptions));
170 | }
171 | assertFalse(unlimitedTabHistoryController.popFragments(1, mockTransactionOptions));
172 |
173 | // Then
174 | ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class);
175 | verify(mockFragNavSwitchController, times(9)).switchTab(argumentCaptor.capture(), eq(mockTransactionOptions));
176 | List allValues = argumentCaptor.getAllValues();
177 |
178 | // The history is [2, 3, 4, 5, 5, 4, 3, 2, 1]
179 | for (int i = 1; i < 5; i++) {
180 | assertEquals((int) allValues.get(i - 1), i + 1);
181 | }
182 | for (int i = 4; i < 9; i++) {
183 | assertEquals((int) allValues.get(i), 9 - i);
184 | }
185 | }
186 |
187 | @Test
188 | public void testHistoryIsSavedAndRestoredWhenSaveCalledNewInstanceCreatedRestoreCalled() {
189 | // Given
190 | for (int i = 5; i > 0; i--) {
191 | unlimitedTabHistoryController.switchTab(i);
192 | }
193 |
194 | // When
195 | unlimitedTabHistoryController.onSaveInstanceState(mockBundle);
196 | UnlimitedTabHistoryController newUniqueTabHistoryController = new UnlimitedTabHistoryController(
197 | mockFragNavPopController,
198 | mockFragNavSwitchController);
199 | mockNavSwitchController(newUniqueTabHistoryController);
200 | newUniqueTabHistoryController.restoreFromBundle(mockBundle);
201 | for (int i = 0; i < 4; i++) {
202 | assertTrue(newUniqueTabHistoryController.popFragments(1, mockTransactionOptions));
203 | }
204 |
205 | // Then
206 | ArgumentCaptor argumentCaptor = ArgumentCaptor.forClass(Integer.class);
207 | verify(mockFragNavSwitchController, times(4)).switchTab(argumentCaptor.capture(), eq(mockTransactionOptions));
208 | List allValues = argumentCaptor.getAllValues();
209 |
210 | // The history is [2, 3, 4, 5]
211 | for (int i = 1; i < 5; i++) {
212 | assertEquals((int) allValues.get(i - 1), i + 1);
213 | }
214 | }
215 |
216 | private void mockNavSwitchController(final UnlimitedTabHistoryController uniqueTabHistoryController) {
217 | doAnswer(new Answer() {
218 | @Override
219 | public Object answer(InvocationOnMock invocation) throws Throwable {
220 | uniqueTabHistoryController.switchTab((Integer) invocation.getArgument(0));
221 | return null;
222 | }
223 | }).when(mockFragNavSwitchController).switchTab(anyInt(), nullable(FragNavTransactionOptions.class));
224 | }
225 |
226 | @SuppressWarnings("unchecked")
227 | private void mockBundle() {
228 | final ArrayList storage = new ArrayList<>();
229 | doAnswer(new Answer() {
230 | @Override
231 | public Object answer(InvocationOnMock invocation) throws Throwable {
232 | storage.clear();
233 | storage.addAll(((ArrayList) invocation.getArgument(1)));
234 | return null;
235 | }
236 | }).when(mockBundle).putIntegerArrayList(anyString(), any(ArrayList.class));
237 | doAnswer(new Answer() {
238 | @Override
239 | public Object answer(InvocationOnMock invocation) throws Throwable {
240 | if (storage.size() > 0) {
241 | return storage;
242 | }
243 | return null;
244 | }
245 | }).when(mockBundle).getIntegerArrayList(anyString());
246 | }
247 | }
--------------------------------------------------------------------------------
/gradle.properties:
--------------------------------------------------------------------------------
1 | # Project-wide Gradle settings.
2 |
3 | # IDE (e.g. Android Studio) users:
4 | # Gradle settings configured through the IDE *will override*
5 | # any settings specified in this file.
6 |
7 | # For more details on how to configure your build environment visit
8 | # http://www.gradle.org/docs/current/userguide/build_environment.html
9 |
10 | # Specifies the JVM arguments used for the daemon process.
11 | # The setting is particularly useful for tweaking memory settings.
12 | # Default value: -Xmx10248m -XX:MaxPermSize=256m
13 | # org.gradle.jvmargs=-Xmx2048m -XX:MaxPermSize=512m -XX:+HeapDumpOnOutOfMemoryError -Dfile.encoding=UTF-8
14 |
15 | # When configured, Gradle will run in incubating parallel mode.
16 | # This option should only be used with decoupled projects. More details, visit
17 | # http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
18 | # org.gradle.parallel=true
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.jar:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/prashant31191/FragNav/87def8b102e9c61059cf773c4d2db2fef8d03263/gradle/wrapper/gradle-wrapper.jar
--------------------------------------------------------------------------------
/gradle/wrapper/gradle-wrapper.properties:
--------------------------------------------------------------------------------
1 | #Thu Sep 07 16:39:57 PDT 2017
2 | distributionBase=GRADLE_USER_HOME
3 | distributionPath=wrapper/dists
4 | zipStoreBase=GRADLE_USER_HOME
5 | zipStorePath=wrapper/dists
6 | distributionUrl=https\://services.gradle.org/distributions/gradle-4.4.1-all.zip
7 |
--------------------------------------------------------------------------------
/gradlew:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env sh
2 |
3 | ##############################################################################
4 | ##
5 | ## Gradle start up script for UN*X
6 | ##
7 | ##############################################################################
8 |
9 | # Attempt to set APP_HOME
10 | # Resolve links: $0 may be a link
11 | PRG="$0"
12 | # Need this for relative symlinks.
13 | while [ -h "$PRG" ] ; do
14 | ls=`ls -ld "$PRG"`
15 | link=`expr "$ls" : '.*-> \(.*\)$'`
16 | if expr "$link" : '/.*' > /dev/null; then
17 | PRG="$link"
18 | else
19 | PRG=`dirname "$PRG"`"/$link"
20 | fi
21 | done
22 | SAVED="`pwd`"
23 | cd "`dirname \"$PRG\"`/" >/dev/null
24 | APP_HOME="`pwd -P`"
25 | cd "$SAVED" >/dev/null
26 |
27 | APP_NAME="Gradle"
28 | APP_BASE_NAME=`basename "$0"`
29 |
30 | # Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
31 | DEFAULT_JVM_OPTS=""
32 |
33 | # Use the maximum available, or set MAX_FD != -1 to use that value.
34 | MAX_FD="maximum"
35 |
36 | warn ( ) {
37 | echo "$*"
38 | }
39 |
40 | die ( ) {
41 | echo
42 | echo "$*"
43 | echo
44 | exit 1
45 | }
46 |
47 | # OS specific support (must be 'true' or 'false').
48 | cygwin=false
49 | msys=false
50 | darwin=false
51 | nonstop=false
52 | case "`uname`" in
53 | CYGWIN* )
54 | cygwin=true
55 | ;;
56 | Darwin* )
57 | darwin=true
58 | ;;
59 | MINGW* )
60 | msys=true
61 | ;;
62 | NONSTOP* )
63 | nonstop=true
64 | ;;
65 | esac
66 |
67 | CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
68 |
69 | # Determine the Java command to use to start the JVM.
70 | if [ -n "$JAVA_HOME" ] ; then
71 | if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
72 | # IBM's JDK on AIX uses strange locations for the executables
73 | JAVACMD="$JAVA_HOME/jre/sh/java"
74 | else
75 | JAVACMD="$JAVA_HOME/bin/java"
76 | fi
77 | if [ ! -x "$JAVACMD" ] ; then
78 | die "ERROR: JAVA_HOME is set to an invalid directory: $JAVA_HOME
79 |
80 | Please set the JAVA_HOME variable in your environment to match the
81 | location of your Java installation."
82 | fi
83 | else
84 | JAVACMD="java"
85 | which java >/dev/null 2>&1 || die "ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
86 |
87 | Please set the JAVA_HOME variable in your environment to match the
88 | location of your Java installation."
89 | fi
90 |
91 | # Increase the maximum file descriptors if we can.
92 | if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
93 | MAX_FD_LIMIT=`ulimit -H -n`
94 | if [ $? -eq 0 ] ; then
95 | if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
96 | MAX_FD="$MAX_FD_LIMIT"
97 | fi
98 | ulimit -n $MAX_FD
99 | if [ $? -ne 0 ] ; then
100 | warn "Could not set maximum file descriptor limit: $MAX_FD"
101 | fi
102 | else
103 | warn "Could not query maximum file descriptor limit: $MAX_FD_LIMIT"
104 | fi
105 | fi
106 |
107 | # For Darwin, add options to specify how the application appears in the dock
108 | if $darwin; then
109 | GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
110 | fi
111 |
112 | # For Cygwin, switch paths to Windows format before running java
113 | if $cygwin ; then
114 | APP_HOME=`cygpath --path --mixed "$APP_HOME"`
115 | CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
116 | JAVACMD=`cygpath --unix "$JAVACMD"`
117 |
118 | # We build the pattern for arguments to be converted via cygpath
119 | ROOTDIRSRAW=`find -L / -maxdepth 1 -mindepth 1 -type d 2>/dev/null`
120 | SEP=""
121 | for dir in $ROOTDIRSRAW ; do
122 | ROOTDIRS="$ROOTDIRS$SEP$dir"
123 | SEP="|"
124 | done
125 | OURCYGPATTERN="(^($ROOTDIRS))"
126 | # Add a user-defined pattern to the cygpath arguments
127 | if [ "$GRADLE_CYGPATTERN" != "" ] ; then
128 | OURCYGPATTERN="$OURCYGPATTERN|($GRADLE_CYGPATTERN)"
129 | fi
130 | # Now convert the arguments - kludge to limit ourselves to /bin/sh
131 | i=0
132 | for arg in "$@" ; do
133 | CHECK=`echo "$arg"|egrep -c "$OURCYGPATTERN" -`
134 | CHECK2=`echo "$arg"|egrep -c "^-"` ### Determine if an option
135 |
136 | if [ $CHECK -ne 0 ] && [ $CHECK2 -eq 0 ] ; then ### Added a condition
137 | eval `echo args$i`=`cygpath --path --ignore --mixed "$arg"`
138 | else
139 | eval `echo args$i`="\"$arg\""
140 | fi
141 | i=$((i+1))
142 | done
143 | case $i in
144 | (0) set -- ;;
145 | (1) set -- "$args0" ;;
146 | (2) set -- "$args0" "$args1" ;;
147 | (3) set -- "$args0" "$args1" "$args2" ;;
148 | (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
149 | (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
150 | (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
151 | (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
152 | (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
153 | (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
154 | esac
155 | fi
156 |
157 | # Escape application args
158 | save ( ) {
159 | for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
160 | echo " "
161 | }
162 | APP_ARGS=$(save "$@")
163 |
164 | # Collect all arguments for the java command, following the shell quoting and substitution rules
165 | eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
166 |
167 | # by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
168 | if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
169 | cd "$(dirname "$0")"
170 | fi
171 |
172 | exec "$JAVACMD" "$@"
173 |
--------------------------------------------------------------------------------
/gradlew.bat:
--------------------------------------------------------------------------------
1 | @if "%DEBUG%" == "" @echo off
2 | @rem ##########################################################################
3 | @rem
4 | @rem Gradle startup script for Windows
5 | @rem
6 | @rem ##########################################################################
7 |
8 | @rem Set local scope for the variables with windows NT shell
9 | if "%OS%"=="Windows_NT" setlocal
10 |
11 | set DIRNAME=%~dp0
12 | if "%DIRNAME%" == "" set DIRNAME=.
13 | set APP_BASE_NAME=%~n0
14 | set APP_HOME=%DIRNAME%
15 |
16 | @rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
17 | set DEFAULT_JVM_OPTS=
18 |
19 | @rem Find java.exe
20 | if defined JAVA_HOME goto findJavaFromJavaHome
21 |
22 | set JAVA_EXE=java.exe
23 | %JAVA_EXE% -version >NUL 2>&1
24 | if "%ERRORLEVEL%" == "0" goto init
25 |
26 | echo.
27 | echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
28 | echo.
29 | echo Please set the JAVA_HOME variable in your environment to match the
30 | echo location of your Java installation.
31 |
32 | goto fail
33 |
34 | :findJavaFromJavaHome
35 | set JAVA_HOME=%JAVA_HOME:"=%
36 | set JAVA_EXE=%JAVA_HOME%/bin/java.exe
37 |
38 | if exist "%JAVA_EXE%" goto init
39 |
40 | echo.
41 | echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
42 | echo.
43 | echo Please set the JAVA_HOME variable in your environment to match the
44 | echo location of your Java installation.
45 |
46 | goto fail
47 |
48 | :init
49 | @rem Get command-line arguments, handling Windows variants
50 |
51 | if not "%OS%" == "Windows_NT" goto win9xME_args
52 |
53 | :win9xME_args
54 | @rem Slurp the command line arguments.
55 | set CMD_LINE_ARGS=
56 | set _SKIP=2
57 |
58 | :win9xME_args_slurp
59 | if "x%~1" == "x" goto execute
60 |
61 | set CMD_LINE_ARGS=%*
62 |
63 | :execute
64 | @rem Setup the command line
65 |
66 | set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
67 |
68 | @rem Execute Gradle
69 | "%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
70 |
71 | :end
72 | @rem End local scope for the variables with windows NT shell
73 | if "%ERRORLEVEL%"=="0" goto mainEnd
74 |
75 | :fail
76 | rem Set variable GRADLE_EXIT_CONSOLE if you need the _script_ return code instead of
77 | rem the _cmd.exe /c_ return code!
78 | if not "" == "%GRADLE_EXIT_CONSOLE%" exit 1
79 | exit /b 1
80 |
81 | :mainEnd
82 | if "%OS%"=="Windows_NT" endlocal
83 |
84 | :omega
85 |
--------------------------------------------------------------------------------
/settings.gradle:
--------------------------------------------------------------------------------
1 | include ':app', ':frag-nav'
2 |
--------------------------------------------------------------------------------