problem with an adapter in android app - android

I have an app in android in which I return some data from facebook(some names and id's) and right after I return this data I use an adapter to set all this names in a list view.
All this work I do it in onCreate().First time I use a method getData() to return some names from facebook and after that I call for the adapter.
The problem is that getData() returns in an array of Strings the names that will be setup up with the adapter.Now,the adapter called does some processing on this array.
How I return these names from web this part works slowly and the array is not filled in time so when the adapter gets called the array is still empty and I get force close.
this is how I do it:
private String[] mStrings;
private String[] fName;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.invitefriends);
....................
/*In here I request for friends from facebook which ae set up in an array*/
mAsyncRunner.request("me/friends", new SampleRequestListener());
/*here is the adapter that processes the arrays returned in SampleRequestListener*/
adapter=new LazyAdapter1(this, mStrings,fName);
list.setAdapter(adapter);
}
/Here is the SampleRequestListener that does return from facebook the names in some arrays/
public class SampleRequestListener extends BaseRequestListener{
int i;
public void onComplete(final String response, final Object state){
friends = new ArrayList<Friends>();
try{
Log.d("facebook-example","Response: " + response.toString());
JSONObject json = Util.parseJson(response);
JSONArray array = json.optJSONArray("data");
if(array!=null){
for(int i=0; i<array.length(); i++)
{
String name=array.getJSONObject(i).getString("name");
String id= array.getJSONObject(i).getString("id");
Friends f=new Friends(name,id);
friends.add(f);
Log.d(name,id);
}
mStrings = new String[friends.size()];
for(int i=0; i< mStrings.length; i++){
mStrings[i] = "http://graph.facebook.com/"+friends.get(i).getId()+"/picture?type=small";
}
fName = new String[friends.size()];
for(int i=0; i<friends.size(); i++)
{
fName[i]=friends.get(i).getName();
}
}
}
catch(JSONException e)
{
//do nothing
}
catch(FacebookError e)
{
e.getMessage();
}
}
}
And finally here is the adapter's body:
public class LazyAdapter1 extends BaseAdapter {
private Activity activity;
private String[] data;
private String[] nume;
private static LayoutInflater inflater=null;
public ImageLoader imageLoader;
public LazyAdapter1(Activity a, String[] d, String[] f) {
activity = a;
data=d;
nume=f;
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader=new ImageLoader(activity.getApplicationContext());
}
public int getCount() {
return data.length;
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public static class ViewHolder{
public TextView text;
public ImageView image;
}
public View getView(int position, View convertView, ViewGroup parent) {
//........
}
}
The problem is that mStrings[] and fName[] are not returned in time by the background thread so they are still empty when I try to act with the adapter on them.
So could someone tell how should I proceed for this thing to work right??Thanks

use the AsyncTask class and extend with your custom class.
this was good for getting the data in background process.
and when the data are fetched then set into the adapter
check this links
http://www.vogella.de/articles/AndroidPerformance/article.html
http://developer.android.com/reference/android/os/AsyncTask.html
http://www.xoriant.com/blog/mobile-application-development/android-async-task.html

Related

App get crashed when trying to display Data through listview

I'm trying to get data using retrofit2 and display those data using a list passing through as a parameter of Custom adapter. When I store data in a List in onResponse() method, in onResponse() method list have some value. But in oncreate() method its give me null. Though, I declared List as global. When I run the app sometimes its display nothing and sometimes app get crash. I know it's sounds like crazy. But it's happen. so, I want to know, what's wrong with my Code? how can I display data in listview?
Forgive me if something wrong with my question pattern yet this my maiden question at this site.
MainActivity`
public class LaboratoryValues extends AppCompatActivity {
public List<Data> productList = null;
List<Data>arrayList = null;
int size;
String st;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_laboratory_values);
//productList = new ArrayList<Data>();
getInvestigation();
for(int i =0; i < size; i++){
st = arrayList.get(i).getName();
}
System.out.println("Name : "+st);//here print Name : null
ListView lview = (ListView) findViewById(R.id.listview);
ListviewAdapter adapter = new ListviewAdapter(this, arrayList);
lview.setAdapter(adapter);
}
private void getInvestigation() {
/* final ProgressDialog progressDialog = new ProgressDialog(MainActivity.this);
progressDialog.setCancelable(false); // set cancelable to false
progressDialog.setMessage("Please Wait"); // set body
progressDialog.show(); // show progress dialog*/
ApiInterface apiService =
Api.getClient(ApiInterface.BASE_URL).create(ApiInterface.class);
Call<Investigation> investigationCall = apiService.getInvestigation();
investigationCall.enqueue(new Callback<Investigation>() {
#Override
public void onResponse(Call<Investigation> call, Response<Investigation> response) {
arrayList = response.body().getData();
//productList.addAll(arrayList);
size = response.body().getData().size();
for (int i = 0; i < size; i++) {
System.out.println("Name : " + arrayList.get(i).getName());//here printing Name is ok
}
}
#Override
public void onFailure(Call<Investigation> call, Throwable t) {
Toast.makeText(getApplicationContext(),"data list is empty",Toast.LENGTH_LONG).show();
}
});
}
}
Custom Adapter (listviewAdapter)
public class ListviewAdapter extends BaseAdapter {
public List<Data> productList;
Activity activity;
//Context mContext;
public ListviewAdapter(Activity activity, List<Data> productList) {
super();
this.activity = activity;
this.productList = productList;
}
#Override
public int getCount() {
return productList.size();
}
#Override
public Object getItem(int position) {
return productList.get(position);
}
#Override
public long getItemId(int position) {
return position;
}
private class ViewHolder {
TextView name;
TextView normal_finding;
TextView increased;
TextView decreased;
TextView others;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
ViewHolder holder;
LayoutInflater inflater = activity.getLayoutInflater();
if (convertView == null) {
convertView = inflater.inflate(R.layout.listview_row, null);
holder = new ViewHolder();
holder.name = convertView.findViewById(R.id.name);
holder.normal_finding =convertView.findViewById(R.id.normal_finding);
holder.increased = convertView.findViewById(R.id.increased);
holder.decreased = convertView.findViewById(R.id.decreased);
holder.others =convertView.findViewById(R.id.others);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
Data item = productList.get(position) ;
holder.name.setText(item.getName());
System.out.println("holderName : "+item.getName() );
holder.normal_finding.setText(item.getNormal_finding());
System.out.println("holderName : "+item.getNormal_finding() );
holder.increased.setText(item.getIncreased());
holder.decreased.setText(item.getDecreased());
holder.others.setText(item.getOthers());
return convertView;
}
}
It's perfectly normal that it dosent work.
putting your method getInvistigation() before the loop does not mean that the response of your request was done
Calling a webservice creates another thread that waits for the server to send the response, sometimes the response takes time depends from the server and the latency of your internet connection.
you simply need to place the treatment (the loop and adapter) inside getInvistagion after getting the data.

How to Retrieve Name | Value pair in Listview for Multiple Checked Items?

My list view is working fine and populating all items from cursor.
i need to assign get the id of the assigned name in the list view.
I am using multiple check boxes to select items from list view.
How can i get the Name|Value pair from listview for selected items?.
ListView listView;
ArrayAdapter<String> adapter;
/** Called when the activity is first created. */
#Override
public void onCreate(Bundle savedInstanceState)
{
try
{
super.onCreate(savedInstanceState);
setContentView(R.layout.servicelist_item);
//String[] sports ;
//get jobtype
Cursor cur=dbHelper.fetchByJobNumber(stringJobnumber);
String cur_type;
cur_type=cur.getString(18);
//passing jobtype to fetch service items through cursor
Cursor cursor=dbHelper.fetchItemsByType(cur_type);
ArrayList<String> sports=new ArrayList<String>();
if (cursor.moveToFirst())
{
do
{
//assigning cursor items to arraylist variable sports
/* assigning item name to list view*/
/*My cursor holds name value pair */
sports.add(cursor.getString(3));
}
while (cursor.moveToNext());
}
//setting array adapter to populate listView
adapter = new ArrayAdapter<String>(this,android.R.layout.simple_list_item_multiple_choice, sports);
listView.setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
listView.setAdapter(adapter);
button.setOnClickListener(this);
}
catch(Exception e)
{
System.out.println(e);
}
}
//Onclick method
public void onClick(View v)
{
SparseBooleanArray checked = listView.getCheckedItemPositions();
ArrayList<String> selectedItems = new ArrayList<String>();
for (int i = 0; i < checked.size(); i++)
{
// Item position in adapter
int position = checked.keyAt(i);
// Add sport if it is checked i.e.) == TRUE!
if (checked.valueAt(i))
selectedItems.add(adapter.getItem(position));
}
final String[] outputStrArr = new String[selectedItems.size()];
Log.e("selectedItems.size()====>>", ""+selectedItems.size());
for (int i = 0; i < selectedItems.size(); i++)
{
Log.e("selectedItems====>>", ""+selectedItems.get(i));
outputStrArr[i] = selectedItems.get(i);
}
}
Looking at your code, I would create an object that holds your name and id:
public class Sport {
private String id;
private String name;
public Sport(String id, String name){
this.id = id;
this.name = name;
}
public String getId(){
return id;
}
public String getName(){
return name;
}
}
I have often needed this feature, so what I did was create a custom adapter that has a String[] of ids, and List<T> for your data, and a map <String, T>. Doing this gives your list a few nice features like accessing by id and position. Where this adapter will maintain 3 instances of data, I recommend only using it for lists that are relatively smaller (less than 100).
Here is the adapter I like to use:
public abstract class MappedAdapter<T> extends BaseAdapter{
private Context mContext;
private Map<String, T> mMap;
private ArrayList<String> mIdList;
public MappedAdapter(Context context, ArrayList<T> arrayList) {
this.mContext = context;
mMap = new HashMap<>();
mIdList = new ArrayList<>();
for (T object : arrayList) {
add(object);
}
}
public MappedAdapter(Context context, T[] arrayList){
this.mContext = context;
mMap = new HashMap<>();
mIdList = new ArrayList<>();
for (T object : arrayList) {
add(object);
}
}
public abstract String getObjectId(T object);
public abstract String getObjectString(T object);
public T getObject(int position) {
return mMap.get(mIdList.get(position));
}
public String getObjectId(int position){
return mIdList.get(position);
}
public boolean add(T object) {
final String id = getObjectId(object);
if(!mMap.containsKey(id)){
mIdList.add(id);
mMap.put(id, object);
this.notifyDataSetChanged();
return true;
}
return false;
}
public void remove(T object){
final String id = getObjectId(object);
mIdList.remove(id);
mMap.remove(id);
this.notifyDataSetChanged();
}
#SuppressWarnings("unchecked")
public Map.Entry<String, T> getEntry(int position){
HashMap<String, T> entry = new HashMap<>();
String key = mIdList.get(position);
entry.put(key, mMap.get(key));
return (Map.Entry<String, T>) entry;
}
#Override
public int getCount() {
return mMap.size();
}
#Override
public Object getItem(int position) {
return mMap.get(mIdList.get(position));
}
#Override
public long getItemId(int position) {
return position;
}
#Override
public View getView(int position, View view, ViewGroup parent) {
T object = mMap.get(mIdList.get(position));
if (view == null) {
int layoutResource = android.R.layout.simple_list_item_1;
view = LayoutInflater.from(mContext).inflate(layoutResource, null);
}
TextView tv = (TextView) view.findViewById(android.R.id.text1);
tv.setText(getObjectString(object));
return view;
}
}
This is an adapter class I use a lot that you just pass in a list, and you can access it by id and position. And will not allow for duplicate objects. It takes a generic so you can use this for many different objects.
This will ask you to override two methods:
getObjectId() Object id is what it will use as the id/key to build the map.
getObjectString() The objectString is what string you want to display in the list view
By default this will be using android.R.layout.simple_list_item_1 for its layout. Obviously, you can change this, or better yet, build a sub class and override the getView()
Usage:
MappedAdapter<Sport> mappedAdapter = new MappedAdapter<Sport>(context, sports){
#Override
public String getObjectId(Sport object) {
return object.getId();
}
#Override
public String getObjectString(Sport object) {
return object.getName();
}
};
ListView listView = ... ;
listView.setAdapter(mappedAdapter);
To get your Key|Value pair, you can get it by position :
Map.Entry<String, Sport> entry = mappedAdapter.getEntry(positon);
String key = entry.getKey();
Sport sport = entry.getValue();
Let me know if you need any clarification. Happy to help.

GridView reverse

I have pictures from my online database in my GridView and I want it in reverse order.
So I want when I add a new picture to my database to be the first in the GridView.
I tried to find an answer but there is nothing about it at stackoverflow.
This is my ListViewAdapter:
public class GetMovieImagesListViewAdapter extends BaseAdapter {
private JSONArray dataArray;
private Activity activity;
private static final String baseUrlForImage = "http://google.com";
private static LayoutInflater inflater = null;
public GetMovieImagesListViewAdapter(JSONArray jsonArray, Activity a){
this.dataArray = jsonArray;
this.activity = a;
inflater = (LayoutInflater) this.activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
}
#Override
public int getCount() {
return this.dataArray.length();
}
#Override
public Object getItem(int position){
return position;
}
#Override
public long getItemId(int position){
return position;
}
#Override
public View getView(int position, View convertView, ViewGroup parent) {
final ListCell cell;
if (convertView == null) {
convertView = inflater.inflate(R.layout.get_images_from_movies_list_cell, null);
cell = new ListCell();
cell.MovieImages = (ImageView) convertView.findViewById(R.id.movie_images_id);
convertView.setTag(cell);
} else {
cell = (ListCell) convertView.getTag();
}
try {
JSONObject jsonObject = this.dataArray.getJSONObject(position);
String nameOfImage = jsonObject.getString("image");
String urlForImageInServer = baseUrlForImage + nameOfImage;
new AsyncTask<String, Void, Bitmap>(){
#Override
protected Bitmap doInBackground(String... params)
{
String url = params[0];
Bitmap icon = null;
try
{
InputStream in = new URL(url).openStream();
icon = BitmapFactory.decodeStream(in);
} catch (IOException e) {
e.printStackTrace();
}
return icon;
}
#Override
protected void onPostExecute(Bitmap result)
{
cell.MovieImages.setImageBitmap(result);
}
}.execute(urlForImageInServer);
} catch (JSONException e) {
e.printStackTrace();
}
return convertView;
}
private class ListCell{
private ImageView MovieImages;
}}
You could fetch each JSONObject by starting from the end of the array and working backwards to the start:
JSONObject jsonObject = this.dataArray.getJSONObject(getCount() - position - 1);
The result that you have received try to iterate through it in the reverse order.
Your Bitmap result data seems to be a single data that you get. I am expecting an array, or arraylist of data that you get in onPostExecute() method parameter while you query your online database.
Say for example, if you get the data in the form of onPostExecute(ArrayList result), then you can simply iterate the 'result' data in reverse order in order to populate your grid view in reverse order.
Check out Iterate arraylist in reverse order

unable to set listview text

I am using json parser to pass image url and description into my listview. now i managed to load the images but how do i change the text for my list view? currently it just shows item 0, item 1 and so on.. how do i pass the description into the lazyadapter?
Main activity:
public class MainActivity extends Activity {
// CREATING JSON PARSER OBJECT
JSONParser jParser = new JSONParser();
JSONArray guide = null;
ListView list;
LazyAdapter adapter;
String[] mImageIds;
ArrayList<String> guideList =new ArrayList<String>();
ArrayList<String> descriptionList =new ArrayList<String>();
// GUIDE URL
private static String url_guide = "http://58.185.41.178/magazine_android/get_guide.txt";
private static final String TAG_GUIDES = "guides"; //the parent node of my JSON
private static final String TAG_DESCRIPTION = "description";
private static final String TAG_IMAGE = "image";
#Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.main);
// LOADING Guide IN BACKGROUND THREAD
new LoadGuide().execute();
list=(ListView)findViewById(R.id.list);
adapter=new LazyAdapter(this,guideList);
list.setAdapter(adapter);
Button b=(Button)findViewById(R.id.button1);
b.setOnClickListener(listener);
}
#Override
public void onDestroy()
{
list.setAdapter(null);
super.onDestroy();
}
public OnClickListener listener=new OnClickListener(){
#Override
public void onClick(View arg0) {
adapter.imageLoader.clearCache();
adapter.notifyDataSetChanged();
}
};
/**
* Background Async Task to Load all product by making HTTP Request
* */
class LoadGuide extends AsyncTask<String, String, String> {
/**
* getting All videos from url
* */
protected String doInBackground(String... args) {
// Building Parameters
List<NameValuePair> params = new ArrayList<NameValuePair>();
// getting JSON string from URL
JSONObject json = jParser.makeHttpRequest(url_guide, "GET", params);
// CHECKING OF JSON RESPONSE
Log.d("All guide: ", json.toString());
try {
guide = json.getJSONArray(TAG_GUIDES);
for (int i = 0; i < guide.length(); i++) {
JSONObject c = guide.getJSONObject(i);
//String title = c.getString(TAG_DESCRIPTION);
String image = c.getString(TAG_IMAGE);
String description = c.getString(TAG_DESCRIPTION);
guideList.add(image);
descriptionList.add(description);
System.out.println(guideList);
}
} catch (JSONException e) {
e.printStackTrace();
}
return null;
}
/**
* After completing background task Dismiss the progress dialog
* **/
protected void onPostExecute(String file_url) {
// UPDATING UI FROM BACKGROUND THREAD
runOnUiThread(new Runnable() {
public void run() {
/**
* Updating parsed JSON data into ListView
* */
adapter.notifyDataSetChanged();
}
});
}
}
}
Image adapter:
public class LazyAdapter extends BaseAdapter {
private Activity activity;
private ArrayList<String> data;
private static LayoutInflater inflater=null;
public ImageLoader imageLoader;
public LazyAdapter(Activity a, ArrayList<String> guideList) {
activity = a;
data=guideList;
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader=new ImageLoader(activity.getApplicationContext());
}
public int getCount() {
return data.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public View getView(int position, View convertView, ViewGroup parent) {
View vi=convertView;
if(convertView==null)
vi = inflater.inflate(R.layout.item, null);
TextView text=(TextView)vi.findViewById(R.id.text);;
ImageView image=(ImageView)vi.findViewById(R.id.image);
text.setText("item "+position);
imageLoader.DisplayImage(data.get(position), image);
return vi;
}
}
In your adapter, you are making the TextView say "item 1", "item 2" etc specifically. What you need to do is add in the Adapter constructor your descriptionList
public LazyAdapter(Activity a, ArrayList<String> guideList, ArrayList<String> descriptionList) {
and then do
text.setText(descriptionList.get(position));
When your activity calls adapter.notifyDataSetChanged(), that forces a re-draw of every item in the list. That will trigger a call into the getView() method your adapter. So your logic belongs in the getView() method:
text.setText(descriptionList.get(position));
Use a single Hashmap for guidelist and descriptionlist and then pass that to the lazyadapter constructor. use the description part of the hashmap in the getview() method to the set the text.
#user1933630
descriptionList.add(description.subString(1,description.length()-1);

OnItemClickListener Problem

i'm creating a lazy load of image in ListView.
i was followed the tutorial from this source which i found in Stack Overflow
It was run successful.
But, when i join the code together with my project then i face a problem. the program was no perform the OnItemClickListener :(
my project have a TabHost and it had 5 tab contents. 2 contents is using the ListActivity and run perfectly.
here is my coding, Main.java:
public class ProductListing extends Activity {
ListView list;
MyListAdapter adapter;
Controller c;
ImageLoader imageLoader;
TextView select;
//========== JSON ===========
ArrayList<String> strName = new ArrayList<String>();
ArrayList<String> strImage = new ArrayList<String>();
ArrayList<String> strDesc = new ArrayList<String>();
ArrayList<String> strSize = new ArrayList<String>();
JSONObject jsonObject;
String[] listItem;
Context context;
#Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
LoadJSON();
setContentView(R.layout.productlisting_tab);
list=(ListView)findViewById(R.id.ListView01);
c = new Controller(this);
adapter=new MyListAdapter(this,this, strName, strImage,strDesc,strSize);
list.setAdapter(adapter);
list.setOnItemClickListener(new OnItemClickListener(){
#Override
public void onItemClick(AdapterView<?> parent, View view,
int position, long id) {
// TODO Auto-generated method stub
System.out.println("Item Clicked");
}
});
}
public void LoadJSON(){
try {
InputStream is = this.getResources().openRawResource(R.raw.premium);
byte[] buffer;
buffer = new byte[is.available()];
while(is.read(buffer) != -1);
String jsonText = new String(buffer);
jsonObject = new JSONObject(jsonText);
JSONObject premium_tab = jsonObject.getJSONObject("premium_tab");
int totalItem = premium_tab.getInt(".total");
for (int i = 1; i <= totalItem; i++) {
JSONObject premium = premium_tab.getJSONObject("premium_"+i);
String tempName =premium.getString(".name").toString();
String tempImg = premium.getString(".image").toString();
String tempDesc = premium.getString(".desc").toString();
String tempSize = premium.getString(".size").toString();
strName.add(tempName);
strImage.add(tempImg);
strDesc.add(tempDesc);
strSize.add(tempSize);
}
} catch (Exception e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
}
}
MyListAdapter.java:
public MyListAdapter(Context b,Activity a, ArrayList<String> strName, ArrayList<String> strImage,
ArrayList<String> strDesc, ArrayList<String> strSize) {
activity = a;
name = strName;
image = strImage;
desc = strDesc;
size = strSize;
inflater = (LayoutInflater)activity.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
imageLoader=new ImageLoader(activity.getApplicationContext());
}
public int getCount() {
return image.size();
}
public Object getItem(int position) {
return position;
}
public long getItemId(int position) {
return position;
}
public static class ViewHolder{
public TextView ProductName,ProductSize, ProductDesc;
public ImageView ProductIcon;
}
public View getView(int position, View convertView, ViewGroup parent) {
View vi=convertView;
ViewHolder holder;
if(convertView==null){
vi = inflater.inflate(R.layout.productlisting, null);
holder=new ViewHolder();
holder.ProductName=(TextView)vi.findViewById(R.id.text);
holder.ProductIcon=(ImageView)vi.findViewById(R.id.image);
holder.ProductDesc=(TextView)vi.findViewById(R.id.textdesc);
holder.ProductSize=(TextView)vi.findViewById(R.id.textsize);
vi.setTag(holder);
}
else
holder=(ViewHolder)vi.getTag();
holder.ProductName.setText(name.get(position));
holder.ProductDesc.setText(desc.get(position));
holder.ProductIcon.setTag(image.get(position));
holder.ProductSize.setText(size.get(position));
imageLoader.DisplayImage(image.get(position), activity, holder.ProductIcon);
return vi;
}
}
Another class which name ImageLoader.java please refer the source link above.
May i know where is my mistake? i understand my code will very ugly, i'm a new in android please help me to solve the problem. it was stuck me for few days.
your reply is very appreciated !!!
P/S: I'm sorry about my bad english, hope you guys understand what i'm talking about.
Thank you.
Regard
Wynix
I use a different technique adding event-listeners. In the OnCreate-method i write btnAdd.setOnClickListener(onAdd); and adding a stand-alone method to hook up to the event like this:
private View.OnClickListener onAdd=new View.OnClickListener() {
public void onClick(View v) {
// your code here
}
};
This makes it easier to search for errors in your code.
From your code, you set the event-listener to the entire list, instead of each individual item. Maybe you should try adding events to individual items instead?
I have solve the problem and solve it. the erroe is on the xml file. in ListView should NOT have
android:focusable="true"; menthod.
anyway Thanks for trying to fix my problem.
Thanks again.
Cheers!
Regard Wynix

Categories

Resources