Commit ff6d0d8f authored by Alexander Ivanov's avatar Alexander Ivanov

Move contact list into separate fragment.

parent 23e3e407
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (c) 2013, Redsolution LTD. All rights reserved.
<!--
Copyright (c) 2013, Redsolution LTD. All rights reserved.
This file is part of Xabber project; you can redistribute it and/or
modify it under the terms of the GNU General Public License, Version 3.
......@@ -12,47 +13,47 @@
You should have received a copy of the GNU General Public License,
along with this program. If not, see http://www.gnu.org/licenses/.
-->
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="vertical"
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
>
android:layout_height="match_parent" >
<RelativeLayout
android:id="@android:id/title"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="@dimen/title_height"
android:background="@drawable/title_main_background_dark"
>
android:layout_alignParentTop="true"
android:background="@drawable/title_main_background_dark" >
<ImageButton
android:id="@+id/back_button"
android:background="@drawable/title_logo"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentLeft="true"
/>
android:background="@drawable/title_logo"
tools:ignore="ContentDescription" />
<LinearLayout
android:orientation="horizontal"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
android:layout_marginRight="@dimen/contact_list_status_mode_width"
android:layout_toRightOf="@id/back_button"
android:layout_alignParentRight="true"
>
android:orientation="horizontal" >
<HorizontalScrollView
android:id="@+id/account_bar"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_marginRight="72dip"
>
android:layout_marginRight="72dip" >
<LinearLayout
android:id="@+id/account_list"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:orientation="horizontal"
>
<!--
android:orientation="horizontal" >
<!--
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/account_bg"
......@@ -98,77 +99,41 @@
/>
</RelativeLayout>
-->
</LinearLayout>
</HorizontalScrollView>
<TextView
android:id="@+id/common_status_text"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_gravity="left"
android:layout_marginLeft="-72dip"
android:textColor="?android:attr/textColorPrimary"
android:text="online"
android:gravity="left|center_vertical"
android:padding="2dip"
android:singleLine="true"
android:gravity="left|center_vertical"
android:layout_gravity="left"
/>
android:text="online"
android:textColor="?android:attr/textColorPrimary" />
</LinearLayout>
<ImageButton
android:id="@+id/common_status_mode"
android:src="@drawable/ic_status"
android:paddingLeft="@dimen/active_chat_padding_right"
android:paddingRight="@dimen/active_chat_padding_right"
android:background="@drawable/btn_status_mode"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentRight="true"
/>
android:background="@drawable/btn_status_mode"
android:paddingLeft="@dimen/active_chat_padding_right"
android:paddingRight="@dimen/active_chat_padding_right"
android:src="@drawable/ic_status" />
</RelativeLayout>
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textFilterEnabled="true"
/>
<RelativeLayout
android:id="@+id/info"
android:id="@+id/container"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:layout_weight="1"
android:gravity="center_vertical"
>
<ImageView
android:id="@+id/connected"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:src="@drawable/ic_connect"
/>
<ImageView
android:id="@+id/disconnected"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:src="@drawable/ic_disconnect"
/>
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textColor="?android:attr/textColorSecondary"
android:layout_below="@id/connected"
android:layout_centerHorizontal="true"
android:text="@string/application_state_starting"
/>
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dip"
android:layout_below="@id/text"
android:visibility="gone"
android:text="@string/application_action_waiting"
/>
</RelativeLayout>
</LinearLayout>
\ No newline at end of file
android:layout_below="@android:id/title" />
</RelativeLayout>
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright (c) 2013, Redsolution LTD. All rights reserved.
This file is part of Xabber project; you can redistribute it and/or
modify it under the terms of the GNU General Public License, Version 3.
Xabber is distributed in the hope that it will be useful, but
WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License,
along with this program. If not, see http://www.gnu.org/licenses/.
-->
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent" >
<ListView
android:id="@android:id/list"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:textFilterEnabled="true" />
<RelativeLayout
android:id="@+id/info"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:gravity="center_vertical" >
<ImageView
android:id="@+id/connected"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:src="@drawable/ic_connect"
tools:ignore="ContentDescription" />
<ImageView
android:id="@+id/disconnected"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_centerHorizontal="true"
android:src="@drawable/ic_disconnect"
tools:ignore="ContentDescription" />
<TextView
android:id="@+id/text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/connected"
android:layout_centerHorizontal="true"
android:text="@string/application_state_starting"
android:textColor="?android:attr/textColorSecondary" />
<Button
android:id="@+id/button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_below="@id/text"
android:layout_centerHorizontal="true"
android:layout_marginTop="20dip"
android:text="@string/application_action_waiting" />
</RelativeLayout>
</RelativeLayout>
......@@ -30,22 +30,17 @@ import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.SystemClock;
import android.support.v4.app.FragmentTransaction;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.KeyEvent;
import android.view.Menu;
import android.view.MenuItem;
import android.view.MotionEvent;
import android.view.View;
import android.view.View.OnLongClickListener;
import android.view.inputmethod.InputMethodManager;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.TextView;
import android.widget.Toast;
......@@ -62,24 +57,18 @@ import com.xabber.android.data.extension.muc.MUCManager;
import com.xabber.android.data.intent.EntityIntentBuilder;
import com.xabber.android.data.message.AbstractChat;
import com.xabber.android.data.message.MessageManager;
import com.xabber.android.data.message.OnChatChangedListener;
import com.xabber.android.data.notification.NotificationManager;
import com.xabber.android.data.roster.AbstractContact;
import com.xabber.android.data.roster.OnContactChangedListener;
import com.xabber.android.data.roster.RosterContact;
import com.xabber.android.data.roster.RosterManager;
import com.xabber.android.ui.adapter.AccountConfiguration;
import com.xabber.android.ui.ContactListFragment.OnContactClickListener;
import com.xabber.android.ui.adapter.AccountToggleAdapter;
import com.xabber.android.ui.adapter.ContactListAdapter;
import com.xabber.android.ui.adapter.GroupConfiguration;
import com.xabber.android.ui.dialog.AccountChooseDialogFragment;
import com.xabber.android.ui.dialog.AccountChooseDialogFragment.OnChoosedListener;
import com.xabber.android.ui.dialog.ConfirmDialogListener;
import com.xabber.android.ui.dialog.ContactIntegrationDialogFragment;
import com.xabber.android.ui.dialog.DialogBuilder;
import com.xabber.android.ui.dialog.StartAtBootDialogFragment;
import com.xabber.android.ui.helper.ContextMenuHelper;
import com.xabber.android.ui.helper.ManagedListActivity;
import com.xabber.android.ui.helper.ManagedActivity;
import com.xabber.androiddev.R;
import com.xabber.xmpp.address.Jid;
import com.xabber.xmpp.uri.XMPPUri;
......@@ -90,10 +79,9 @@ import com.xabber.xmpp.uri.XMPPUri;
* @author alexander.ivanov
*
*/
public class ContactList extends ManagedListActivity implements
OnContactChangedListener, OnAccountChangedListener,
OnChatChangedListener, View.OnClickListener, ConfirmDialogListener,
OnItemClickListener, OnLongClickListener, OnChoosedListener {
public class ContactList extends ManagedActivity implements
OnAccountChangedListener, View.OnClickListener, OnLongClickListener,
OnChoosedListener, OnContactClickListener {
/**
* Select contact to be invited to the room was requested.
......@@ -116,10 +104,7 @@ public class ContactList extends ManagedListActivity implements
private static final int DIALOG_CLOSE_APPLICATION_ID = 0x57;
/**
* Adapter for contact list.
*/
private ContactListAdapter contactListAdapter;
private static final String CONTACT_LIST_TAG = "CONTACT_LIST";
/**
* Adapter for account list.
......@@ -154,16 +139,14 @@ public class ContactList extends ManagedListActivity implements
return;
setContentView(R.layout.contact_list);
titleView = findViewById(android.R.id.title);
ListView listView = getListView();
listView.setOnItemClickListener(this);
listView.setItemsCanFocus(true);
FragmentTransaction fragmentTransaction = getSupportFragmentManager()
.beginTransaction();
fragmentTransaction.add(R.id.container, new ContactListFragment(),
CONTACT_LIST_TAG);
fragmentTransaction.commit();
registerForContextMenu(listView);
contactListAdapter = new ContactListAdapter(this, listView);
setListAdapter(contactListAdapter);
accountToggleAdapter = new AccountToggleAdapter(this, this,
(LinearLayout) findViewById(R.id.account_list));
......@@ -273,15 +256,8 @@ public class ContactList extends ManagedListActivity implements
super.onResume();
updateStatusBar();
rebuildAccountToggler();
Application.getInstance().addUIListener(OnAccountChangedListener.class,
this);
Application.getInstance().addUIListener(OnContactChangedListener.class,
this);
Application.getInstance().addUIListener(OnChatChangedListener.class,
this);
contactListAdapter.onChange();
if (ContactList.ACTION_ROOM_INVITE.equals(action)
|| Intent.ACTION_SEND.equals(action)
|| Intent.ACTION_CREATE_SHORTCUT.equals(action)) {
......@@ -336,17 +312,8 @@ public class ContactList extends ManagedListActivity implements
@Override
protected void onPause() {
super.onPause();
unregisterListeners();
}
private void unregisterListeners() {
Application.getInstance().removeUIListener(
OnAccountChangedListener.class, this);
Application.getInstance().removeUIListener(
OnContactChangedListener.class, this);
Application.getInstance().removeUIListener(OnChatChangedListener.class,
this);
contactListAdapter.removeRefreshRequests();
}
@Override
......@@ -396,7 +363,7 @@ public class ContactList extends ManagedListActivity implements
case OPTION_MENU_EXIT_ID:
Application.getInstance().requestToClose();
showDialog(DIALOG_CLOSE_APPLICATION_ID);
unregisterListeners();
getContactListFragment().unregisterListeners();
new Handler().postDelayed(new Runnable() {
@Override
public void run() {
......@@ -416,41 +383,25 @@ public class ContactList extends ManagedListActivity implements
NotificationManager.getInstance().removeMessageNotification(
chat.getAccount(), chat.getUser());
}
contactListAdapter.onChange();
getContactListFragment().getAdapter().onChange();
return true;
}
return false;
}
private ContactListFragment getContactListFragment() {
return (ContactListFragment) getSupportFragmentManager()
.findFragmentByTag(CONTACT_LIST_TAG);
}
@Override
public void onCreateContextMenu(ContextMenu menu, View view,
ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, view, menuInfo);
if (view == getListView()) {
final AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
BaseEntity baseEntity = (BaseEntity) getListView()
.getItemAtPosition(info.position);
if (baseEntity == null)
// Account toggler
return;
if (baseEntity instanceof AbstractContact) {
ContextMenuHelper.createContactContextMenu(this,
contactListAdapter, (AbstractContact) baseEntity, menu);
} else if (baseEntity instanceof AccountConfiguration) {
ContextMenuHelper.createAccountContextMenu(this,
contactListAdapter, baseEntity.getAccount(), menu);
} else if (baseEntity instanceof GroupConfiguration) {
ContextMenuHelper.createGroupContextMenu(this,
contactListAdapter, baseEntity.getAccount(),
baseEntity.getUser(), menu);
}
} else {
// Account panel
ContextMenuHelper.createAccountContextMenu(this,
contactListAdapter,
getContactListFragment().getAdapter(),
accountToggleAdapter.getItemForView(view), menu);
}
}
@Override
protected Dialog onCreateDialog(int id) {
......@@ -492,7 +443,7 @@ public class ContactList extends ManagedListActivity implements
case R.id.back_button: // Xabber icon button
case R.id.common_status_text:
case android.R.id.title:
scrollUp();
getContactListFragment().scrollUp();
break;
default:
String account = accountToggleAdapter.getItemForView(view);
......@@ -500,69 +451,15 @@ public class ContactList extends ManagedListActivity implements
break;
if (!SettingsManager.contactsShowAccounts()) {
if (AccountManager.getInstance().getAccounts().size() < 2)
scrollUp();
else
setSelectedAccount(account);
} else
scrollTo(account);
break;
}
}
/**
* Scroll contact list to specified account.
*
* @param account
*/
private void scrollTo(String account) {
ListView listView = getListView();
long count = listView.getCount();
for (int position = 0; position < (int) count; position++) {
BaseEntity baseEntity = (BaseEntity) listView
.getItemAtPosition(position);
if (baseEntity != null
&& baseEntity instanceof AccountConfiguration
&& baseEntity.getAccount().equals(account)) {
listView.setSelection(position);
stopMovement();
break;
}
}
}
/**
* Filter out contact list for selected account.
*
* @param account
*/
private void setSelectedAccount(String account) {
if (account.equals(AccountManager.getInstance().getSelectedAccount()))
SettingsManager.setContactsSelectedAccount("");
else
SettingsManager.setContactsSelectedAccount(account);
getContactListFragment().scrollUp();
else {
getContactListFragment().setSelectedAccount(account);
rebuildAccountToggler();
contactListAdapter.onChange();
stopMovement();
}
/**
* Stop fling scrolling.
*/
private void stopMovement() {
getListView().onTouchEvent(
MotionEvent.obtain(SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL,
0, 0, 0));
} else
getContactListFragment().scrollTo(account);
break;
}
/**
* Scroll to the top of contact list.
*/
private void scrollUp() {
ListView listView = getListView();
if (listView.getCount() > 0)
listView.setSelection(0);
stopMovement();
}
@Override
......@@ -576,22 +473,7 @@ public class ContactList extends ManagedListActivity implements
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
Object object = parent.getAdapter().getItem(position);
if (object == null) {
// Account toggler
} else if (object instanceof AbstractContact) {
onContactClick((AbstractContact) object);
} else if (object instanceof GroupConfiguration) {
GroupConfiguration groupConfiguration = (GroupConfiguration) object;
contactListAdapter.setExpanded(groupConfiguration.getAccount(),
groupConfiguration.getUser(),
!groupConfiguration.isExpanded());
}
}
private void onContactClick(AbstractContact abstractContact) {
public void onContactClick(AbstractContact abstractContact) {
if (ACTION_ROOM_INVITE.equals(action)) {
action = null;
Intent intent = getIntent();
......@@ -638,21 +520,9 @@ public class ContactList extends ManagedListActivity implements
}
}
@Override
public void onContactsChanged(Collection<BaseEntity> addresses) {
contactListAdapter.refreshRequest();
}
@Override
public void onAccountsChanged(Collection<String> accounts) {
accountToggleAdapter.onChange();
contactListAdapter.refreshRequest();
}
@Override
public void onChatChanged(String account, String user, boolean incoming) {
if (incoming)
contactListAdapter.refreshRequest();
}
@Override
......@@ -660,18 +530,6 @@ public class ContactList extends ManagedListActivity implements
openChat(new BaseEntity(account, user), text);
}
@Override
public void onAccept(DialogBuilder dialogBuilder) {
}
@Override
public void onDecline(DialogBuilder dialogBuilder) {
}
@Override
public void onCancel(DialogBuilder dialogBuilder) {
}
private void updateStatusBar() {
String statusText = SettingsManager.statusText();
StatusMode statusMode = SettingsManager.statusMode();
......
package com.xabber.android.ui;
import java.util.Collection;
import android.os.Bundle;
import android.os.SystemClock;
import android.support.v4.app.Fragment;
import android.view.ContextMenu;
import android.view.ContextMenu.ContextMenuInfo;
import android.view.LayoutInflater;
import android.view.MotionEvent;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.AdapterView.AdapterContextMenuInfo;
import android.widget.AdapterView.OnItemClickListener;
import android.widget.ListView;
import com.xabber.android.data.Application;
import com.xabber.android.data.SettingsManager;
import com.xabber.android.data.account.AccountManager;
import com.xabber.android.data.account.OnAccountChangedListener;
import com.xabber.android.data.entity.BaseEntity;
import com.xabber.android.data.message.OnChatChangedListener;
import com.xabber.android.data.roster.AbstractContact;
import com.xabber.android.data.roster.OnContactChangedListener;
import com.xabber.android.ui.adapter.AccountConfiguration;
import com.xabber.android.ui.adapter.ContactListAdapter;
import com.xabber.android.ui.adapter.GroupConfiguration;
import com.xabber.android.ui.adapter.UpdatableAdapter;
import com.xabber.android.ui.helper.ContextMenuHelper;
import com.xabber.androiddev.R;
public class ContactListFragment extends Fragment implements
OnAccountChangedListener, OnContactChangedListener,
OnChatChangedListener, OnItemClickListener {
private ContactListAdapter adapter;
private ListView listView;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.contact_list_fragment, container,
false);
listView = (ListView) view.findViewById(android.R.id.list);
listView.setOnItemClickListener(this);
listView.setItemsCanFocus(true);
registerForContextMenu(listView);
adapter = new ContactListAdapter(getActivity(), listView,
view.findViewById(R.id.info));
listView.setAdapter(adapter);
return view;
}
@Override
public void onResume() {
super.onResume();
Application.getInstance().addUIListener(OnAccountChangedListener.class,
this);
Application.getInstance().addUIListener(OnContactChangedListener.class,
this);
Application.getInstance().addUIListener(OnChatChangedListener.class,
this);
adapter.onChange();
}
@Override
public void onPause() {
super.onPause();
unregisterListeners();
}
@Override
public void onCreateContextMenu(ContextMenu menu, View view,
ContextMenuInfo menuInfo) {
AdapterContextMenuInfo info = (AdapterContextMenuInfo) menuInfo;
BaseEntity baseEntity = (BaseEntity) listView
.getItemAtPosition(info.position);
if (baseEntity instanceof AbstractContact) {
ContextMenuHelper.createContactContextMenu(getActivity(), adapter,
(AbstractContact) baseEntity, menu);
} else if (baseEntity instanceof AccountConfiguration) {
ContextMenuHelper.createAccountContextMenu(getActivity(), adapter,
baseEntity.getAccount(), menu);
} else if (baseEntity instanceof GroupConfiguration) {
ContextMenuHelper.createGroupContextMenu(getActivity(), adapter,
baseEntity.getAccount(), baseEntity.getUser(), menu);
} else
throw new IllegalStateException();
}
@Override
public void onItemClick(AdapterView<?> parent, View view, int position,
long id) {
Object object = parent.getAdapter().getItem(position);
if (object instanceof AbstractContact) {
((OnContactClickListener) getActivity())
.onContactClick((AbstractContact) object);
} else if (object instanceof GroupConfiguration) {
GroupConfiguration groupConfiguration = (GroupConfiguration) object;
adapter.setExpanded(groupConfiguration.getAccount(),
groupConfiguration.getUser(),
!groupConfiguration.isExpanded());
} else
throw new IllegalStateException();
}
@Override
public void onContactsChanged(Collection<BaseEntity> addresses) {
adapter.refreshRequest();
}
@Override
public void onAccountsChanged(Collection<String> accounts) {
adapter.refreshRequest();
}
@Override
public void onChatChanged(String account, String user, boolean incoming) {
if (incoming)
adapter.refreshRequest();
}
/**
* Force stop contact list updates before pause or application close.
*/
void unregisterListeners() {
Application.getInstance().removeUIListener(
OnAccountChangedListener.class, this);
Application.getInstance().removeUIListener(
OnContactChangedListener.class, this);
Application.getInstance().removeUIListener(OnChatChangedListener.class,
this);
adapter.removeRefreshRequests();
}
UpdatableAdapter getAdapter() {
return adapter;
}
/**
* Scroll contact list to specified account.
*
* @param account
*/
void scrollTo(String account) {
long count = listView.getCount();
for (int position = 0; position < (int) count; position++) {
BaseEntity baseEntity = (BaseEntity) listView
.getItemAtPosition(position);
if (baseEntity != null
&& baseEntity instanceof AccountConfiguration
&& baseEntity.getAccount().equals(account)) {
stopMovement();
listView.setSelection(position);
break;
}
}
}
/**
* Filter out contact list for selected account.
*
* @param account
*/
void setSelectedAccount(String account) {
if (account.equals(AccountManager.getInstance().getSelectedAccount()))
SettingsManager.setContactsSelectedAccount("");
else
SettingsManager.setContactsSelectedAccount(account);
stopMovement();
adapter.onChange();
}
/**
* Scroll to the top of contact list.
*/
void scrollUp() {
if (listView.getCount() > 0)
listView.setSelection(0);
stopMovement();
}
/**
* Stop fling scrolling.
*/
private void stopMovement() {
listView.onTouchEvent(MotionEvent.obtain(SystemClock.uptimeMillis(),
SystemClock.uptimeMillis(), MotionEvent.ACTION_CANCEL, 0, 0, 0));
}
public interface OnContactClickListener {
void onContactClick(AbstractContact contact);
}
}
......@@ -121,10 +121,11 @@ public class ContactListAdapter extends
*/
private Date nextRefresh;
public ContactListAdapter(Activity activity, ListView listView) {
public ContactListAdapter(Activity activity, ListView listView,
View infoView) {
super(activity, listView, new ChatContactInflater(activity),
GroupManager.getInstance());
infoView = activity.findViewById(R.id.info);
this.infoView = infoView;
if (infoView != null) {
connectedView = infoView.findViewById(R.id.connected);
disconnectedView = infoView.findViewById(R.id.disconnected);
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment