tags navigation and self contact
This commit is contained in:
parent
18c41eb05e
commit
a2a943c36a
|
@ -99,7 +99,7 @@ android {
|
||||||
compileSdkVersion 33
|
compileSdkVersion 33
|
||||||
|
|
||||||
defaultConfig {
|
defaultConfig {
|
||||||
minSdkVersion 21
|
minSdkVersion 24
|
||||||
targetSdkVersion 33
|
targetSdkVersion 33
|
||||||
versionCode 42058
|
versionCode 42058
|
||||||
versionName "2.12.5"
|
versionName "2.12.5"
|
||||||
|
|
|
@ -109,6 +109,11 @@ public class Contact implements ListItem, Blockable {
|
||||||
this.keys = new JSONObject();
|
this.keys = new JSONObject();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public Contact(Contact other) {
|
||||||
|
this(null, other.systemName, other.serverName, other.presenceName, other.jid, other.subscription, other.photoUri, other.systemAccount, other.keys == null ? null : other.keys.toString(), other.getAvatar() == null ? null : other.getAvatar().sha1sum, other.mLastseen, other.mLastPresence, other.groups == null ? null : other.groups.toString(), other.rtpCapability);
|
||||||
|
setAccount(other.getAccount());
|
||||||
|
}
|
||||||
|
|
||||||
public static Contact fromCursor(final Cursor cursor) {
|
public static Contact fromCursor(final Cursor cursor) {
|
||||||
final Jid jid;
|
final Jid jid;
|
||||||
try {
|
try {
|
||||||
|
|
|
@ -27,9 +27,10 @@ import eu.siacs.conversations.ui.adapter.ListItemAdapter;
|
||||||
public abstract class AbstractSearchableListItemActivity extends XmppActivity implements TextView.OnEditorActionListener {
|
public abstract class AbstractSearchableListItemActivity extends XmppActivity implements TextView.OnEditorActionListener {
|
||||||
protected ActivityChooseContactBinding binding;
|
protected ActivityChooseContactBinding binding;
|
||||||
private final List<ListItem> listItems = new ArrayList<>();
|
private final List<ListItem> listItems = new ArrayList<>();
|
||||||
private ArrayAdapter<ListItem> mListItemsAdapter;
|
private ListItemAdapter mListItemsAdapter;
|
||||||
|
|
||||||
private EditText mSearchEditText;
|
protected MenuItem mMenuSearchView;
|
||||||
|
protected EditText mSearchEditText;
|
||||||
|
|
||||||
private final MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() {
|
private final MenuItem.OnActionExpandListener mOnActionExpandListener = new MenuItem.OnActionExpandListener() {
|
||||||
|
|
||||||
|
@ -84,7 +85,7 @@ public abstract class AbstractSearchableListItemActivity extends XmppActivity im
|
||||||
return mSearchEditText;
|
return mSearchEditText;
|
||||||
}
|
}
|
||||||
|
|
||||||
public ArrayAdapter<ListItem> getListItemAdapter() {
|
public ListItemAdapter getListItemAdapter() {
|
||||||
return mListItemsAdapter;
|
return mListItemsAdapter;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -102,13 +103,13 @@ public abstract class AbstractSearchableListItemActivity extends XmppActivity im
|
||||||
@Override
|
@Override
|
||||||
public boolean onCreateOptionsMenu(final Menu menu) {
|
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||||
getMenuInflater().inflate(R.menu.choose_contact, menu);
|
getMenuInflater().inflate(R.menu.choose_contact, menu);
|
||||||
final MenuItem menuSearchView = menu.findItem(R.id.action_search);
|
mMenuSearchView = menu.findItem(R.id.action_search);
|
||||||
final View mSearchView = menuSearchView.getActionView();
|
final View mSearchView = mMenuSearchView.getActionView();
|
||||||
mSearchEditText = mSearchView.findViewById(R.id.search_field);
|
mSearchEditText = mSearchView.findViewById(R.id.search_field);
|
||||||
mSearchEditText.addTextChangedListener(mSearchTextWatcher);
|
mSearchEditText.addTextChangedListener(mSearchTextWatcher);
|
||||||
mSearchEditText.setHint(R.string.search_contacts);
|
mSearchEditText.setHint(R.string.search_contacts);
|
||||||
mSearchEditText.setOnEditorActionListener(this);
|
mSearchEditText.setOnEditorActionListener(this);
|
||||||
menuSearchView.setOnActionExpandListener(mOnActionExpandListener);
|
mMenuSearchView.setOnActionExpandListener(mOnActionExpandListener);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -120,7 +120,7 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
|
||||||
|
|
||||||
multiple = intent.getBooleanExtra(EXTRA_SELECT_MULTIPLE, false);
|
multiple = intent.getBooleanExtra(EXTRA_SELECT_MULTIPLE, false);
|
||||||
if (multiple) {
|
if (multiple) {
|
||||||
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE_MODAL);
|
getListView().setChoiceMode(ListView.CHOICE_MODE_MULTIPLE);
|
||||||
getListView().setMultiChoiceModeListener(this);
|
getListView().setMultiChoiceModeListener(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -136,6 +136,15 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
|
||||||
final SharedPreferences preferences = getPreferences();
|
final SharedPreferences preferences = getPreferences();
|
||||||
this.startSearching = intent.getBooleanExtra("direct_search", false) && preferences.getBoolean("start_searching", getResources().getBoolean(R.bool.start_searching));
|
this.startSearching = intent.getBooleanExtra("direct_search", false) && preferences.getBoolean("start_searching", getResources().getBoolean(R.bool.start_searching));
|
||||||
|
|
||||||
|
getListItemAdapter().refreshSettings();
|
||||||
|
getListItemAdapter().setOnTagClickedListener((tag) -> {
|
||||||
|
if (mMenuSearchView != null) {
|
||||||
|
mMenuSearchView.expandActionView();
|
||||||
|
mSearchEditText.setText("");
|
||||||
|
mSearchEditText.append(tag);
|
||||||
|
filterContacts(tag);
|
||||||
|
}
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onFabClicked(View v) {
|
private void onFabClicked(View v) {
|
||||||
|
@ -278,10 +287,19 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
|
||||||
getListItems().add(contact);
|
getListItems().add(contact);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
final Contact self = new Contact(account.getSelfContact());
|
||||||
|
self.setSystemName("Note to Self");
|
||||||
|
if (self.match(this, needle)) {
|
||||||
|
getListItems().add(self);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Collections.sort(getListItems());
|
Collections.sort(getListItems());
|
||||||
getListItemAdapter().notifyDataSetChanged();
|
getListItemAdapter().notifyDataSetChanged();
|
||||||
|
for (int i = 0; i < getListItemAdapter().getCount(); i++) {
|
||||||
|
getListView().setItemChecked(i, selected.contains(getListItemAdapter().getItem(i).getJid().toString()));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private String[] getSelectedContactJids() {
|
private String[] getSelectedContactJids() {
|
||||||
|
@ -390,8 +408,24 @@ public class ChooseContactActivity extends AbstractSearchableListItemActivity im
|
||||||
@Override
|
@Override
|
||||||
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
public void onItemClick(AdapterView<?> parent, View view, int position, long id) {
|
||||||
if (multiple) {
|
if (multiple) {
|
||||||
startActionMode(this);
|
if (getListView().isItemChecked(position)) {
|
||||||
getListView().setItemChecked(position, true);
|
selected.add(getListItemAdapter().getItem(position).getJid().toString());
|
||||||
|
} else {
|
||||||
|
selected.remove(getListItemAdapter().getItem(position).getJid().toString());
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selected.isEmpty()) {
|
||||||
|
this.binding.fab.setImageResource(R.drawable.ic_person_add_white_24dp);
|
||||||
|
if (this.showEnterJid) {
|
||||||
|
this.binding.fab.show();
|
||||||
|
} else {
|
||||||
|
this.binding.fab.hide();
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
binding.fab.setImageResource(R.drawable.ic_forward_white_24dp);
|
||||||
|
binding.fab.show();
|
||||||
|
}
|
||||||
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
final InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||||
|
|
|
@ -12,6 +12,7 @@ import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.preference.PreferenceManager;
|
||||||
import android.text.Editable;
|
import android.text.Editable;
|
||||||
import android.text.Html;
|
import android.text.Html;
|
||||||
import android.text.TextWatcher;
|
import android.text.TextWatcher;
|
||||||
|
@ -21,6 +22,7 @@ import android.util.Pair;
|
||||||
import android.view.ContextMenu;
|
import android.view.ContextMenu;
|
||||||
import android.view.ContextMenu.ContextMenuInfo;
|
import android.view.ContextMenu.ContextMenuInfo;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
|
import android.view.LayoutInflater;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
|
@ -49,6 +51,8 @@ import androidx.databinding.DataBindingUtil;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentTransaction;
|
import androidx.fragment.app.FragmentTransaction;
|
||||||
|
import androidx.recyclerview.widget.LinearLayoutManager;
|
||||||
|
import androidx.recyclerview.widget.RecyclerView;
|
||||||
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
import androidx.swiperefreshlayout.widget.SwipeRefreshLayout;
|
||||||
import androidx.viewpager.widget.PagerAdapter;
|
import androidx.viewpager.widget.PagerAdapter;
|
||||||
import androidx.viewpager.widget.ViewPager;
|
import androidx.viewpager.widget.ViewPager;
|
||||||
|
@ -58,9 +62,15 @@ import com.leinardi.android.speeddial.SpeedDialActionItem;
|
||||||
import com.leinardi.android.speeddial.SpeedDialView;
|
import com.leinardi.android.speeddial.SpeedDialView;
|
||||||
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.Comparator;
|
||||||
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
|
import java.util.stream.Collectors;
|
||||||
|
|
||||||
import eu.siacs.conversations.Config;
|
import eu.siacs.conversations.Config;
|
||||||
import eu.siacs.conversations.R;
|
import eu.siacs.conversations.R;
|
||||||
|
@ -83,6 +93,7 @@ import eu.siacs.conversations.ui.util.PendingItem;
|
||||||
import eu.siacs.conversations.ui.util.SoftKeyboardUtils;
|
import eu.siacs.conversations.ui.util.SoftKeyboardUtils;
|
||||||
import eu.siacs.conversations.ui.widget.SwipeRefreshListFragment;
|
import eu.siacs.conversations.ui.widget.SwipeRefreshListFragment;
|
||||||
import eu.siacs.conversations.utils.AccountUtils;
|
import eu.siacs.conversations.utils.AccountUtils;
|
||||||
|
import eu.siacs.conversations.utils.UIHelper;
|
||||||
import eu.siacs.conversations.utils.XmppUri;
|
import eu.siacs.conversations.utils.XmppUri;
|
||||||
import eu.siacs.conversations.xmpp.Jid;
|
import eu.siacs.conversations.xmpp.Jid;
|
||||||
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
|
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
|
||||||
|
@ -102,6 +113,7 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
||||||
private ListPagerAdapter mListPagerAdapter;
|
private ListPagerAdapter mListPagerAdapter;
|
||||||
private final List<ListItem> contacts = new ArrayList<>();
|
private final List<ListItem> contacts = new ArrayList<>();
|
||||||
private ListItemAdapter mContactsAdapter;
|
private ListItemAdapter mContactsAdapter;
|
||||||
|
private TagsAdapter mTagsAdapter = new TagsAdapter();
|
||||||
private final List<ListItem> conferences = new ArrayList<>();
|
private final List<ListItem> conferences = new ArrayList<>();
|
||||||
private ListItemAdapter mConferenceAdapter;
|
private ListItemAdapter mConferenceAdapter;
|
||||||
private final List<String> mActivatedAccounts = new ArrayList<>();
|
private final List<String> mActivatedAccounts = new ArrayList<>();
|
||||||
|
@ -703,6 +715,15 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
||||||
mSearchEditText = mSearchView.findViewById(R.id.search_field);
|
mSearchEditText = mSearchView.findViewById(R.id.search_field);
|
||||||
mSearchEditText.addTextChangedListener(mSearchTextWatcher);
|
mSearchEditText.addTextChangedListener(mSearchTextWatcher);
|
||||||
mSearchEditText.setOnEditorActionListener(mSearchDone);
|
mSearchEditText.setOnEditorActionListener(mSearchDone);
|
||||||
|
|
||||||
|
SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||||
|
boolean showDynamicTags = preferences.getBoolean(SettingsActivity.SHOW_DYNAMIC_TAGS, getResources().getBoolean(R.bool.show_dynamic_tags));
|
||||||
|
if (showDynamicTags) {
|
||||||
|
RecyclerView tags = mSearchView.findViewById(R.id.tags);
|
||||||
|
tags.setLayoutManager(new LinearLayoutManager(this, LinearLayoutManager.HORIZONTAL, false));
|
||||||
|
tags.setAdapter(mTagsAdapter);
|
||||||
|
}
|
||||||
|
|
||||||
String initialSearchValue = mInitialSearchValue.pop();
|
String initialSearchValue = mInitialSearchValue.pop();
|
||||||
if (initialSearchValue != null) {
|
if (initialSearchValue != null) {
|
||||||
mMenuSearchView.expandActionView();
|
mMenuSearchView.expandActionView();
|
||||||
|
@ -996,7 +1017,9 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
||||||
|
|
||||||
protected void filterContacts(String needle) {
|
protected void filterContacts(String needle) {
|
||||||
this.contacts.clear();
|
this.contacts.clear();
|
||||||
|
ArrayList<ListItem.Tag> tags = new ArrayList<>();
|
||||||
final List<Account> accounts = xmppConnectionService.getAccounts();
|
final List<Account> accounts = xmppConnectionService.getAccounts();
|
||||||
|
boolean foundSopranica = false;
|
||||||
for (Account account : accounts) {
|
for (Account account : accounts) {
|
||||||
if (account.getStatus() != Account.State.DISABLED) {
|
if (account.getStatus() != Account.State.DISABLED) {
|
||||||
for (Contact contact : account.getRoster().getContacts()) {
|
for (Contact contact : account.getRoster().getContacts()) {
|
||||||
|
@ -1006,11 +1029,52 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
||||||
|| (needle != null && !needle.trim().isEmpty())
|
|| (needle != null && !needle.trim().isEmpty())
|
||||||
|| s.compareTo(Presence.Status.OFFLINE) < 0)) {
|
|| s.compareTo(Presence.Status.OFFLINE) < 0)) {
|
||||||
this.contacts.add(contact);
|
this.contacts.add(contact);
|
||||||
|
tags.addAll(contact.getTags(this));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
final Contact self = new Contact(account.getSelfContact());
|
||||||
|
self.setSystemName("Note to Self");
|
||||||
|
if (self.match(this, needle)) {
|
||||||
|
this.contacts.add(self);
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Bookmark bookmark : account.getBookmarks()) {
|
||||||
|
if (bookmark.match(this, needle)) {
|
||||||
|
if (bookmark.getJid().toString().equals("discuss@conference.soprani.ca")) {
|
||||||
|
foundSopranica = true;
|
||||||
|
}
|
||||||
|
this.contacts.add(bookmark);
|
||||||
|
tags.addAll(bookmark.getTags(this));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Comparator<Map.Entry<ListItem.Tag,Integer>> sortTagsBy = Map.Entry.comparingByValue(Comparator.reverseOrder());
|
||||||
|
sortTagsBy = sortTagsBy.thenComparing(entry -> entry.getKey().getName());
|
||||||
|
|
||||||
|
mTagsAdapter.setTags(
|
||||||
|
tags.stream()
|
||||||
|
.collect(Collectors.toMap((x) -> x, (t) -> 1, (c1, c2) -> c1 + c2))
|
||||||
|
.entrySet().stream()
|
||||||
|
.sorted(sortTagsBy)
|
||||||
|
.map(e -> e.getKey()).collect(Collectors.toList())
|
||||||
|
);
|
||||||
Collections.sort(this.contacts);
|
Collections.sort(this.contacts);
|
||||||
|
|
||||||
|
final boolean sopranicaDeleted = getPreferences().getBoolean("cheogram_sopranica_bookmark_deleted", false);
|
||||||
|
|
||||||
|
if (!sopranicaDeleted && !foundSopranica && (needle == null || needle.equals("")) && xmppConnectionService.getAccounts().size() > 0) {
|
||||||
|
Bookmark bookmark = new Bookmark(
|
||||||
|
xmppConnectionService.getAccounts().get(0),
|
||||||
|
Jid.of("discuss@conference.soprani.ca")
|
||||||
|
);
|
||||||
|
bookmark.setBookmarkName("Soprani.ca / Cheogram Discussion");
|
||||||
|
bookmark.addChild("group").setContent("support");
|
||||||
|
this.contacts.add(0, bookmark);
|
||||||
|
}
|
||||||
|
|
||||||
mContactsAdapter.notifyDataSetChanged();
|
mContactsAdapter.notifyDataSetChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1393,4 +1457,65 @@ public class StartConversationActivity extends XmppActivity implements XmppConne
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
class TagsAdapter extends RecyclerView.Adapter<TagsAdapter.ViewHolder> {
|
||||||
|
class ViewHolder extends RecyclerView.ViewHolder {
|
||||||
|
protected TextView tv;
|
||||||
|
|
||||||
|
public ViewHolder(View v) {
|
||||||
|
super(v);
|
||||||
|
tv = (TextView) v;
|
||||||
|
tv.setOnClickListener(view -> {
|
||||||
|
String needle = mSearchEditText.getText().toString();
|
||||||
|
String tag = tv.getText().toString();
|
||||||
|
String[] parts = needle.split("[,\\s]+");
|
||||||
|
if(needle.isEmpty()) {
|
||||||
|
needle = tag;
|
||||||
|
} else if (tag.toLowerCase(Locale.US).contains(parts[parts.length-1])) {
|
||||||
|
needle = needle.replace(parts[parts.length-1], tag);
|
||||||
|
} else {
|
||||||
|
needle += ", " + tag;
|
||||||
|
}
|
||||||
|
mSearchEditText.setText("");
|
||||||
|
mSearchEditText.append(needle);
|
||||||
|
filter(needle);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTag(ListItem.Tag tag) {
|
||||||
|
tv.setText(tag.getName());
|
||||||
|
tv.setBackgroundColor(tag.getColor());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
protected List<ListItem.Tag> tags = new ArrayList<>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public ViewHolder onCreateViewHolder(ViewGroup viewGroup, int i) {
|
||||||
|
View view = LayoutInflater.from(viewGroup.getContext()).inflate(R.layout.list_item_tag, null);
|
||||||
|
return new ViewHolder(view);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onBindViewHolder(ViewHolder viewHolder, int i) {
|
||||||
|
viewHolder.setTag(tags.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getItemCount() {
|
||||||
|
return tags.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
public void setTags(final List<ListItem.Tag> tags) {
|
||||||
|
ListItem.Tag channelTag = new ListItem.Tag("Channel", UIHelper.getColorForName("Channel", true));
|
||||||
|
String needle = mSearchEditText == null ? "" : mSearchEditText.getText().toString().toLowerCase(Locale.US).trim();
|
||||||
|
HashSet<String> parts = new HashSet<>(Arrays.asList(needle.split("[,\\s]+")));
|
||||||
|
this.tags = tags.stream().filter(
|
||||||
|
tag -> !tag.equals(channelTag) && !parts.contains(tag.getName().toLowerCase(Locale.US))
|
||||||
|
).collect(Collectors.toList());
|
||||||
|
if (!parts.contains("channel") && tags.contains(channelTag)) this.tags.add(0, channelTag);
|
||||||
|
notifyDataSetChanged();
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -15,4 +15,10 @@
|
||||||
android:imeOptions="flagNoExtractUi|actionSearch"
|
android:imeOptions="flagNoExtractUi|actionSearch"
|
||||||
android:inputType="textEmailAddress|textNoSuggestions"/>
|
android:inputType="textEmailAddress|textNoSuggestions"/>
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/tags"
|
||||||
|
android:layout_below="@+id/search_field"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
</RelativeLayout>
|
</RelativeLayout>
|
Loading…
Reference in a new issue