tags editor
This commit is contained in:
parent
dca3e80bad
commit
fee1a67332
|
@ -87,6 +87,8 @@ dependencies {
|
|||
implementation 'com.github.bumptech.glide:glide:4.15.1'
|
||||
implementation 'info.androidhive:imagefilters:1.0.7'
|
||||
implementation 'com.github.chrisbanes:PhotoView:2.3.0'
|
||||
|
||||
implementation 'com.splitwise:tokenautocomplete:3.0.2'
|
||||
}
|
||||
|
||||
ext {
|
||||
|
|
18
src/conversations/res/layout/actionview_edit.xml
Normal file
18
src/conversations/res/layout/actionview_edit.xml
Normal file
|
@ -0,0 +1,18 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<RelativeLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:addStatesFromChildren="true"
|
||||
android:focusable="true"
|
||||
android:gravity="center">
|
||||
|
||||
<EditText
|
||||
android:id="@+id/search_field"
|
||||
style="@style/Widget.Conversations.SearchView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:focusable="true"
|
||||
android:imeOptions="flagNoExtractUi|actionDone"
|
||||
android:inputType="textPersonName"/>
|
||||
|
||||
</RelativeLayout>
|
|
@ -184,12 +184,19 @@ public class Contact implements ListItem, Blockable {
|
|||
return jid;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Tag> getTags(Context context) {
|
||||
public List<Tag> getGroupTags() {
|
||||
final ArrayList<Tag> tags = new ArrayList<>();
|
||||
for (final String group : getGroups(true)) {
|
||||
tags.add(new Tag(group, UIHelper.getColorForName(group)));
|
||||
}
|
||||
return tags;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<Tag> getTags(Context context) {
|
||||
final HashSet<Tag> tags = new HashSet<>();
|
||||
tags.addAll(getGroupTags());
|
||||
|
||||
Presence.Status status = getShownStatus();
|
||||
if (status != Presence.Status.OFFLINE) {
|
||||
tags.add(UIHelper.getTagForStatus(context, status));
|
||||
|
@ -197,7 +204,10 @@ public class Contact implements ListItem, Blockable {
|
|||
if (isBlocked()) {
|
||||
tags.add(new Tag(context.getString(R.string.blocked), 0xff2e2f3b));
|
||||
}
|
||||
return tags;
|
||||
if (!showInRoster() && getSystemAccount() != null) {
|
||||
tags.add(new Tag("Android", UIHelper.getColorForName("Android")));
|
||||
}
|
||||
return new ArrayList<>(tags);
|
||||
}
|
||||
|
||||
public boolean match(Context context, String needle) {
|
||||
|
@ -316,6 +326,10 @@ public class Contact implements ListItem, Blockable {
|
|||
return systemAccount;
|
||||
}
|
||||
|
||||
public void setGroups(List<String> groups) {
|
||||
this.groups = new JSONArray(groups);
|
||||
}
|
||||
|
||||
public void setSystemAccount(Uri lookupUri) {
|
||||
this.systemAccount = lookupUri;
|
||||
}
|
||||
|
|
|
@ -2,6 +2,7 @@ package eu.siacs.conversations.entities;
|
|||
|
||||
import android.content.Context;
|
||||
|
||||
import java.io.Serializable;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
|
||||
|
@ -16,7 +17,7 @@ public interface ListItem extends Comparable<ListItem>, AvatarService.Avatarable
|
|||
|
||||
List<Tag> getTags(Context context);
|
||||
|
||||
final class Tag {
|
||||
final class Tag implements Serializable {
|
||||
private final String name;
|
||||
private final int color;
|
||||
|
||||
|
|
|
@ -2,10 +2,12 @@ package eu.siacs.conversations.ui;
|
|||
|
||||
import android.Manifest;
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.content.Intent;
|
||||
import android.content.SharedPreferences;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.net.Uri;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
|
@ -16,13 +18,18 @@ import android.provider.ContactsContract.Intents;
|
|||
import android.text.Spannable;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.RelativeSizeSpan;
|
||||
import android.util.TypedValue;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CompoundButton;
|
||||
import android.widget.CompoundButton.OnCheckedChangeListener;
|
||||
import android.widget.EditText;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
|
||||
|
@ -32,9 +39,13 @@ import androidx.databinding.DataBindingUtil;
|
|||
|
||||
import org.openintents.openpgp.util.OpenPgpUtils;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
|
@ -43,6 +54,7 @@ import eu.siacs.conversations.crypto.axolotl.FingerprintStatus;
|
|||
import eu.siacs.conversations.crypto.axolotl.XmppAxolotlSession;
|
||||
import eu.siacs.conversations.databinding.ActivityContactDetailsBinding;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.entities.Bookmark;
|
||||
import eu.siacs.conversations.entities.Contact;
|
||||
import eu.siacs.conversations.entities.ListItem;
|
||||
import eu.siacs.conversations.services.AbstractQuickConversationsService;
|
||||
|
@ -55,6 +67,7 @@ import eu.siacs.conversations.ui.util.AvatarWorkerTask;
|
|||
import eu.siacs.conversations.ui.util.GridManager;
|
||||
import eu.siacs.conversations.ui.util.JidDialog;
|
||||
import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
|
||||
import eu.siacs.conversations.ui.util.SoftKeyboardUtils;
|
||||
import eu.siacs.conversations.utils.AccountUtils;
|
||||
import eu.siacs.conversations.utils.Compatibility;
|
||||
import eu.siacs.conversations.utils.Emoticons;
|
||||
|
@ -62,6 +75,7 @@ import eu.siacs.conversations.utils.IrregularUnicodeDetector;
|
|||
import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
|
||||
import eu.siacs.conversations.utils.UIHelper;
|
||||
import eu.siacs.conversations.utils.XmppUri;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xml.Namespace;
|
||||
import eu.siacs.conversations.xmpp.Jid;
|
||||
import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
|
||||
|
@ -73,6 +87,8 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
|
|||
private final int REQUEST_SYNC_CONTACTS = 0x28cf;
|
||||
ActivityContactDetailsBinding binding;
|
||||
private MediaAdapter mMediaAdapter;
|
||||
protected MenuItem edit = null;
|
||||
protected MenuItem save = null;
|
||||
|
||||
private Contact contact;
|
||||
private final DialogInterface.OnClickListener removeFromRoster = new DialogInterface.OnClickListener() {
|
||||
|
@ -185,7 +201,6 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
|
|||
|
||||
@Override
|
||||
protected void refreshUiReal() {
|
||||
invalidateOptionsMenu();
|
||||
populateView();
|
||||
}
|
||||
|
||||
|
@ -194,7 +209,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
|
|||
if (http) {
|
||||
return "https://conversations.im/i/" + XmppUri.lameUrlEncode(contact.getJid().asBareJid().toEscapedString());
|
||||
} else {
|
||||
return "xmpp:" + contact.getJid().asBareJid().toEscapedString();
|
||||
return "xmpp:" + Uri.encode(contact.getJid().asBareJid().toEscapedString(), "@/+");
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -242,7 +257,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
|
|||
recreate();
|
||||
} else {
|
||||
final SharedPreferences preferences = PreferenceManager.getDefaultSharedPreferences(this);
|
||||
this.showDynamicTags = preferences.getBoolean(SettingsActivity.SHOW_DYNAMIC_TAGS, false);
|
||||
this.showDynamicTags = preferences.getBoolean(SettingsActivity.SHOW_DYNAMIC_TAGS, getResources().getBoolean(R.bool.show_dynamic_tags));
|
||||
this.showLastSeen = preferences.getBoolean("last_activity", false);
|
||||
}
|
||||
binding.mediaWrapper.setVisibility(Compatibility.hasStoragePermission(this) ? View.VISIBLE : View.GONE);
|
||||
|
@ -262,6 +277,19 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
|
|||
}
|
||||
}
|
||||
|
||||
protected void saveEdits() {
|
||||
binding.editTags.setVisibility(View.GONE);
|
||||
if (edit != null) {
|
||||
EditText text = edit.getActionView().findViewById(R.id.search_field);
|
||||
contact.setServerName(text.getText().toString());
|
||||
contact.setGroups(binding.editTags.getObjects().stream().map(tag -> tag.getName()).collect(Collectors.toList()));
|
||||
ContactDetailsActivity.this.xmppConnectionService.pushContactToServer(contact);
|
||||
populateView();
|
||||
edit.collapseActionView();
|
||||
}
|
||||
if (save != null) save.setVisible(false);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(final MenuItem menuItem) {
|
||||
if (MenuDoubleTabUtil.shouldIgnoreTap()) {
|
||||
|
@ -285,16 +313,56 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
|
|||
.setPositiveButton(getString(R.string.delete),
|
||||
removeFromRoster).create().show();
|
||||
break;
|
||||
case R.id.action_save:
|
||||
saveEdits();
|
||||
break;
|
||||
case R.id.action_edit_contact:
|
||||
Uri systemAccount = contact.getSystemAccount();
|
||||
if (systemAccount == null) {
|
||||
quickEdit(contact.getServerName(), R.string.contact_name, value -> {
|
||||
contact.setServerName(value);
|
||||
ContactDetailsActivity.this.xmppConnectionService.pushContactToServer(contact);
|
||||
populateView();
|
||||
return null;
|
||||
}, true);
|
||||
menuItem.expandActionView();
|
||||
EditText text = menuItem.getActionView().findViewById(R.id.search_field);
|
||||
text.setOnEditorActionListener((v, actionId, event) -> {
|
||||
saveEdits();
|
||||
return true;
|
||||
});
|
||||
text.setText(contact.getServerName());
|
||||
text.requestFocus();
|
||||
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
|
||||
if (imm != null) {
|
||||
imm.showSoftInput(text, InputMethodManager.SHOW_IMPLICIT);
|
||||
}
|
||||
binding.tags.setVisibility(View.GONE);
|
||||
binding.editTags.clearSync();
|
||||
for (final ListItem.Tag group : contact.getGroupTags()) {
|
||||
binding.editTags.addObjectSync(group);
|
||||
}
|
||||
ArrayList<ListItem.Tag> tags = new ArrayList<>();
|
||||
for (final Account account : xmppConnectionService.getAccounts()) {
|
||||
for (Contact contact : account.getRoster().getContacts()) {
|
||||
tags.addAll(contact.getTags(this));
|
||||
}
|
||||
for (Bookmark bookmark : account.getBookmarks()) {
|
||||
tags.addAll(bookmark.getTags(this));
|
||||
}
|
||||
}
|
||||
Comparator<Map.Entry<ListItem.Tag,Integer>> sortTagsBy = Map.Entry.comparingByValue(Comparator.reverseOrder());
|
||||
sortTagsBy = sortTagsBy.thenComparing(entry -> entry.getKey().getName());
|
||||
|
||||
ArrayAdapter<ListItem.Tag> adapter = new ArrayAdapter<>(
|
||||
this,
|
||||
android.R.layout.simple_list_item_1,
|
||||
tags.stream()
|
||||
.collect(Collectors.toMap((x) -> x, (t) -> 1, (c1, c2) -> c1 + c2))
|
||||
.entrySet().stream()
|
||||
.sorted(sortTagsBy)
|
||||
.map(e -> e.getKey()).collect(Collectors.toList())
|
||||
);
|
||||
binding.editTags.setAdapter(adapter);
|
||||
if (showDynamicTags) binding.editTags.setVisibility(View.VISIBLE);
|
||||
if (save != null) save.setVisible(true);
|
||||
} else {
|
||||
menuItem.collapseActionView();
|
||||
if (save != null) save.setVisible(false);
|
||||
Intent intent = new Intent(Intent.ACTION_EDIT);
|
||||
intent.setDataAndType(systemAccount, Contacts.CONTENT_ITEM_TYPE);
|
||||
intent.putExtra("finishActivityOnSaveCompleted", true);
|
||||
|
@ -320,6 +388,8 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
|
|||
public boolean onCreateOptionsMenu(final Menu menu) {
|
||||
getMenuInflater().inflate(R.menu.contact_details, menu);
|
||||
AccountUtils.showHideMenuItems(menu);
|
||||
edit = menu.findItem(R.id.action_edit_contact);
|
||||
save = menu.findItem(R.id.action_save);
|
||||
MenuItem block = menu.findItem(R.id.action_block);
|
||||
MenuItem unblock = menu.findItem(R.id.action_unblock);
|
||||
MenuItem edit = menu.findItem(R.id.action_edit_contact);
|
||||
|
@ -342,6 +412,19 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
|
|||
edit.setVisible(false);
|
||||
delete.setVisible(false);
|
||||
}
|
||||
edit.setOnActionExpandListener(new MenuItem.OnActionExpandListener() {
|
||||
@Override
|
||||
public boolean onMenuItemActionCollapse(MenuItem item) {
|
||||
SoftKeyboardUtils.hideSoftKeyboard(ContactDetailsActivity.this);
|
||||
binding.editTags.setVisibility(View.GONE);
|
||||
if (save != null) save.setVisible(false);
|
||||
populateView();
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onMenuItemActionExpand(MenuItem item) { return true; }
|
||||
});
|
||||
return super.onCreateOptionsMenu(menu);
|
||||
}
|
||||
|
||||
|
@ -349,6 +432,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
|
|||
if (contact == null) {
|
||||
return;
|
||||
}
|
||||
if (binding.editTags.getVisibility() != View.GONE) return;
|
||||
invalidateOptionsMenu();
|
||||
setTitle(contact.getDisplayName());
|
||||
if (contact.showInRoster()) {
|
||||
|
@ -549,6 +633,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
|
|||
xmppConnectionService.getAttachments(account, contact.getJid().asBareJid(), limit, this);
|
||||
this.binding.showMedia.setOnClickListener((v) -> MediaBrowserActivity.launch(this, contact));
|
||||
}
|
||||
|
||||
populateView();
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,57 @@
|
|||
package eu.siacs.conversations.ui.widget;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.Context;
|
||||
import android.graphics.Rect;
|
||||
import android.util.AttributeSet;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.TextView;
|
||||
|
||||
import com.tokenautocomplete.TokenCompleteTextView;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.ListItem;
|
||||
import eu.siacs.conversations.utils.UIHelper;
|
||||
|
||||
public class TagEditorView extends TokenCompleteTextView<ListItem.Tag> {
|
||||
public TagEditorView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
setTokenClickStyle(TokenCompleteTextView.TokenClickStyle.Delete);
|
||||
setThreshold(1);
|
||||
performBestGuess(false);
|
||||
allowCollapse(false);
|
||||
}
|
||||
|
||||
public void clearSync() {
|
||||
for (ListItem.Tag tag : getObjects()) {
|
||||
removeObjectSync(tag);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
protected View getViewForObject(ListItem.Tag tag) {
|
||||
LayoutInflater inflater = (LayoutInflater) getContext().getSystemService(Activity.LAYOUT_INFLATER_SERVICE);
|
||||
final TextView tv = (TextView) inflater.inflate(R.layout.list_item_tag, (ViewGroup) getParent(), false);
|
||||
tv.setText(tag.getName());
|
||||
tv.setBackgroundColor(tag.getColor());
|
||||
return tv;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected ListItem.Tag defaultObject(String completionText) {
|
||||
return new ListItem.Tag(completionText, UIHelper.getColorForName(completionText));
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean shouldIgnoreToken(ListItem.Tag tag) {
|
||||
return getObjects().contains(tag);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onFocusChanged(boolean hasFocus, int direction, Rect previous) {
|
||||
super.onFocusChanged(hasFocus, direction, previous);
|
||||
performCompletion();
|
||||
}
|
||||
}
|
|
@ -68,6 +68,16 @@
|
|||
android:layout_marginTop="4dp"
|
||||
android:orientation="horizontal"></com.wefika.flowlayout.FlowLayout>
|
||||
|
||||
<eu.siacs.conversations.ui.widget.TagEditorView
|
||||
android:id="@+id/edit_tags"
|
||||
android:visibility="gone"
|
||||
android:hint="Tags"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_marginLeft="-4dp"
|
||||
android:layout_marginTop="-4dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/details_lastseen"
|
||||
android:layout_width="wrap_content"
|
||||
|
|
|
@ -1,13 +1,21 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<menu xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<item
|
||||
android:id="@+id/action_edit_contact"
|
||||
android:icon="?attr/icon_edit"
|
||||
android:orderInCategory="10"
|
||||
app:showAsAction="always"
|
||||
android:title="@string/action_edit_contact"/>
|
||||
app:showAsAction="collapseActionView|always"
|
||||
android:title="@string/action_edit_contact"
|
||||
app:actionLayout="@layout/actionview_edit" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_save"
|
||||
android:icon="@drawable/ic_save_white_24dp"
|
||||
android:title="@string/save"
|
||||
android:visible="false"
|
||||
app:showAsAction="always" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_share"
|
||||
|
@ -61,4 +69,4 @@
|
|||
app:showAsAction="never"
|
||||
android:title="@string/action_settings"/>
|
||||
|
||||
</menu>
|
||||
</menu>
|
||||
|
|
|
@ -353,7 +353,7 @@
|
|||
<string name="no_application_found_to_open_link">No app found to open link</string>
|
||||
<string name="no_application_found_to_view_contact">No app found to view contact</string>
|
||||
<string name="pref_show_dynamic_tags">Dynamic Tags</string>
|
||||
<string name="pref_show_dynamic_tags_summary">Display read-only tags underneath contacts</string>
|
||||
<string name="pref_show_dynamic_tags_summary">Allow organizing with tags</string>
|
||||
<string name="enable_notifications">Enable notifications</string>
|
||||
<string name="no_conference_server_found">No group chat server found</string>
|
||||
<string name="conference_creation_failed">Could not create group chat</string>
|
||||
|
|
Loading…
Reference in a new issue