display irregular unicode code points

This commit is contained in:
Daniel Gultsch 2018-03-08 14:02:48 +01:00
parent 52135625d8
commit 6944c12186
10 changed files with 177 additions and 38 deletions

View file

@ -63,16 +63,6 @@ public class Bookmark extends Element implements ListItem {
} }
} }
@Override
public String getDisplayJid() {
Jid jid = getJid();
if (jid != null) {
return jid.toString();
} else {
return getAttribute("jid"); //fallback if jid wasn't parsable
}
}
@Override @Override
public Jid getJid() { public Jid getJid() {
return this.getAttributeAsJid("jid"); return this.getAttributeAsJid("jid");

View file

@ -127,15 +127,6 @@ public class Contact implements ListItem, Blockable {
} }
} }
@Override
public String getDisplayJid() {
if (jid != null) {
return jid.toString();
} else {
return null;
}
}
public String getProfilePhoto() { public String getProfilePhoto() {
return this.photoUri; return this.photoUri;
} }

View file

@ -10,8 +10,6 @@ import rocks.xmpp.addr.Jid;
public interface ListItem extends Comparable<ListItem> { public interface ListItem extends Comparable<ListItem> {
String getDisplayName(); String getDisplayName();
String getDisplayJid();
Jid getJid(); Jid getJid();
List<Tag> getTags(Context context); List<Tag> getTags(Context context);

View file

@ -38,6 +38,7 @@ import eu.siacs.conversations.entities.Contact;
import eu.siacs.conversations.entities.ListItem; import eu.siacs.conversations.entities.ListItem;
import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnAccountUpdate;
import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate; import eu.siacs.conversations.services.XmppConnectionService.OnRosterUpdate;
import eu.siacs.conversations.utils.IrregularUnicodeBlockDetector;
import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.UIHelper;
import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.utils.XmppUri;
import eu.siacs.conversations.xml.Namespace; import eu.siacs.conversations.xml.Namespace;
@ -129,8 +130,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
AlertDialog.Builder builder = new AlertDialog.Builder( AlertDialog.Builder builder = new AlertDialog.Builder(
ContactDetailsActivity.this); ContactDetailsActivity.this);
builder.setTitle(getString(R.string.action_add_phone_book)); builder.setTitle(getString(R.string.action_add_phone_book));
builder.setMessage(getString(R.string.add_phone_book_text, builder.setMessage(getString(R.string.add_phone_book_text, contact.getJid().toString()));
contact.getDisplayJid()));
builder.setNegativeButton(getString(R.string.cancel), null); builder.setNegativeButton(getString(R.string.cancel), null);
builder.setPositiveButton(getString(R.string.add), addToPhonebook); builder.setPositiveButton(getString(R.string.add), addToPhonebook);
builder.create().show(); builder.create().show();
@ -235,9 +235,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
break; break;
case R.id.action_delete_contact: case R.id.action_delete_contact:
builder.setTitle(getString(R.string.action_delete_contact)) builder.setTitle(getString(R.string.action_delete_contact))
.setMessage( .setMessage(getString(R.string.remove_contact_text, contact.getJid().toString()))
getString(R.string.remove_contact_text,
contact.getDisplayJid()))
.setPositiveButton(getString(R.string.delete), .setPositiveButton(getString(R.string.delete),
removeFromRoster).create().show(); removeFromRoster).create().show();
break; break;
@ -386,12 +384,7 @@ public class ContactDetailsActivity extends OmemoActivity implements OnAccountUp
} }
} }
if (contact.getPresences().size() > 1) { binding.detailsContactjid.setText(IrregularUnicodeBlockDetector.style(this,contact.getJid()));
binding.detailsContactjid.setText(contact.getDisplayJid() + " ("
+ contact.getPresences().size() + ")");
} else {
binding.detailsContactjid.setText(contact.getDisplayJid());
}
String account; String account;
if (Config.DOMAIN_LOCK != null) { if (Config.DOMAIN_LOCK != null) {
account = contact.getAccount().getJid().getLocal(); account = contact.getAccount().getJid().getLocal();

View file

@ -35,6 +35,7 @@ import eu.siacs.conversations.databinding.KeysCardBinding;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.utils.CryptoHelper; import eu.siacs.conversations.utils.CryptoHelper;
import eu.siacs.conversations.utils.IrregularUnicodeBlockDetector;
import eu.siacs.conversations.utils.XmppUri; import eu.siacs.conversations.utils.XmppUri;
import eu.siacs.conversations.xmpp.OnKeyStatusUpdated; import eu.siacs.conversations.xmpp.OnKeyStatusUpdated;
import rocks.xmpp.addr.Jid; import rocks.xmpp.addr.Jid;
@ -195,9 +196,8 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) { for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) {
hasForeignKeys = true; hasForeignKeys = true;
KeysCardBinding keysCardBinding = DataBindingUtil.inflate(getLayoutInflater(),R.layout.keys_card, binding.foreignKeys,false); KeysCardBinding keysCardBinding = DataBindingUtil.inflate(getLayoutInflater(),R.layout.keys_card, binding.foreignKeys,false);
//final LinearLayout layout = (LinearLayout) getLayoutInflater().inflate(R.layout.keys_card, foreignKeys, false);
final Jid jid = entry.getKey(); final Jid jid = entry.getKey();
keysCardBinding.foreignKeysTitle.setText(jid.toString()); keysCardBinding.foreignKeysTitle.setText(IrregularUnicodeBlockDetector.style(this,jid));
keysCardBinding.foreignKeysTitle.setOnClickListener(v -> switchToContactDetails(mAccount.getRoster().getContact(jid))); keysCardBinding.foreignKeysTitle.setOnClickListener(v -> switchToContactDetails(mAccount.getRoster().getContact(jid)));
final Map<String, Boolean> fingerprints = entry.getValue(); final Map<String, Boolean> fingerprints = entry.getValue();
for (final String fingerprint : fingerprints.keySet()) { for (final String fingerprint : fingerprints.keySet()) {
@ -397,7 +397,7 @@ public class TrustKeysActivity extends OmemoActivity implements OnKeyStatusUpdat
fingerprint, fingerprint,
FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint))); FingerprintStatus.createActive(ownKeysToTrust.get(fingerprint)));
} }
List<Jid> acceptedTargets = mConversation == null ? new ArrayList<Jid>() : mConversation.getAcceptedCryptoTargets(); List<Jid> acceptedTargets = mConversation == null ? new ArrayList<>() : mConversation.getAcceptedCryptoTargets();
synchronized (this.foreignKeysToTrust) { synchronized (this.foreignKeysToTrust) {
for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) { for (Map.Entry<Jid, Map<String, Boolean>> entry : foreignKeysToTrust.entrySet()) {
Jid jid = entry.getKey(); Jid jid = entry.getKey();

View file

@ -1,6 +1,5 @@
package eu.siacs.conversations.ui.adapter; package eu.siacs.conversations.ui.adapter;
import android.content.Context;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Resources; import android.content.res.Resources;
import android.databinding.DataBindingUtil; import android.databinding.DataBindingUtil;
@ -27,8 +26,11 @@ import eu.siacs.conversations.databinding.ContactBinding;
import eu.siacs.conversations.entities.ListItem; import eu.siacs.conversations.entities.ListItem;
import eu.siacs.conversations.ui.SettingsActivity; import eu.siacs.conversations.ui.SettingsActivity;
import eu.siacs.conversations.ui.XmppActivity; import eu.siacs.conversations.ui.XmppActivity;
import eu.siacs.conversations.ui.util.Color;
import eu.siacs.conversations.utils.EmojiWrapper; import eu.siacs.conversations.utils.EmojiWrapper;
import eu.siacs.conversations.utils.IrregularUnicodeBlockDetector;
import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.UIHelper;
import rocks.xmpp.addr.Jid;
public class ListItemAdapter extends ArrayAdapter<ListItem> { public class ListItemAdapter extends ArrayAdapter<ListItem> {
@ -108,10 +110,10 @@ public class ListItemAdapter extends ArrayAdapter<ListItem> {
viewHolder.tags.addView(tv); viewHolder.tags.addView(tv);
} }
} }
final String jid = item.getDisplayJid(); final Jid jid = item.getJid();
if (jid != null) { if (jid != null) {
viewHolder.jid.setVisibility(View.VISIBLE); viewHolder.jid.setVisibility(View.VISIBLE);
viewHolder.jid.setText(jid); viewHolder.jid.setText(IrregularUnicodeBlockDetector.style(activity, jid));
} else { } else {
viewHolder.jid.setVisibility(View.GONE); viewHolder.jid.setVisibility(View.GONE);
} }

View file

@ -0,0 +1,160 @@
/*
* Copyright (c) 2018, Daniel Gultsch All rights reserved.
*
* Redistribution and use in source and binary forms, with or without modification,
* are permitted provided that the following conditions are met:
*
* 1. Redistributions of source code must retain the above copyright notice, this
* list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright notice,
* this list of conditions and the following disclaimer in the documentation and/or
* other materials provided with the distribution.
*
* 3. Neither the name of the copyright holder nor the names of its contributors
* may be used to endorse or promote products derived from this software without
* specific prior written permission.
*
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR
* ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
* ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
*/
package eu.siacs.conversations.utils;
import android.content.Context;
import android.support.annotation.ColorInt;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.SpannableStringBuilder;
import android.text.style.ForegroundColorSpan;
import android.util.LruCache;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Set;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import eu.siacs.conversations.R;
import eu.siacs.conversations.ui.util.Color;
import rocks.xmpp.addr.Jid;
public class IrregularUnicodeBlockDetector {
private static final Map<Character.UnicodeBlock,Character.UnicodeBlock> NORMALIZATION_MAP;
static {
Map<Character.UnicodeBlock,Character.UnicodeBlock> temp = new HashMap<>();
temp.put(Character.UnicodeBlock.LATIN_1_SUPPLEMENT, Character.UnicodeBlock.BASIC_LATIN);
NORMALIZATION_MAP = Collections.unmodifiableMap(temp);
}
private static Character.UnicodeBlock normalize(Character.UnicodeBlock in) {
if (NORMALIZATION_MAP.containsKey(in)) {
return NORMALIZATION_MAP.get(in);
} else {
return in;
}
}
private static final LruCache<Jid, Pattern> CACHE = new LruCache<>(100);
public static Spannable style(Context context, Jid jid) {
return style(jid, Color.get(context, R.attr.color_warning));
}
private static Spannable style(Jid jid, @ColorInt int color) {
SpannableStringBuilder builder = new SpannableStringBuilder();
if (jid.getLocal() != null) {
SpannableString local = new SpannableString(jid.getLocal());
Matcher matcher = find(jid).matcher(local);
while (matcher.find()) {
if (matcher.start() < matcher.end()) {
local.setSpan(new ForegroundColorSpan(color), matcher.start(), matcher.end(), Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
builder.append(local);
builder.append('@');
}
if (jid.getDomain() != null) {
builder.append(jid.getDomain());
}
if (builder.length() != 0 && jid.getResource() != null) {
builder.append('/');
builder.append(jid.getResource());
}
return builder;
}
private static Map<Character.UnicodeBlock, List<String>> map(Jid jid) {
Map<Character.UnicodeBlock, List<String>> map = new HashMap<>();
String local = jid.getLocal();
final int length = local.length();
for (int offset = 0; offset < length; ) {
final int codePoint = local.codePointAt(offset);
Character.UnicodeBlock block = normalize(Character.UnicodeBlock.of(codePoint));
List<String> codePoints;
if (map.containsKey(block)) {
codePoints = map.get(block);
} else {
codePoints = new ArrayList<>();
map.put(block, codePoints);
}
codePoints.add(String.copyValueOf(Character.toChars(codePoint)));
offset += Character.charCount(codePoint);
}
return map;
}
private static Set<String> eliminateFirstAndGetCodePoints(Map<Character.UnicodeBlock, List<String>> map) {
Character.UnicodeBlock block = Character.UnicodeBlock.BASIC_LATIN;
int size = 0;
for (Map.Entry<Character.UnicodeBlock, List<String>> entry : map.entrySet()) {
if (entry.getValue().size() > size) {
size = entry.getValue().size();
block = entry.getKey();
}
}
map.remove(block);
Set<String> all = new HashSet<>();
for (List<String> codePoints : map.values()) {
all.addAll(codePoints);
}
return all;
}
private static Pattern find(Jid jid) {
synchronized (CACHE) {
Pattern pattern = CACHE.get(jid);
if (pattern != null) {
return pattern;
}
pattern = create(eliminateFirstAndGetCodePoints(map(jid)));
CACHE.put(jid, pattern);
return pattern;
}
}
private static Pattern create(Set<String> codePoints) {
final StringBuilder pattern = new StringBuilder();
for (String codePoint : codePoints) {
if (pattern.length() != 0) {
pattern.append('|');
}
pattern.append(Pattern.quote(codePoint));
}
return Pattern.compile(pattern.toString());
}
}

View file

@ -8,6 +8,7 @@
<attr name="color_background_secondary" format="reference|color" /> <attr name="color_background_secondary" format="reference|color" />
<attr name="color_background_primary" format="reference|color" /> <attr name="color_background_primary" format="reference|color" />
<attr name="color_warning" format="reference|color"/>
<attr name="ic_send_cancel_offline" format="reference"/> <attr name="ic_send_cancel_offline" format="reference"/>
<attr name="ic_send_location_offline" format="reference"/> <attr name="ic_send_location_offline" format="reference"/>

View file

@ -19,6 +19,8 @@
<color name="grey800">#ff424242</color> <color name="grey800">#ff424242</color>
<color name="grey900">#ff282828</color> <color name="grey900">#ff282828</color>
<color name="red500">#fff44336</color> <color name="red500">#fff44336</color>
<color name="red_a700">#ffd50000</color>
<color name="red_a100">#ffff8a80</color>
<color name="red800">#ffc62828</color> <color name="red800">#ffc62828</color>
<color name="orange500">#ffff9800</color> <color name="orange500">#ffff9800</color>
<color name="green500">#ff259b24</color> <color name="green500">#ff259b24</color>

View file

@ -8,6 +8,7 @@
<item name="color_background_primary">@color/grey50</item> <item name="color_background_primary">@color/grey50</item>
<item name="color_background_secondary">@color/grey200</item> <item name="color_background_secondary">@color/grey200</item>
<item name="color_warning">@color/red_a700</item>
<item name="android:windowActionModeOverlay">true</item> <item name="android:windowActionModeOverlay">true</item>
<item name="android:actionModeBackground">@color/accent</item> <item name="android:actionModeBackground">@color/accent</item>
@ -84,6 +85,7 @@
<item name="color_background_primary">@color/grey800</item> <item name="color_background_primary">@color/grey800</item>
<item name="color_background_secondary">@color/grey900</item> <item name="color_background_secondary">@color/grey900</item>
<item name="color_warning">@color/red_a100</item>
<item name="android:windowActionModeOverlay">true</item> <item name="android:windowActionModeOverlay">true</item>
<item name="android:actionModeBackground">@color/accent</item> <item name="android:actionModeBackground">@color/accent</item>