Merge pull request #519 from nextcloud/chips-magic

Adds mention chips
This commit is contained in:
Mario Đanić 2019-04-10 14:39:07 +02:00 committed by GitHub
commit f35c4b486c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
26 changed files with 442 additions and 67 deletions

View File

@ -17,8 +17,8 @@ android {
targetSdkVersion 28 targetSdkVersion 28
testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner"
versionCode 90 versionCode 91
versionName "6.0.0beta2" versionName "6.0.0beta3"
flavorDimensions "default" flavorDimensions "default"
renderscriptTargetApi 19 renderscriptTargetApi 19
@ -100,7 +100,6 @@ android {
} }
ext { ext {
supportLibraryVersion = '28.0.0'
workVersion = "1.0.0" workVersion = "1.0.0"
} }
@ -115,17 +114,17 @@ dependencies {
implementation fileTree(dir: 'libs', include: ['*.jar']) implementation fileTree(dir: 'libs', include: ['*.jar'])
implementation 'androidx.appcompat:appcompat:1.0.2' implementation 'androidx.appcompat:appcompat:1.0.2'
implementation 'com.google.android.material:material:1.0.0' implementation 'com.google.android.material:material:1.0.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha3' implementation 'androidx.constraintlayout:constraintlayout:2.0.0-alpha4'
implementation 'com.github.vanniktech:Emoji:746caa4623' implementation 'com.github.vanniktech:Emoji:746caa4623'
implementation 'org.michaelevans.colorart:library:0.0.3' implementation 'org.michaelevans.colorart:library:0.0.3'
implementation "android.arch.work:work-runtime:${workVersion}" implementation "android.arch.work:work-runtime:${workVersion}"
implementation "android.arch.work:work-rxjava2:${workVersion}" implementation "android.arch.work:work-rxjava2:${workVersion}"
androidTestImplementation "android.arch.work:work-testing:${workVersion}" androidTestImplementation "android.arch.work:work-testing:${workVersion}"
implementation 'androidx.biometric:biometric:1.0.0-alpha03' implementation 'androidx.biometric:biometric:1.0.0-alpha04'
implementation "androidx.lifecycle:lifecycle-extensions:2.0.0" implementation "androidx.lifecycle:lifecycle-extensions:2.0.0"
implementation 'androidx.multidex:multidex:2.0.0' implementation 'androidx.multidex:multidex:2.0.1'
implementation 'io.reactivex.rxjava2:rxandroid:2.1.1' implementation 'io.reactivex.rxjava2:rxandroid:2.1.1'
implementation "io.reactivex.rxjava2:rxjava:2.2.7" implementation "io.reactivex.rxjava2:rxjava:2.2.7"

View File

@ -45,18 +45,28 @@ import java.util.List;
public class MentionAutocompleteItem extends AbstractFlexibleItem<UserItem.UserItemViewHolder> public class MentionAutocompleteItem extends AbstractFlexibleItem<UserItem.UserItemViewHolder>
implements IFilterable<String> { implements IFilterable<String> {
private String userId; private String objectId;
private String displayName; private String displayName;
private String source;
private UserEntity currentUser; private UserEntity currentUser;
public MentionAutocompleteItem(String userId, String displayName, UserEntity currentUser) { public MentionAutocompleteItem(String objectId, String displayName, String source, UserEntity currentUser) {
this.userId = userId; this.objectId = objectId;
this.displayName = displayName; this.displayName = displayName;
this.source = source;
this.currentUser = currentUser; this.currentUser = currentUser;
} }
public String getUserId() { public String getSource() {
return userId; return source;
}
public void setSource(String source) {
this.source = source;
}
public String getObjectId() {
return objectId;
} }
public String getDisplayName() { public String getDisplayName() {
@ -67,7 +77,7 @@ public class MentionAutocompleteItem extends AbstractFlexibleItem<UserItem.UserI
public boolean equals(Object o) { public boolean equals(Object o) {
if (o instanceof MentionAutocompleteItem) { if (o instanceof MentionAutocompleteItem) {
MentionAutocompleteItem inItem = (MentionAutocompleteItem) o; MentionAutocompleteItem inItem = (MentionAutocompleteItem) o;
return (userId.equals(inItem.userId) && displayName.equals(inItem.displayName)); return (objectId.equals(inItem.objectId) && displayName.equals(inItem.displayName));
} }
return false; return false;
@ -93,22 +103,22 @@ public class MentionAutocompleteItem extends AbstractFlexibleItem<UserItem.UserI
String.valueOf(adapter.getFilter(String.class)), NextcloudTalkApplication.getSharedApplication() String.valueOf(adapter.getFilter(String.class)), NextcloudTalkApplication.getSharedApplication()
.getResources().getColor(R.color.colorPrimary)); .getResources().getColor(R.color.colorPrimary));
if (holder.contactMentionId != null) { if (holder.contactMentionId != null) {
FlexibleUtils.highlightText(holder.contactMentionId, "@" + userId, FlexibleUtils.highlightText(holder.contactMentionId, "@" + objectId,
String.valueOf(adapter.getFilter(String.class)), NextcloudTalkApplication.getSharedApplication() String.valueOf(adapter.getFilter(String.class)), NextcloudTalkApplication.getSharedApplication()
.getResources().getColor(R.color.colorPrimary)); .getResources().getColor(R.color.colorPrimary));
} }
} else { } else {
holder.contactDisplayName.setText(displayName); holder.contactDisplayName.setText(displayName);
if (holder.contactMentionId != null) { if (holder.contactMentionId != null) {
holder.contactMentionId.setText("@" + userId); holder.contactMentionId.setText("@" + objectId);
} }
} }
if (userId.equals("all")) { if (source.equals("calls")) {
holder.avatarFlipView.setFrontImageBitmap(DisplayUtils.getRoundedBitmapFromVectorDrawableResource(NextcloudTalkApplication.getSharedApplication().getResources(), R.drawable.ic_people_group_white_24px)); holder.avatarFlipView.setFrontImageBitmap(DisplayUtils.getRoundedBitmapFromVectorDrawableResource(NextcloudTalkApplication.getSharedApplication().getResources(), R.drawable.ic_people_group_white_24px));
} else { } else {
GlideUrl glideUrl = new GlideUrl(ApiUtils.getUrlForAvatarWithName(currentUser.getBaseUrl(), GlideUrl glideUrl = new GlideUrl(ApiUtils.getUrlForAvatarWithName(currentUser.getBaseUrl(),
userId, R.dimen.avatar_size), new LazyHeaders.Builder() objectId, R.dimen.avatar_size), new LazyHeaders.Builder()
.setHeader("Accept", "image/*") .setHeader("Accept", "image/*")
.setHeader("User-Agent", ApiUtils.getUserAgent()) .setHeader("User-Agent", ApiUtils.getUserAgent())
.build()); .build());
@ -129,7 +139,7 @@ public class MentionAutocompleteItem extends AbstractFlexibleItem<UserItem.UserI
@Override @Override
public boolean filter(String constraint) { public boolean filter(String constraint) {
return userId != null && StringUtils.containsIgnoreCase(userId, constraint) || return objectId != null && StringUtils.containsIgnoreCase(objectId, constraint) ||
displayName != null && StringUtils.containsIgnoreCase(displayName, constraint); displayName != null && StringUtils.containsIgnoreCase(displayName, constraint);
} }

View File

@ -28,14 +28,12 @@ import android.text.Spannable;
import android.text.SpannableString; import android.text.SpannableString;
import android.text.style.ForegroundColorSpan; import android.text.style.ForegroundColorSpan;
import android.view.View; import android.view.View;
import android.widget.TextView;
import butterknife.BindView; import butterknife.BindView;
import butterknife.ButterKnife; import butterknife.ButterKnife;
import com.nextcloud.talk.R; import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.utils.DisplayUtils; import com.nextcloud.talk.utils.DisplayUtils;
import com.vanniktech.emoji.EmojiTextView; import com.vanniktech.emoji.EmojiTextView;
import eu.davidea.flexibleadapter.FlexibleAdapter; import eu.davidea.flexibleadapter.FlexibleAdapter;
import eu.davidea.flexibleadapter.items.AbstractFlexibleItem; import eu.davidea.flexibleadapter.items.AbstractFlexibleItem;
import eu.davidea.viewholders.FlexibleViewHolder; import eu.davidea.viewholders.FlexibleViewHolder;

View File

@ -145,18 +145,26 @@ public class MagicIncomingTextMessageViewHolder
Map<String, String> individualHashMap = message.getMessageParameters().get(key); Map<String, String> individualHashMap = message.getMessageParameters().get(key);
if (individualHashMap != null) { if (individualHashMap != null) {
if (individualHashMap.get("type").equals("user") || individualHashMap.get("type").equals("guest") || individualHashMap.get("type").equals("call")) { if (individualHashMap.get("type").equals("user") || individualHashMap.get("type").equals("guest") || individualHashMap.get("type").equals("call")) {
int color;
if (individualHashMap.get("id").equals(message.getActiveUserId())) { if (individualHashMap.get("id").equals(message.getActiveUserId())) {
color = NextcloudTalkApplication.getSharedApplication().getResources().getColor(R.color messageString =
.nc_incoming_text_mention_you); DisplayUtils.searchAndReplaceWithMentionSpan(messageText.getContext(),
messageString,
individualHashMap.get("id"),
individualHashMap.get("name"),
individualHashMap.get("type"),
userUtils.getUserById(message.getActiveUserId()),
R.xml.chip_simple_background);
} else { } else {
color = NextcloudTalkApplication.getSharedApplication().getResources().getColor(R.color messageString =
.nc_incoming_text_mention_others); DisplayUtils.searchAndReplaceWithMentionSpan(messageText.getContext(),
messageString,
individualHashMap.get("id"),
individualHashMap.get("name"),
individualHashMap.get("type"),
userUtils.getUserById(message.getActiveUserId()),
R.xml.chip_accent_background);
} }
messageString = DisplayUtils.searchAndColor(messageString,
"@" + individualHashMap.get("name"), color);
} else if (individualHashMap.get("type").equals("file")) { } else if (individualHashMap.get("type").equals("file")) {
itemView.setOnClickListener(v -> { itemView.setOnClickListener(v -> {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(individualHashMap.get("link"))); Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(individualHashMap.get("link")));

View File

@ -58,6 +58,9 @@ public class MagicOutcomingTextMessageViewHolder extends MessageHolders.Outcomin
@Inject @Inject
UserUtils userUtils; UserUtils userUtils;
@Inject
Context context;
private View itemView; private View itemView;
public MagicOutcomingTextMessageViewHolder(View itemView) { public MagicOutcomingTextMessageViewHolder(View itemView) {
@ -76,7 +79,6 @@ public class MagicOutcomingTextMessageViewHolder extends MessageHolders.Outcomin
Spannable messageString = new SpannableString(message.getText()); Spannable messageString = new SpannableString(message.getText());
Context context = NextcloudTalkApplication.getSharedApplication().getApplicationContext();
itemView.setSelected(false); itemView.setSelected(false);
messageTimeView.setTextColor(context.getResources().getColor(R.color.white60)); messageTimeView.setTextColor(context.getResources().getColor(R.color.white60));
@ -92,11 +94,23 @@ public class MagicOutcomingTextMessageViewHolder extends MessageHolders.Outcomin
if (individualHashMap.get("type").equals("user") || individualHashMap.get("type").equals("guest") || individualHashMap.get("type").equals("call")) { if (individualHashMap.get("type").equals("user") || individualHashMap.get("type").equals("guest") || individualHashMap.get("type").equals("call")) {
if (!individualHashMap.get("id").equals(message.getActiveUserId())) { if (!individualHashMap.get("id").equals(message.getActiveUserId())) {
messageString = messageString =
DisplayUtils.searchAndColor(messageString, DisplayUtils.searchAndReplaceWithMentionSpan(messageText.getContext(),
"@" + individualHashMap.get("name"), NextcloudTalkApplication messageString,
.getSharedApplication().getResources().getColor(R.color.nc_outcoming_text_default)); individualHashMap.get("id"),
individualHashMap.get("name"),
individualHashMap.get("type"),
userUtils.getUserById(message.getActiveUserId()),
R.xml.chip_simple_background);
} else {
messageString =
DisplayUtils.searchAndReplaceWithMentionSpan(messageText.getContext(),
messageString,
individualHashMap.get("id"),
individualHashMap.get("name"),
individualHashMap.get("type"),
userUtils.getUserById(message.getActiveUserId()),
R.xml.chip_outgoing_own_mention);
} }
} else if (individualHashMap.get("type").equals("file")) { } else if (individualHashMap.get("type").equals("file")) {
itemView.setOnClickListener(v -> { itemView.setOnClickListener(v -> {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(individualHashMap.get("link"))); Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse(individualHashMap.get("link")));

View File

@ -20,15 +20,31 @@
package com.nextcloud.talk.callbacks; package com.nextcloud.talk.callbacks;
import android.graphics.Typeface; import android.content.Context;
import android.text.Editable; import android.text.Editable;
import android.text.Spanned; import android.text.Spanned;
import android.text.style.DynamicDrawableSpan;
import com.nextcloud.talk.R;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.models.json.mention.Mention; import com.nextcloud.talk.models.json.mention.Mention;
import com.nextcloud.talk.utils.DisplayUtils;
import com.nextcloud.talk.utils.MagicCharPolicy; import com.nextcloud.talk.utils.MagicCharPolicy;
import com.nextcloud.talk.utils.text.Spans; import com.nextcloud.talk.utils.text.Spans;
import com.otaliastudios.autocomplete.AutocompleteCallback; import com.otaliastudios.autocomplete.AutocompleteCallback;
import com.vanniktech.emoji.EmojiEditText;
public class MentionAutocompleteCallback implements AutocompleteCallback<Mention> { public class MentionAutocompleteCallback implements AutocompleteCallback<Mention> {
private Context context;
private UserEntity conversationUser;
private EmojiEditText emojiEditText;
public MentionAutocompleteCallback(Context context, UserEntity conversationUser,
EmojiEditText emojiEditText) {
this.context = context;
this.conversationUser = conversationUser;
this.emojiEditText = emojiEditText;
}
@Override @Override
public boolean onPopupItemClicked(Editable editable, Mention item) { public boolean onPopupItemClicked(Editable editable, Mention item) {
int[] range = MagicCharPolicy.getQueryRange(editable); int[] range = MagicCharPolicy.getQueryRange(editable);
@ -37,8 +53,14 @@ public class MentionAutocompleteCallback implements AutocompleteCallback<Mention
int end = range[1]; int end = range[1];
String replacement = item.getLabel(); String replacement = item.getLabel();
editable.replace(start, end, replacement + " "); editable.replace(start, end, replacement + " ");
Spans.MentionSpan mentionSpan = new Spans.MentionSpan(Typeface.BOLD, item.getId(), item.getLabel()); Spans.MentionChipSpan mentionChipSpan =
editable.setSpan(mentionSpan, start, start + item.getLabel().length() , Spanned.SPAN_INCLUSIVE_EXCLUSIVE); new Spans.MentionChipSpan(DisplayUtils.getDrawableForMentionChipSpan(context,
item.getId(), item.getLabel(), conversationUser, item.getSource(),
R.xml.chip_accent_background, emojiEditText),
DynamicDrawableSpan.ALIGN_BASELINE,
item.getId(), item.getLabel());
editable.setSpan(mentionChipSpan, start, start + item.getLabel().length(),
Spanned.SPAN_INCLUSIVE_EXCLUSIVE);
return true; return true;
} }

View File

@ -95,7 +95,6 @@ import pub.devrel.easypermissions.AfterPermissionGranted;
import javax.inject.Inject; import javax.inject.Inject;
import java.io.IOException; import java.io.IOException;
import java.lang.reflect.Field;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit; import java.util.concurrent.TimeUnit;

View File

@ -118,6 +118,8 @@ public class ChatController extends BaseController implements MessagesListAdapte
UserUtils userUtils; UserUtils userUtils;
@Inject @Inject
AppPreferences appPreferences; AppPreferences appPreferences;
@Inject
Context context;
@BindView(R.id.messagesListView) @BindView(R.id.messagesListView)
MessagesList messagesListView; MessagesList messagesListView;
@BindView(R.id.messageInputView) @BindView(R.id.messageInputView)
@ -385,8 +387,9 @@ public class ChatController extends BaseController implements MessagesListAdapte
} }
Editable editable = messageInput.getEditableText(); Editable editable = messageInput.getEditableText();
Spans.MentionSpan[] mentionSpans = editable.getSpans(0, messageInput.length(), Spans.MentionSpan.class); Spans.MentionChipSpan[] mentionSpans = editable.getSpans(0, messageInput.length(),
Spans.MentionSpan mentionSpan; Spans.MentionChipSpan.class);
Spans.MentionChipSpan mentionSpan;
for (int i = 0; i < mentionSpans.length; i++) { for (int i = 0; i < mentionSpans.length; i++) {
mentionSpan = mentionSpans[i]; mentionSpan = mentionSpans[i];
if (start >= editable.getSpanStart(mentionSpan) && start < editable.getSpanEnd(mentionSpan)) { if (start >= editable.getSpanStart(mentionSpan) && start < editable.getSpanEnd(mentionSpan)) {
@ -482,7 +485,8 @@ public class ChatController extends BaseController implements MessagesListAdapte
float elevation = 6f; float elevation = 6f;
Drawable backgroundDrawable = new ColorDrawable(Color.WHITE); Drawable backgroundDrawable = new ColorDrawable(Color.WHITE);
AutocompletePresenter<Mention> presenter = new MentionAutocompletePresenter(getApplicationContext(), roomToken); AutocompletePresenter<Mention> presenter = new MentionAutocompletePresenter(getApplicationContext(), roomToken);
AutocompleteCallback<Mention> callback = new MentionAutocompleteCallback(); AutocompleteCallback<Mention> callback = new MentionAutocompleteCallback(getActivity(),
conversationUser, messageInput);
if (mentionAutocomplete == null && messageInput != null) { if (mentionAutocomplete == null && messageInput != null) {
mentionAutocomplete = Autocomplete.<Mention>on(messageInput) mentionAutocomplete = Autocomplete.<Mention>on(messageInput)
@ -728,8 +732,9 @@ public class ChatController extends BaseController implements MessagesListAdapte
private void submitMessage() { private void submitMessage() {
final Editable editable = messageInput.getEditableText(); final Editable editable = messageInput.getEditableText();
Spans.MentionSpan mentionSpans[] = editable.getSpans(0, editable.length(), Spans.MentionSpan.class); Spans.MentionChipSpan mentionSpans[] = editable.getSpans(0, editable.length(),
Spans.MentionSpan mentionSpan; Spans.MentionChipSpan.class);
Spans.MentionChipSpan mentionSpan;
for (int i = 0; i < mentionSpans.length; i++) { for (int i = 0; i < mentionSpans.length; i++) {
mentionSpan = mentionSpans[i]; mentionSpan = mentionSpans[i];
editable.replace(editable.getSpanStart(mentionSpan), editable.getSpanEnd(mentionSpan), "@" + mentionSpan.getId()); editable.replace(editable.getSpanStart(mentionSpan), editable.getSpanEnd(mentionSpan), "@" + mentionSpan.getId());

View File

@ -32,10 +32,10 @@ import android.view.ViewGroup;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import androidx.biometric.BiometricPrompt; import androidx.biometric.BiometricPrompt;
import androidx.fragment.app.FragmentActivity;
import autodagger.AutoInjector; import autodagger.AutoInjector;
import butterknife.OnClick; import butterknife.OnClick;
import com.nextcloud.talk.R; import com.nextcloud.talk.R;
import com.nextcloud.talk.activities.MainActivity;
import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.controllers.base.BaseController; import com.nextcloud.talk.controllers.base.BaseController;
import com.nextcloud.talk.utils.SecurityUtils; import com.nextcloud.talk.utils.SecurityUtils;
@ -88,7 +88,7 @@ public class LockedController extends BaseController {
Executor executor = Executors.newSingleThreadExecutor(); Executor executor = Executors.newSingleThreadExecutor();
final BiometricPrompt biometricPrompt = new BiometricPrompt((MainActivity) context, executor, final BiometricPrompt biometricPrompt = new BiometricPrompt((FragmentActivity) context, executor,
new BiometricPrompt.AuthenticationCallback() { new BiometricPrompt.AuthenticationCallback() {
@Override @Override
public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) { public void onAuthenticationSucceeded(@NonNull BiometricPrompt.AuthenticationResult result) {

View File

@ -192,8 +192,10 @@ public class CallMenuController extends BaseController implements FlexibleAdapte
if (conversation.canLeave(currentUser)) { if (conversation.canLeave(currentUser)) {
menuItems.add(new MenuItem(getResources().getString(R.string.nc_leave), 1, getResources().getDrawable(R.drawable menuItems.add(new MenuItem(getResources().getString(R.string.nc_leave), 1,
.ic_close_grey600_24dp))); DisplayUtils.getTintedDrawable(getResources(),
R.drawable.ic_exit_to_app_black_24dp, R.color.grey_600)
));
} }
} else if (menuType.equals(MenuType.SHARE)) { } else if (menuType.equals(MenuType.SHARE)) {
prepareIntent(); prepareIntent();

View File

@ -22,7 +22,6 @@ package com.nextcloud.talk.controllers.bottomsheet;
import android.content.ComponentName; import android.content.ComponentName;
import android.content.Intent; import android.content.Intent;
import android.content.res.ColorStateList;
import android.os.Bundle; import android.os.Bundle;
import android.text.Editable; import android.text.Editable;
import android.text.InputType; import android.text.InputType;
@ -33,7 +32,10 @@ import android.view.View;
import android.view.ViewGroup; import android.view.ViewGroup;
import android.view.inputmethod.EditorInfo; import android.view.inputmethod.EditorInfo;
import android.widget.Button; import android.widget.Button;
import androidx.annotation.NonNull;
import autodagger.AutoInjector;
import butterknife.BindView;
import butterknife.OnClick;
import com.bluelinelabs.conductor.RouterTransaction; import com.bluelinelabs.conductor.RouterTransaction;
import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler; import com.bluelinelabs.conductor.changehandler.HorizontalChangeHandler;
import com.google.android.material.textfield.TextInputEditText; import com.google.android.material.textfield.TextInputEditText;
@ -47,17 +49,11 @@ import com.nextcloud.talk.utils.ShareUtils;
import com.nextcloud.talk.utils.bundle.BundleKeys; import com.nextcloud.talk.utils.bundle.BundleKeys;
import com.nextcloud.talk.utils.database.user.UserUtils; import com.nextcloud.talk.utils.database.user.UserUtils;
import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder; import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder;
import org.greenrobot.eventbus.EventBus; import org.greenrobot.eventbus.EventBus;
import org.parceler.Parcels; import org.parceler.Parcels;
import javax.inject.Inject; import javax.inject.Inject;
import androidx.annotation.NonNull;
import autodagger.AutoInjector;
import butterknife.BindView;
import butterknife.OnClick;
@AutoInjector(NextcloudTalkApplication.class) @AutoInjector(NextcloudTalkApplication.class)
public class EntryMenuController extends BaseController { public class EntryMenuController extends BaseController {

View File

@ -20,6 +20,7 @@
package com.nextcloud.talk.models.json.mention; package com.nextcloud.talk.models.json.mention;
import com.bluelinelabs.logansquare.annotation.JsonField; import com.bluelinelabs.logansquare.annotation.JsonField;
import com.bluelinelabs.logansquare.annotation.JsonIgnore;
import com.bluelinelabs.logansquare.annotation.JsonObject; import com.bluelinelabs.logansquare.annotation.JsonObject;
import lombok.Data; import lombok.Data;
import org.parceler.Parcel; import org.parceler.Parcel;
@ -34,7 +35,7 @@ public class Mention {
@JsonField(name = "label") @JsonField(name = "label")
String label; String label;
// type of user (guests or users) // type of user (guests or users or calls)
@JsonField(name = "source") @JsonField(name = "source")
String source; String source;
} }

View File

@ -113,7 +113,8 @@ public class MentionAutocompletePresenter extends RecyclerViewPresenter<Mention>
List<AbstractFlexibleItem> internalAbstractFlexibleItemList = new ArrayList<>(); List<AbstractFlexibleItem> internalAbstractFlexibleItemList = new ArrayList<>();
for (Mention mention : mentionsList) { for (Mention mention : mentionsList) {
internalAbstractFlexibleItemList.add( internalAbstractFlexibleItemList.add(
new MentionAutocompleteItem(mention.getId(), mention.getLabel(), new MentionAutocompleteItem(mention.getId(),
mention.getLabel(), mention.getSource(),
currentUser)); currentUser));
} }
@ -143,9 +144,9 @@ public class MentionAutocompletePresenter extends RecyclerViewPresenter<Mention>
Mention mention = new Mention(); Mention mention = new Mention();
MentionAutocompleteItem mentionAutocompleteItem = (MentionAutocompleteItem) adapter.getItem(position); MentionAutocompleteItem mentionAutocompleteItem = (MentionAutocompleteItem) adapter.getItem(position);
if (mentionAutocompleteItem != null) { if (mentionAutocompleteItem != null) {
mention.setId(mentionAutocompleteItem.getUserId()); mention.setId(mentionAutocompleteItem.getObjectId());
mention.setLabel(mentionAutocompleteItem.getDisplayName()); mention.setLabel(mentionAutocompleteItem.getDisplayName());
mention.setSource("users"); mention.setSource(mentionAutocompleteItem.getSource());
dispatchClick(mention); dispatchClick(mention);
} }
return true; return true;

View File

@ -24,6 +24,7 @@ import android.annotation.SuppressLint;
import android.content.Context; import android.content.Context;
import android.content.Intent; import android.content.Intent;
import android.content.res.ColorStateList; import android.content.res.ColorStateList;
import android.content.res.Configuration;
import android.content.res.Resources; import android.content.res.Resources;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.Canvas; import android.graphics.Canvas;
@ -37,10 +38,7 @@ import android.net.Uri;
import android.os.Build; import android.os.Build;
import android.text.*; import android.text.*;
import android.text.method.LinkMovementMethod; import android.text.method.LinkMovementMethod;
import android.text.style.AbsoluteSizeSpan; import android.text.style.*;
import android.text.style.ClickableSpan;
import android.text.style.ForegroundColorSpan;
import android.text.style.StyleSpan;
import android.util.Log; import android.util.Log;
import android.util.TypedValue; import android.util.TypedValue;
import android.view.View; import android.view.View;
@ -50,15 +48,26 @@ import androidx.annotation.*;
import androidx.appcompat.widget.AppCompatDrawableManager; import androidx.appcompat.widget.AppCompatDrawableManager;
import androidx.core.content.ContextCompat; import androidx.core.content.ContextCompat;
import androidx.core.graphics.drawable.DrawableCompat; import androidx.core.graphics.drawable.DrawableCompat;
import com.facebook.common.executors.UiThreadImmediateExecutorService;
import com.facebook.common.references.CloseableReference;
import com.facebook.datasource.DataSource;
import com.facebook.drawee.backends.pipeline.Fresco;
import com.facebook.drawee.controller.ControllerListener; import com.facebook.drawee.controller.ControllerListener;
import com.facebook.drawee.view.SimpleDraweeView; import com.facebook.drawee.view.SimpleDraweeView;
import com.facebook.imagepipeline.common.RotationOptions; import com.facebook.imagepipeline.common.RotationOptions;
import com.facebook.imagepipeline.core.ImagePipeline;
import com.facebook.imagepipeline.datasource.BaseBitmapDataSubscriber;
import com.facebook.imagepipeline.image.CloseableImage;
import com.facebook.imagepipeline.image.ImageInfo; import com.facebook.imagepipeline.image.ImageInfo;
import com.facebook.imagepipeline.postprocessors.RoundAsCirclePostprocessor; import com.facebook.imagepipeline.postprocessors.RoundAsCirclePostprocessor;
import com.facebook.imagepipeline.request.ImageRequest; import com.facebook.imagepipeline.request.ImageRequest;
import com.facebook.imagepipeline.request.ImageRequestBuilder; import com.facebook.imagepipeline.request.ImageRequestBuilder;
import com.google.android.material.chip.ChipDrawable;
import com.nextcloud.talk.R; import com.nextcloud.talk.R;
import com.nextcloud.talk.application.NextcloudTalkApplication; import com.nextcloud.talk.application.NextcloudTalkApplication;
import com.nextcloud.talk.models.database.UserEntity;
import com.nextcloud.talk.utils.text.Spans;
import com.vanniktech.emoji.EmojiEditText;
import com.vanniktech.emoji.EmojiTextView; import com.vanniktech.emoji.EmojiTextView;
import java.lang.reflect.Constructor; import java.lang.reflect.Constructor;
@ -209,6 +218,96 @@ public class DisplayUtils {
} }
public static Drawable getDrawableForMentionChipSpan(Context context, String id, String label,
UserEntity conversationUser, String type,
@XmlRes int chipResource,
@Nullable EmojiEditText emojiEditText) {
ChipDrawable chip = ChipDrawable.createFromResource(context, chipResource);
chip.setText(label);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) {
Configuration config = context.getResources().getConfiguration();
chip.setLayoutDirection(config.getLayoutDirection());
}
int drawable;
boolean isCall = "call".equals(type) || "calls".equals(type);
if (!isCall) {
if (chipResource == R.xml.chip_accent_background) {
drawable = R.drawable.white_circle;
} else {
drawable = R.drawable.accent_circle;
}
chip.setChipIcon(context.getDrawable(drawable));
} else {
chip.setChipIcon(getRoundedDrawable(context.getDrawable(R.drawable.ic_people_group_white_24px)));
}
chip.setBounds(0, 0, chip.getIntrinsicWidth(), chip.getIntrinsicHeight());
if (!isCall) {
ImageRequest imageRequest =
getImageRequestForUrl(ApiUtils.getUrlForAvatarWithName(conversationUser.getBaseUrl(), id, R.dimen.avatar_size_big));
ImagePipeline imagePipeline = Fresco.getImagePipeline();
DataSource<CloseableReference<CloseableImage>> dataSource = imagePipeline.fetchDecodedImage(imageRequest, context);
dataSource.subscribe(
new BaseBitmapDataSubscriber() {
@Override
protected void onNewResultImpl(Bitmap bitmap) {
if (bitmap != null) {
chip.setChipIcon(getRoundedDrawable(new BitmapDrawable(bitmap)));
// A hack to refresh the chip icon
if (emojiEditText != null) {
emojiEditText.post(() -> emojiEditText.setTextKeepState(emojiEditText.getText(), TextView.BufferType.SPANNABLE));
}
}
}
@Override
protected void onFailureImpl(DataSource<CloseableReference<CloseableImage>> dataSource) {
}
},
UiThreadImmediateExecutorService.getInstance());
}
return chip;
}
public static Spannable searchAndReplaceWithMentionSpan(Context context, Spannable text,
String id, String label, String type,
UserEntity conversationUser,
@XmlRes int chipXmlRes) {
Spannable spannableString = new SpannableString(text);
String stringText = text.toString();
Matcher m = Pattern.compile("@" + label,
Pattern.CASE_INSENSITIVE | Pattern.LITERAL | Pattern.MULTILINE)
.matcher(spannableString);
int lastStartIndex = -1;
Spans.MentionChipSpan mentionChipSpan;
while (m.find()) {
int start = stringText.indexOf(m.group(), lastStartIndex);
int end = start + m.group().length();
lastStartIndex = end;
mentionChipSpan = new Spans.MentionChipSpan(DisplayUtils.getDrawableForMentionChipSpan(context,
id, label, conversationUser, type, chipXmlRes, null),
DynamicDrawableSpan.ALIGN_BASELINE, id,
label);
spannableString.setSpan(mentionChipSpan, start, end, Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
}
return spannableString;
}
public static Spannable searchAndColor(Spannable text, String searchText, @ColorInt int color) { public static Spannable searchAndColor(Spannable text, String searchText, @ColorInt int color) {
Spannable spannableString = new SpannableString(text); Spannable spannableString = new SpannableString(text);

View File

@ -105,6 +105,13 @@ public class UserUtils {
} }
public UserEntity getUserById(String id) {
Result findUserQueryResult = dataStore.select(User.class).where(UserEntity.USER_ID.eq(id))
.limit(1).get();
return (UserEntity) findUserQueryResult.firstOrNull();
}
public UserEntity getUserWithId(long id) { public UserEntity getUserWithId(long id) {
Result findUserQueryResult = dataStore.select(User.class).where(UserEntity.ID.eq(id)) Result findUserQueryResult = dataStore.select(User.class).where(UserEntity.ID.eq(id))
.limit(1).get(); .limit(1).get();

View File

@ -20,20 +20,22 @@
package com.nextcloud.talk.utils.text; package com.nextcloud.talk.utils.text;
import android.text.style.StyleSpan; import android.graphics.drawable.Drawable;
import androidx.annotation.NonNull;
import lombok.Data; import lombok.Data;
public class Spans { public class Spans {
@Data @Data
public static class MentionSpan extends StyleSpan { public static class MentionChipSpan extends TextAlignedImageSpan {
String id; String id;
String label; String label;
public MentionSpan(int style, String id, String label) { public MentionChipSpan(@NonNull Drawable drawable, int verticalAlignment, String id, String label) {
super(style); super(drawable, verticalAlignment);
this.id = id; this.id = id;
this.label = label; this.label = label;
} }
} }
} }

View File

@ -0,0 +1,71 @@
/*
* Nextcloud Talk application
*
* @author Mario Danic
* Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
*
* This program is free software: you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation, either version 3 of the License, or
* at your option) any later version.
*
* This program 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/>.
*
* Taken and adapter from
*/
package com.nextcloud.talk.utils.text;
import android.graphics.Canvas;
import android.graphics.Paint;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.text.style.ImageSpan;
import androidx.annotation.NonNull;
public class TextAlignedImageSpan extends ImageSpan {
public TextAlignedImageSpan(@NonNull Drawable drawable, int verticalAlignment) {
super(drawable, verticalAlignment);
}
public int getSize(@NonNull Paint paint, CharSequence text, int start, int end,
Paint.FontMetricsInt fontMetricsInt) {
Drawable drawable = getDrawable();
Rect drawableBounds = drawable.getBounds();
if (fontMetricsInt != null) {
Paint.FontMetricsInt fmPaint = paint.getFontMetricsInt();
int fontHeight = fmPaint.bottom - fmPaint.top;
int drHeight = drawableBounds.bottom - drawableBounds.top;
int top = drHeight / 2 - fontHeight / 4;
int bottom = drHeight / 2 + fontHeight / 4;
fontMetricsInt.ascent = -bottom;
fontMetricsInt.top = -bottom;
fontMetricsInt.bottom = top;
fontMetricsInt.descent = top;
}
return drawableBounds.right;
}
@Override
public void draw(@NonNull Canvas canvas, CharSequence text, int start, int end,
float x, int top, int y, int bottom, @NonNull Paint paint) {
Drawable drawable = getDrawable();
canvas.save();
int transY;
transY = ((bottom - top) - drawable.getBounds().bottom) / 2 + top;
canvas.translate(x, transY);
drawable.draw(canvas);
canvas.restore();
}
}

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Talk application
~
~ @author Mario Danic
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ at your option) any later version.
~
~ This program 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/>.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid
android:color="@color/colorAccent"/>
</shape>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Talk application
~
~ @author Mario Danic
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ at your option) any later version.
~
~ This program 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/>.
-->
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<solid
android:color="@color/white"/>
</shape>

View File

@ -52,6 +52,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textColor="@color/colorPrimary" android:textColor="@color/colorPrimary"
android:layout_marginBottom="4dp"
android:textSize="12sp" /> android:textSize="12sp" />
<com.vanniktech.emoji.EmojiTextView <com.vanniktech.emoji.EmojiTextView
@ -59,11 +60,11 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:textIsSelectable="true" android:textIsSelectable="true"
android:lineSpacingMultiplier="1.2"
app:layout_alignSelf="flex_start" app:layout_alignSelf="flex_start"
app:layout_flexGrow="1" app:layout_flexGrow="1"
app:layout_wrapBefore="true" /> app:layout_wrapBefore="true" />
<TextView <TextView
android:id="@id/messageTime" android:id="@id/messageTime"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View File

@ -44,6 +44,7 @@
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignWithParentIfMissing="true" android:layout_alignWithParentIfMissing="true"
android:textColorHighlight="@color/nc_grey" android:textColorHighlight="@color/nc_grey"
android:lineSpacingMultiplier="1.2"
android:textIsSelectable="true" /> android:textIsSelectable="true" />
<TextView <TextView

View File

@ -40,6 +40,7 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_centerVertical="true" android:layout_centerVertical="true"
android:lineSpacingMultiplier="1.2"
android:imeOptions="actionDone" android:imeOptions="actionDone"
android:layout_toStartOf="@id/sendButtonSpace" android:layout_toStartOf="@id/sendButtonSpace"
android:inputType="textAutoCorrect|textMultiLine|textCapSentences"/> android:inputType="textAutoCorrect|textMultiLine|textCapSentences"/>

View File

@ -16,4 +16,12 @@
<item name="android:textSize">12sp</item> <item name="android:textSize">12sp</item>
</style> </style>
<style name="ChipTextAppearance" parent="TextAppearance.MaterialComponents.Chip">
<item name="android:textColor">@android:color/white</item>
</style>
<style name="ChipAccentTextAppearance" parent="TextAppearance.MaterialComponents.Chip">
<item name="android:textColor">@color/colorAccent</item>
</style>
</resources> </resources>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Talk application
~
~ @author Mario Danic
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ at your option) any later version.
~
~ This program 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/>.
-->
<chip xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:textAppearance="@style/ChipTextAppearance"
app:chipBackgroundColor="@color/colorAccent"
app:closeIconEnabled="false"/>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Talk application
~
~ @author Mario Danic
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ at your option) any later version.
~
~ This program 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/>.
-->
<chip xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:textAppearance="@style/ChipAccentTextAppearance"
app:chipBackgroundColor="@color/white_four"
app:closeIconEnabled="false" />

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ Nextcloud Talk application
~
~ @author Mario Danic
~ Copyright (C) 2017-2018 Mario Danic <mario@lovelyhq.com>
~
~ This program is free software: you can redistribute it and/or modify
~ it under the terms of the GNU General Public License as published by
~ the Free Software Foundation, either version 3 of the License, or
~ at your option) any later version.
~
~ This program 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/>.
-->
<chip xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:textAppearance="@style/ChipAccentTextAppearance"
app:chipBackgroundColor="@color/white"
app:closeIconEnabled="false" />