How to save state of Fragments in ViewPager when orientation changes - android

I have an application that Implements ActionBarSherlock, it's mainly composed of
ViewPager with different content of fragments (for each page), with ViewPager indicator.
Here is my onCreate Method of the Activity that extends SherlockFragmentActivity
// Assume that tabs,sources,types are string arrays e.g. tabs = ["tab1","tab2","tab3"]
// types = ["listview","listview","webview"]
// according to the type of each tab, the type of content of the fragment associated to that tab is determined (e.g. WebView or ListView)
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
super.setContentView(R.layout.main);
srcContext = getBaseContext();
srcActivity = SaudiActivity.this;
int selectPos = 0;
Intent sender = getIntent();
FragmentPagerAdapter mAdapter = new NewsListAdapter(
getSupportFragmentManager());
ViewPager pager = (ViewPager) findViewById(R.id.pager);
pager.setAdapter(mAdapter);
TabPageIndicator indicator = (TabPageIndicator) findViewById(R.id.indicator);
indicator.setViewPager(pager);
pager.setOffscreenPageLimit(tabs.length);
pager.setCurrentItem((tabs.length - 1));
currentPosition = (tabs.length - 1);
indicator.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
#Override
public void onPageSelected(int position) {
currentPosition = position;
if ((type[position] == "listview" || type[position]
.equals("listview")) && loaded[position] == false) {
loaded[position] = true;
getNews(listViews[position], position);
}
}
#Override
public void onPageScrolled(int position, float positionOffset,
int positionOffsetPixels) {
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
getSupportActionBar().setCustomView(R.layout.logo);
getSupportActionBar().setDisplayShowCustomEnabled(true);
getSupportActionBar().setDisplayHomeAsUpEnabled(false);
getSupportActionBar().setDisplayShowTitleEnabled(false);
getSupportActionBar().setDisplayShowHomeEnabled(false);
Context ctx = getSupportActionBar().getThemedContext();
SourcesAdapter adapter = new SourcesAdapter(ctx,
R.layout.navigation_list_item, src_name, icons, src_value);
}
Here are my News List Adapter (that creates the fragments),
public static class MyViewHolder {
public TextView title;
public ImageView icon;
}
class NewsListAdapter extends FragmentPagerAdapter {
public NewsListAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
return new NewsFragment(MyActivity.this, position);
}
// This is the number of pages -- 5
#Override
public int getCount() {
return tabs.length;
}
// This is the title of the page that will apppear on the "tab"
public CharSequence getPageTitle(int position) {
return tabs[position];
}
}
public List<NewsItem> NewsItems;
Finally here's my NewsFragment:
public static class NewsFragment extends Fragment {
private int position;
public NewsFragment() {
}
public NewsFragment(Context ctx, int pos) {
this.position = pos;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = null;
if (type[position].equals("listview")) {
// Put a ListView in the Fragment
} else {
// Put a WebView in the Fragment
}
return view;
}
#Override
public void onSaveInstanceState(Bundle outState) {
super.onSaveInstanceState(outState);
}
}

Let the fragment handle it's state by overriding onSaveInstanceState and placing the necessary values into outState. Recover that state in onCreateView using savedInstanceState. Also I'm pretty sure that if you're using Actionbarsherlock fragments need to extend SherlockFragment rather than Fragment.

Related

get the hidden textview value in fragment from activity onpageselected event android

I want to get the hidden value which is set in Fragment Page in order to work on it. I am setting the hidden value in Fragment Page and want to get that hidden value in the activity page's Viewpager onPageSelected value in oreder to update the fragment with detail information. Is this possible?
My Fragment page
public class CountryFragment extends Fragment {
public static final String STARTUP_ID = "startupID";
private String st_id;
public CountryFragment() {
// emtpy
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
String st_id = getArguments().getString(STARTUP_ID);
this.st_id = st_id;
View v = inflater.inflate(R.layout.country_fragment, container, false);
TextView hiddenstatupid= (TextView) v.findViewById(R.id.hiddenstatuptextview);
hiddenstatupid.setText(st_id);
return v;
}
}
Activity Page
public class IndividualPage extends ActionBarActivity{
ViewPager pager;
#Override
public void onCreate (Bundle savedInstanceState)
{
pager = (ViewPager)findViewById(R.id.viewpager);
pager.setOnPageChangeListener(new ViewPager.OnPageChangeListener() {
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {
}
#Override
public void onPageSelected(int position) {
//here is where i need to get the hidden id of startup which is in the countryfragment
}
#Override
public void onPageScrollStateChanged(int state) {
}
});
My CountryPageAdapter
public class CountryPageAdapter extends FragmentPagerAdapter {
public CountryPageAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
mFragmentTags = new HashMap<Integer, String>();
}
#Override
public Fragment getItem(int position) {
return this.fragments.get(position);
}
}
Iy your CountryFragment part of the viewpager? If so, then you can have a local store of your fragments in your pager adapter and then get the fragment from the viewpager adapter using the position.
If your adapter extends FragmentStatePagerAdapter you have to implement the getItem() method.
You will have something like this:
#Override
public void onPageSelected(int position) {
CountryFragment frag = pager.getAdapter().getItem(position);
}
Hope this helps.

ViewPager recreate fragments all the time

I am using ViewPager with my fragments. All fragments has animation and it does not work when switching back maybe because ViewPager saves somehow state of near Fragments, do you have some idea how to avoid it?
getItemPosition() doesn't make anything in this case
private class MyFragmentPagerAdapter extends FragmentPagerAdapter {
public MyFragmentPagerAdapter(FragmentManager fm) {
super(fm);
}
#Override
public CharSequence getPageTitle(int position) {
return null;
}
#Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return GetStartedOneFragment.newInstance();
case 1:
return GetStartedTwoFragment.newInstance();
case 2:
return GetStartedThreeFragment.newInstance();
case 3:
return GetStartedFourFragment.newInstance();
case 4:
return GetStartedFiveFragment.newInstance();
case 5:
return GetStartedSixFragment.newInstance();
default:
return GetStartedOneFragment.newInstance();
}
}
#Override
public int getCount() {
return PAGE_COUNT;
}
#Override
public int getItemPosition(Object object) {
return POSITION_NONE;
}
}
I used a Fragment as a slider content, see the sample code below. Feel free to ask more details if required.
I have 4 flight modes in the example. The ViewPager is used with the associated adapter (ScreenSlidePagerAdapter).
You can see the app in the Play Store to see the slider (search "Flight Recorder 24").
// In the main fragment java code
private View headerView;
private ViewPager mPager;
public ScreenSlidePagerAdapter mPagerAdapter;
// ...
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState)
{
super.onCreateView(inflater, container, savedInstanceState);
View mainView = inflater.inflate(R.layout.monitoring_fragment,container, false);
// The header of the list
View view = inflater.inflate(R.layout.header_monitoring_fragment, mListView, false);
headerView = view;
//...
mPager = (ViewPager) view.findViewById(R.id.pager);
FragmentManager fm = getChildFragmentManager();
mPagerAdapter = new ScreenSlidePagerAdapter(fm);
mPager.setAdapter(mPagerAdapter);
mPager.setCurrentItem(UserConfiguration.getUserConf().getPositionFromFlightMode());
// Attach the page change listener inside the activity
mPager.setOnPageChangeListener(new ViewPager.OnPageChangeListener()
{
// This method will be invoked when a new page becomes selected.
#Override
public void onPageSelected(int position)
{
int flight_mode = UserConfiguration.getFlightModeFromPosition(position);
UserConfiguration.getUserConf().setFlightMode(flight_mode);
}
// This method will be invoked when the current page is scrolled
#Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels)
{
// Code goes here
}
// Called when the scroll state changes:
// SCROLL_STATE_IDLE, SCROLL_STATE_DRAGGING, SCROLL_STATE_SETTLING
#Override
public void onPageScrollStateChanged(int state)
{
// Code goes here
}
});
// ...
}
// ...
// Slider adapter
private class ScreenSlidePagerAdapter extends FragmentPagerAdapter
{
public ScreenSlidePagerAdapter(FragmentManager fm)
{
super(fm);
}
#Override
public Fragment getItem(int position)
{
// position to flight mode
int flight_mode = UserConfiguration.getFlightModeFromPosition(position);
FlightModeFragment flightModeFragment = FlightModeFragment.newInstance(flight_mode);
return flightModeFragment;
}
public int getItemPosition(Object object)
{
return POSITION_NONE;
}
#Override
public int getCount()
{
return 4;
}
}
Then you have to create a Fragment with your slider content (here the class FlightModeFragment), it will be instanciated with an integer to identify the page embedded into a Bundle savedInstanceState (I have 4 pages).
public class FlightModeFragment extends Fragment
{
LinearLayout mainLayout;
ImageView live15minImageView;
ImageView modeImageView;
ImageView imageViewLeft;
ImageView imageViewRight;
private TextView textViewRecord;
private TextView textViewGps;
int flight_mode = UserConfiguration.AC_LISTENER_TRAVEL_RECORDER_MODE;
#Override
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
// handle fragment arguments
Bundle arguments = getArguments();
if(arguments != null)
{
flight_mode = arguments.getInt("flight_mode");
}
}
// ...
}

When to reference a fragment created in ViewPager

I have a ViewPager that sets up two fragments as swipeable tabs. I am trying to find these fragments in the hosting activity.
I set them up as class global variables:
private SharedBillFragment sharedBill;
private SeparateBillFragment separateBill;
and try to find them in the activity's onStart():
#Override
protected void onStart() {
super.onStart();
findFragments();
}
where findFragments() is (I know this is kind of a hack, but it should work for the moment...):
private void findFragments() {
sharedBill = (SharedBillFragment)
getFragmentManager().findFragmentByTag("android:switcher:" + R.id.viewPager + ":" + 0);
separateBill = (SeparateBillFragment)
getFragmentManager().findFragmentByTag("android:switcher:" + R.id.viewPager + ":" + 1);
}
But this is not working, the fragments are not found. When i call findFragments() in a method that is called later (following a user interaction), the fragments are found and I can use them. Example:
private void setCountryTip(String selectedCountry) {
findFragments();
sharedBill.setPercentage(getCountryTip(selectedCountry));
separateBill.setPercentage(getCountryTip(selectedCountry));
}
But even when called then, the fragments are only available in the method where findFragments() is called, even though I set them as class global variables.
Best case, I would like to find the fragments when the app is started and store them in class global variables. Any help how to achieve that?
EDIT:
Some more information on how my fragments are setup. In my MainActivity I have:
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_main);
String[] mTabTitles = getResources().getStringArray(R.array.tabs);
final ActionBar actionBar = getActionBar();
actionBar.setNavigationMode(ActionBar.NAVIGATION_MODE_TABS);
mTabsAdapter = new TabsAdapter(getFragmentManager());
mViewPager = (ViewPager) findViewById(R.id.viewPager);
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
actionBar.setSelectedNavigationItem(position);
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
#Override
public void onPageScrollStateChanged(int arg0) {
}
});
mViewPager.setAdapter(mTabsAdapter);
for (String tab_name : mTabTitles) {
actionBar.addTab(actionBar.newTab().setText(tab_name).setTabListener(this));
}
}
and my TabsAdapter:
public class TabsAdapter extends FragmentPagerAdapter {
final int NUMBER_OF_TABS = 2;
public TabsAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int index) {
switch (index) {
case 0:
return new SharedBillFragment();
case 1:
return new SeparateBillFragment();
}
return null;
}
#Override
public int getCount() {
return NUMBER_OF_TABS;
}
}
EDIT: Regarding the NPE, my spinner is setup as follows in the fragment:
private Spinner spCountry;
public void onActivityCreated(Bundle savedInstanceState) {
super.onActivityCreated(savedInstanceState);
spCountry = (Spinner) getActivity().findViewById(R.id.spCountry);
spCountry.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
#Override
public void onItemSelected(AdapterView<?> adapterView, View view, int pos, long l) {
mCallback.onCountrySelected(spCountry.getItemAtPosition(pos).toString());
}
#Override
public void onNothingSelected(AdapterView<?> adapterView) {
}
});
}
public void setCountry(String country) {
ArrayAdapter spCountryAdapter = (ArrayAdapter) spCountry.getAdapter();
spCountry.setSelection(spCountryAdapter.getPosition(country));
}
The spinner gets its entries via the xml property "android:entries".
Not a very nice and re-usable solution, but since you are using static fragments it could work: make the adapters type Fragment, and initialize the fragments in the activity. Than add the two fragment to the adapter, and in the adapters getItem function only return the corresponding fragment.
Adapter:
public class TabsAdapter extends FragmentPagerAdapter {
List<Fragment> fragments;
public TabsAdapter(FragmentManager fm, List<Fragment> fragments) {
super(fm);
this.fragments = fragments;
}
#Override
public Fragment getItem(int index) {
return fragments.get(index);
}
#Override
public int getCount() {
return fragments.size();
}
}
Activity:
sharedBillFragment = new SharedBillFragment();
separateBillFragment = new SeparateBillFragment();
List<Fragment> fragments = new ArrayList<Fragment>();
fragments.add(sharedBillFragment);
fragments.add(separateBillFragment);
mTabsAdapter = new TabsAdapter(getFragmentManager(), fragments);
mViewPager = (ViewPager) findViewById(R.id.viewPager);
mViewPager.setOnPageChangeListener(new ViewPager.SimpleOnPageChangeListener() {
#Override
public void onPageSelected(int position) {
actionBar.setSelectedNavigationItem(position);
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
}
#Override
public void onPageScrollStateChanged(int arg0) {
}
});
mViewPager.setAdapter(mTabsAdapter);
Find the spinner in the onCreateView like this:
public View onCreateView (LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View v = inflater.inflate(R.layout.fragment, containter, false);
spCountry = (Spinner) getActivity().findViewById(R.id.spCountry);
...
return v;
}

ViewPagerIndicator tabs loses content

I am using Holoeverywhere and ViewPagerIndicator; using Holoeverywhere1 I implemented the slider and tabs in the Fragment using ViewPagerIndicator. Everything is working fine, Slider working, the menu in it, the tabs; the tabs works fine only the first time, when I move to other Fragment using the slider and come back to the tabs Fragment, the contents are no more available.
Code for the Fragment where tabs are created
public class SeconFrag extends Fragment {
ViewPager mViewPager;
FragmentAdapter mTabsAdapter;
PageIndicator mIndicator;
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
mTabsAdapter = new FragmentAdapter(getSupportFragmentManager());
View view = inflater.inflate(R.layout.second_frag);
mViewPager = (ViewPager) view.findViewById(R.id.pager);
mViewPager.setAdapter(mTabsAdapter);
TitlePageIndicator indicator = (TitlePageIndicator) view
.findViewById(R.id.indicator);
indicator.setViewPager(mViewPager);
indicator.setFooterIndicatorStyle(IndicatorStyle.Triangle);
mIndicator = indicator;
return view;
}
#Override
public void onResume() {
super.onResume();
getSupportActionBar().setSubtitle("Create");
}
}
FragmentAdapter code
public class FragmentAdapter extends FragmentPagerAdapter{
protected static final String[] CONTENT = new String[] { "Choose", "Customise" };
private int mCount = CONTENT.length;
public FragmentAdapter(FragmentManager fm) {
super(fm);
}
#Override
public Fragment getItem(int position) {
if (position == 0) {
return TabOne.newInstance();
} else if (position == 1) {
return TabTwo.newInstance();
}else if (position == 3 ) {
return TabOne.newInstance();
}else
return null;
}
#Override
public int getCount() {
return mCount;
}
#Override
public CharSequence getPageTitle(int position) {
return FragmentAdapter.CONTENT[position % CONTENT.length];
}
public void setCount(int count) {
if (count > 0 && count <= 10) {
mCount = count;
notifyDataSetChanged();
}
}
}
on of the tab Fragment
public final class TabOne extends Fragment {
public static TabOne newInstance() {
TabOne fragment = new TabOne();
return fragment;
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View myFragmentView = inflater.inflate(R.layout.tab_one, container,
false);
return myFragmentView;
}
}
in the SeconFrag OnPause() I removed the Fragment no luck, hide() it in the OnPause() and tried to show() in the OnResume() still not showing content after the Fragment is changed.
EDIT

need help to fix code for getting current view in viewpager

Need help to fix a code I saw in a https://stackoverflow.com/a/9275732/1938357. The solution I need is get the current view onany page in my viewpager based application
Below is the code for reference
public View getCurrentView(ViewPager pager) {
for (int i = 0; i < pager.getChildCount(); i++) {
View child = pager.getChildAt(i);
if (child.getX() <= pager.getScrollX() + pager.getWidth()
&& child.getX() + child.getWidth() >= pager.getScrollX() + pager.getWidth()) {
return child;
}
}
return getChildAt(0);
I am trying to access the current view in the view pager and used the code above. It works fine when I scroll forwards. But when I scroll backwards then after 2 backward scrolls this stops working.
Public class myActivity extends FragmentActivity
//Variable declarations
protected void onCreate(Bundle savedInstanceState) {
ViewPager mPager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);
View CurrView;
OnPageChangeListener pageChangelistener = new OnPageChangeListener() {
#Override
public void onPageSelected(int pageSelected) {
doTextViewChnges();//access the text view and update it based on pageSelected
---THIS IS WHERE I AM STUCK IN TRYING TO GET THE TEXTVIEW IN MY CURRENT FRAGMWNT/VIEW-------
}
mPager.setOnPageChangeListener(pageChangelistener);
}
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
public ScreenSlidePagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
}
#Override
public android.support.v4.app.Fragment getItem(int position) {
return ScreenSlidePageFragment.create(position, <other parameters I want to pass>);
}
#Override
public int getCount() {
return NUM_PAGES;
}
}
And below is my ScreenSlidePageFragment class
public class ScreenSlidePageFragment extends android.support.v4.app.Fragment {
public static final String ARG_PAGE = "pagenumber";
public static final String ARG_PAGEARRAY = "pages";
private int mPageNumber;
//Declare other variables
#SuppressLint("NewApi")
public static ScreenSlidePageFragment create(int pageNumber, String[] pages, int bookmarkPageNumber, String storyName, int linesInOnePage) {
ScreenSlidePageFragment fragment = new ScreenSlidePageFragment();
Bundle args = new Bundle();
args.putInt(ARG_PAGE, pageNumber);
args.putStringArray(ARG_PAGEARRAY, pages);
fragment.setArguments(args);
return fragment;
}
#SuppressLint("NewApi")
public ScreenSlidePageFragment() {
}
#SuppressLint("NewApi")
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPageNumber = getArguments().getInt(ARG_PAGE);
pages = getArguments().getStringArray(ARG_PAGEARRAY);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
rootView = (ViewGroup) inflater
.inflate(R.layout.activity_story, container, false);
//load layout with all components filled up for that page
loadView(rootView);
return rootView;
}
public int getPageNumber() {
return mPageNumber;
}
It is very hard to discuss in comments with example of code.
As I see in grepcode FragmentStatePagerAdapter.setPrimaryItem(), receive object as android.support.v4.app.Fragment. But you are receive ClassCastException when trying to cast object as Fragment. Can you check your casting: do you really cast object in android.support.v4.app.Fragment not in android.app.Fragment? If all right, add your exception message to your question, please.
UPDATE:
If your ScreenSlidePagerAdapter has method getCurrentView(), then your method 'getCurrentView()' can work like this:
public View getCurrentView(ViewPager pager) {
return ((ScreenSlidePagerAdapter) pager.getAdapter()).getCurrentView();
}
And method getCurrentView() of your ScreenSlidePagerAdapter:
public View getCurrentView() {
return currView;
}
UPDATE 2:
I've made some modification with your code:
Your Activity with private Adapter:
public class myActivity extends FragmentActivity implements ISelectItemListener {
// Your variable declarations and methods
#Override
protected void onCreate(Bundle savedInstanceState) {
final ViewPager pager = (ViewPager) findViewById(R.id.pager);
mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
pager.setAdapter(mPagerAdapter);
pager.setOnPageChangeListener(
new OnPageChangeListener() {
#Override
public void onPageSelected(int pageSelected) {
final ScreenSlidePageFragment fragment = mPagerAdapter.getFragment(pageSelected);
if (fragment != null) {
fragment.doTextViewChnges();
}
}
#Override
public void onPageScrollStateChanged(int arg0) {
// TODO Auto-generated method stub
}
#Override
public void onPageScrolled(int arg0, float arg1, int arg2) {
// TODO Auto-generated method stub
}
});
}
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
//Your variable declarations
private final ScreenSlidePageFragment[] mFragments;
public ScreenSlidePagerAdapter(android.support.v4.app.FragmentManager fm) {
super(fm);
mFragments = new ScreenSlidePageFragment[NUM_PAGES];
}
#Override
public int getCount() {
return NUM_PAGES;
}
// It will create new ScreenSlidePageFragment by position if it's not exsist
#Override
public android.support.v4.app.Fragment getItem(int position) {
if (getFragment(position) == null) {
mFragments[position] = ScreenSlidePageFragment.create(position, <other parameters I want to pass>);
}
return getFragment(position);
}
// It will give you ScreenSlidePageFragment if it's exists, or null
public ScreenSlidePageFragment getFragment(int position) {
return mFragments[position];
}
}
}
Your Fragment:
public class ScreenSlidePageFragment extends Fragment {
//Your variables and methods
public static ScreenSlidePageFragment create(int pageNumber, String[] pages, int bookmarkPageNumber, String storyName, int linesInOnePage) {
final Bundle args = new Bundle();
args.putInt(ARG_PAGE, pageNumber);
args.putStringArray(ARG_PAGEARRAY, pages);
final ScreenSlidePageFragment fragment = new ScreenSlidePageFragment();
fragment.setArguments(args);
return fragment;
}
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
mPageNumber = getArguments().getInt(ARG_PAGE);
pages = getArguments().getStringArray(ARG_PAGEARRAY);
}
#Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
rootView = (ViewGroup) inflater.inflate(R.layout.activity_story, container, false);
loadView(rootView);
return rootView;
}
public int getPageNumber() {
return mPageNumber;
}
// Let's keep logic of modifications in fragment
public void doTextViewChnges() {
final View view = getView();
// do what you want with your View and his children
}
}

Categories

Resources