소스 검색

Merge branch '2.2-work'

Mario Danic 6 년 전
부모
커밋
09ed036513
37개의 변경된 파일920개의 추가작업 그리고 225개의 파일을 삭제
  1. 3 1
      app/build.gradle
  2. 5 0
      app/src/generic/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java
  3. 35 0
      app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.java
  4. 25 1
      app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.java
  5. 9 0
      app/src/main/java/com/nextcloud/talk/api/NcApi.java
  6. 9 2
      app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.java
  7. 1 7
      app/src/main/java/com/nextcloud/talk/controllers/CallController.java
  8. 36 2
      app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java
  9. 66 8
      app/src/main/java/com/nextcloud/talk/controllers/ChatController.java
  10. 19 52
      app/src/main/java/com/nextcloud/talk/controllers/ContactsController.java
  11. 2 7
      app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/CallMenuController.java
  12. 4 0
      app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/EntryMenuController.java
  13. 1 1
      app/src/main/java/com/nextcloud/talk/jobs/NotificationJob.java
  14. 5 1
      app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.java
  15. 2 0
      app/src/main/java/com/nextcloud/talk/models/json/rooms/Room.java
  16. 4 0
      app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java
  17. 89 0
      app/src/main/java/com/nextcloud/talk/utils/emoticons/EmoticonSpan.java
  18. 255 0
      app/src/main/java/com/nextcloud/talk/utils/emoticons/EmoticonUtils.java
  19. 0 25
      app/src/main/res/drawable/ic_hearing_white_24dp.xml
  20. 1 2
      app/src/main/res/drawable/ic_volume_up_white_24dp.xml
  21. 28 0
      app/src/main/res/drawable/incoming_gradient.xml
  22. 33 0
      app/src/main/res/drawable/shape_grouped_incoming_message.xml
  23. 33 0
      app/src/main/res/drawable/shape_grouped_outcoming_message.xml
  24. 33 0
      app/src/main/res/drawable/shape_incoming_message.xml
  25. 33 0
      app/src/main/res/drawable/shape_outcoming_message.xml
  26. 0 59
      app/src/main/res/layout/bottom_buttons.xml
  27. 4 1
      app/src/main/res/layout/controller_call.xml
  28. 29 20
      app/src/main/res/layout/controller_call_notification.xml
  29. 50 11
      app/src/main/res/layout/controller_chat.xml
  30. 0 4
      app/src/main/res/layout/controller_generic_rv.xml
  31. 9 10
      app/src/main/res/layout/item_custom_incoming_text_message.xml
  32. 8 8
      app/src/main/res/layout/item_custom_outcoming_text_message.xml
  33. 47 0
      app/src/main/res/layout/library_fast_scroller_layout.xml
  34. 30 0
      app/src/main/res/menu/menu_contacts.xml
  35. 7 0
      app/src/main/res/menu/menu_conversation_plus_filter.xml
  36. 1 1
      app/src/main/res/values/dimens.xml
  37. 4 2
      app/src/main/res/values/strings.xml

+ 3 - 1
app/build.gradle

@@ -19,6 +19,8 @@ android {
         versionName "2.1.0"
 
         flavorDimensions "default"
+        renderscriptTargetApi 19
+        renderscriptSupportModeEnabled true
 
         productFlavors {
             // used for f-droid
@@ -152,7 +154,7 @@ dependencies {
 
     implementation 'com.github.wooplr:Spotlight:1.2.3'
 
-    implementation 'com.github.stfalcon:chatkit:0.2.2'
+    implementation 'com.github.stfalcon:chatkit:0.3.0'
     implementation 'com.otaliastudios:autocomplete:1.1.0'
 
     implementation 'com.github.Kennyc1012:BottomSheet:2.4.0'

+ 5 - 0
app/src/generic/java/com/nextcloud/talk/utils/ClosedInterfaceImpl.java

@@ -28,4 +28,9 @@ public class ClosedInterfaceImpl implements ClosedInterface {
     public void providerInstallerInstallIfNeededAsync() {
         // does absolutely nothing :)
     }
+
+    @Override
+    public boolean isGooglePlayServicesAvailable() {
+        return false;
+    }
 }

+ 35 - 0
app/src/main/java/com/nextcloud/talk/adapters/messages/MagicIncomingTextMessageViewHolder.java

@@ -20,12 +20,16 @@
 
 package com.nextcloud.talk.adapters.messages;
 
+import android.content.Context;
 import android.text.Spannable;
 import android.text.SpannableString;
 import android.text.TextUtils;
+import android.text.style.RelativeSizeSpan;
 import android.view.View;
 import android.widget.TextView;
 
+import com.amulyakhare.textdrawable.TextDrawable;
+import com.google.android.flexbox.FlexboxLayout;
 import com.kevalpatel2106.emoticongifkeyboard.widget.EmoticonTextView;
 import com.nextcloud.talk.R;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
@@ -33,7 +37,9 @@ import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.json.chat.ChatMessage;
 import com.nextcloud.talk.utils.DisplayUtils;
 import com.nextcloud.talk.utils.database.user.UserUtils;
+import com.nextcloud.talk.utils.emoticons.EmoticonUtils;
 import com.stfalcon.chatkit.messages.MessageHolders;
+import com.stfalcon.chatkit.utils.ShapeImageView;
 
 import java.util.HashMap;
 
@@ -53,16 +59,24 @@ public class MagicIncomingTextMessageViewHolder
     @BindView(R.id.messageText)
     EmoticonTextView messageText;
 
+    @BindView(R.id.messageUserAvatar)
+    ShapeImageView messageUserAvatarView;
+
+    @BindView(R.id.messageTime)
+    TextView messageTimeView;
+
     @Inject
     UserUtils userUtils;
 
     private UserEntity currentUser;
+    private View itemView;
 
     public MagicIncomingTextMessageViewHolder(View itemView) {
         super(itemView);
         ButterKnife.bind(this, itemView);
         NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
 
+        this.itemView = itemView;
         currentUser = userUtils.getCurrentUser();
     }
 
@@ -77,8 +91,23 @@ public class MagicIncomingTextMessageViewHolder
             messageAuthor.setText(R.string.nc_nick_guest);
         }
 
+        if (message.getActorType().equals("guests")) {
+            TextDrawable drawable = TextDrawable.builder().beginConfig().bold()
+                    .endConfig().buildRound(String.valueOf(messageAuthor.getText().charAt(0)), NextcloudTalkApplication
+                            .getSharedApplication().getResources().getColor(R.color.nc_grey));
+            messageUserAvatarView.setVisibility(View.VISIBLE);
+            messageUserAvatarView.setImageDrawable(drawable);
+        }
+
         HashMap<String, HashMap<String, String>> messageParameters = message.getMessageParameters();
 
+        Context context = NextcloudTalkApplication.getSharedApplication().getApplicationContext();
+        itemView.setSelected(false);
+        messageTimeView.setTextColor(context.getResources().getColor(R.color.warm_grey_four));
+
+        FlexboxLayout.LayoutParams layoutParams = (FlexboxLayout.LayoutParams) messageTimeView.getLayoutParams();
+        layoutParams.setWrapBefore(false);
+
         Spannable messageString = new SpannableString(message.getText());
 
         if (messageParameters != null && message.getMessageParameters().size() > 0) {
@@ -100,8 +129,14 @@ public class MagicIncomingTextMessageViewHolder
                 }
             }
 
+        } else if (EmoticonUtils.isMessageWithSingleEmoticonOnly(context, message.getText())) {
+            messageString.setSpan(new RelativeSizeSpan(2.5f), 0, messageString.length(),
+                    Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+            layoutParams.setWrapBefore(true);
+            itemView.setSelected(true);
         }
 
+        messageTimeView.setLayoutParams(layoutParams);
         messageText.setText(messageString);
     }
 }

+ 25 - 1
app/src/main/java/com/nextcloud/talk/adapters/messages/MagicOutcomingTextMessageViewHolder.java

@@ -20,10 +20,14 @@
 
 package com.nextcloud.talk.adapters.messages;
 
+import android.content.Context;
 import android.text.Spannable;
 import android.text.SpannableString;
+import android.text.style.RelativeSizeSpan;
 import android.view.View;
+import android.widget.TextView;
 
+import com.google.android.flexbox.FlexboxLayout;
 import com.kevalpatel2106.emoticongifkeyboard.widget.EmoticonTextView;
 import com.nextcloud.talk.R;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
@@ -31,6 +35,7 @@ import com.nextcloud.talk.models.database.UserEntity;
 import com.nextcloud.talk.models.json.chat.ChatMessage;
 import com.nextcloud.talk.utils.DisplayUtils;
 import com.nextcloud.talk.utils.database.user.UserUtils;
+import com.nextcloud.talk.utils.emoticons.EmoticonUtils;
 import com.stfalcon.chatkit.messages.MessageHolders;
 
 import java.util.HashMap;
@@ -46,16 +51,22 @@ public class MagicOutcomingTextMessageViewHolder extends MessageHolders.Outcomin
     @BindView(R.id.messageText)
     EmoticonTextView messageText;
 
+    @BindView(R.id.messageTime)
+    TextView messageTimeView;
+
     @Inject
     UserUtils userUtils;
 
     private UserEntity currentUser;
 
+    private View itemView;
+
     public MagicOutcomingTextMessageViewHolder(View itemView) {
         super(itemView);
         ButterKnife.bind(this, itemView);
         NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
 
+        this.itemView = itemView;
         currentUser = userUtils.getCurrentUser();
     }
 
@@ -67,6 +78,13 @@ public class MagicOutcomingTextMessageViewHolder extends MessageHolders.Outcomin
 
         Spannable messageString = new SpannableString(message.getText());
 
+        Context context = NextcloudTalkApplication.getSharedApplication().getApplicationContext();
+        itemView.setSelected(false);
+        messageTimeView.setTextColor(context.getResources().getColor(R.color.white60));
+
+        FlexboxLayout.LayoutParams layoutParams = (FlexboxLayout.LayoutParams) messageTimeView.getLayoutParams();
+        layoutParams.setWrapBefore(false);
+
         if (messageParameters != null && message.getMessageParameters().size() > 0) {
             for (String key : message.getMessageParameters().keySet()) {
                 HashMap<String, String> individualHashMap = message.getMessageParameters().get(key);
@@ -80,9 +98,15 @@ public class MagicOutcomingTextMessageViewHolder extends MessageHolders.Outcomin
                 }
             }
 
+        } else if (EmoticonUtils.isMessageWithSingleEmoticonOnly(context, message.getText())) {
+            messageString.setSpan(new RelativeSizeSpan(2.5f), 0, messageString.length(),
+                    Spannable.SPAN_INCLUSIVE_EXCLUSIVE);
+            layoutParams.setWrapBefore(true);
+            messageTimeView.setTextColor(context.getResources().getColor(R.color.warm_grey_four));
+            itemView.setSelected(true);
         }
 
+        messageTimeView.setLayoutParams(layoutParams);
         messageText.setText(messageString);
     }
-
 }

+ 9 - 0
app/src/main/java/com/nextcloud/talk/api/NcApi.java

@@ -289,4 +289,13 @@ public interface NcApi {
                                                                  @Url String url, @Query("search") String query,
                                                                  @Nullable @Query("limit") Integer limit);
 
+    // Url is: /api/{apiVersion}/room/{token}/favorite
+    @POST
+    Observable<GenericOverall> addConversationToFavorites(@Header("Authorization") String authorization,
+                                                   @Url String url);
+
+    // Url is: /api/{apiVersion}/room/{token}/favorite
+    @DELETE
+    Observable<GenericOverall> removeConversationFromFavorites(@Header("Authorization") String authorization,
+                                                          @Url String url);
 }

+ 9 - 2
app/src/main/java/com/nextcloud/talk/controllers/AccountVerificationController.java

@@ -46,10 +46,11 @@ import com.nextcloud.talk.models.json.generic.Status;
 import com.nextcloud.talk.models.json.rooms.RoomsOverall;
 import com.nextcloud.talk.models.json.userprofile.UserProfileOverall;
 import com.nextcloud.talk.utils.ApiUtils;
-import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder;
+import com.nextcloud.talk.utils.ClosedInterfaceImpl;
 import com.nextcloud.talk.utils.bundle.BundleKeys;
 import com.nextcloud.talk.utils.database.user.UserUtils;
 import com.nextcloud.talk.utils.preferences.AppPreferences;
+import com.nextcloud.talk.utils.singletons.ApplicationWideMessageHolder;
 
 import org.greenrobot.eventbus.EventBus;
 import org.greenrobot.eventbus.Subscribe;
@@ -261,7 +262,13 @@ public class AccountVerificationController extends BaseController {
                     public void onNext(UserEntity userEntity) {
                         internalAccountId = userEntity.getId();
 
-                        registerForPush();
+                        if (new ClosedInterfaceImpl().isGooglePlayServicesAvailable()) {
+                            registerForPush();
+                        } else {
+                            getActivity().runOnUiThread(() -> progressText.setText(progressText.getText().toString() + "\n" +
+                                    getResources().getString(R.string.nc_push_disabled)));
+                            fetchAndStoreCapabilities();
+                        }
                     }
 
                     @Override

+ 1 - 7
app/src/main/java/com/nextcloud/talk/controllers/CallController.java

@@ -598,13 +598,7 @@ public class CallController extends BaseController {
     public void onEnableSpeakerphoneClick() {
         if (audioManager != null) {
             audioManager.toggleUseSpeakerphone();
-            if (audioManager.isSpeakerphoneAutoOn()) {
-                callControlEnableSpeaker.getFrontImageView().setImageDrawable(getResources().getDrawable(R.drawable
-                        .ic_speaker_white_24dp));
-            } else {
-                callControlEnableSpeaker.getFrontImageView().setImageDrawable(getResources().getDrawable(R.drawable
-                        .ic_hearing_white_24dp));
-            }
+            callControlEnableSpeaker.flipSilently(!callControlEnableSpeaker.isFlipped());
         }
     }
 

+ 36 - 2
app/src/main/java/com/nextcloud/talk/controllers/CallNotificationController.java

@@ -21,10 +21,17 @@
 package com.nextcloud.talk.controllers;
 
 import android.annotation.SuppressLint;
+import android.graphics.Bitmap;
+import android.graphics.drawable.BitmapDrawable;
 import android.media.MediaPlayer;
 import android.net.Uri;
 import android.os.Bundle;
+import android.renderscript.Allocation;
+import android.renderscript.Element;
+import android.renderscript.RenderScript;
+import android.renderscript.ScriptIntrinsicBlur;
 import android.support.annotation.NonNull;
+import android.support.constraint.ConstraintLayout;
 import android.text.TextUtils;
 import android.util.Log;
 import android.view.LayoutInflater;
@@ -40,7 +47,10 @@ import com.bumptech.glide.load.engine.DiskCacheStrategy;
 import com.bumptech.glide.load.model.GlideUrl;
 import com.bumptech.glide.load.model.LazyHeaders;
 import com.bumptech.glide.load.resource.bitmap.CircleCrop;
+import com.bumptech.glide.load.resource.bitmap.TransformationUtils;
 import com.bumptech.glide.request.RequestOptions;
+import com.bumptech.glide.request.target.SimpleTarget;
+import com.bumptech.glide.request.transition.Transition;
 import com.nextcloud.talk.R;
 import com.nextcloud.talk.api.NcApi;
 import com.nextcloud.talk.application.NextcloudTalkApplication;
@@ -96,6 +106,9 @@ public class CallNotificationController extends BaseController {
     @BindView(R.id.callAnswerCameraView)
     MagicFlipView callAnswerCameraView;
 
+    @BindView(R.id.constraintLayout)
+    ConstraintLayout constraintLayout;
+
     private List<Disposable> disposablesList = new ArrayList<>();
     private Bundle originalBundle;
     private String roomId;
@@ -104,6 +117,7 @@ public class CallNotificationController extends BaseController {
     private Room currentRoom;
     private MediaPlayer mediaPlayer;
     private boolean leavingScreen = false;
+    private RenderScript renderScript;
 
     public CallNotificationController(Bundle args) {
         super(args);
@@ -249,6 +263,7 @@ public class CallNotificationController extends BaseController {
     protected void onViewBound(@NonNull View view) {
         super.onViewBound(view);
 
+        renderScript = RenderScript.create(getActivity());
         handleFromNotification();
 
         String callRingtonePreferenceString = appPreferences.getCallRingtoneUri();
@@ -296,8 +311,27 @@ public class CallNotificationController extends BaseController {
                         .load(glideUrl)
                         .centerInside()
                         .override(avatarSize, avatarSize)
-                        .apply(RequestOptions.bitmapTransform(new CircleCrop()))
-                        .into(avatarImageView);
+                        .into(new SimpleTarget<Bitmap>() {
+                            @Override
+                            public void onResourceReady(Bitmap resource, Transition<? super Bitmap> transition) {
+                                if (getActivity() != null) {
+                                    avatarImageView.setImageBitmap(TransformationUtils.circleCrop(GlideApp.get
+                                            (getActivity()).getBitmapPool(), resource, avatarSize, avatarSize));
+                                }
+
+                                final Allocation input = Allocation.createFromBitmap(renderScript, resource);
+                                final Allocation output = Allocation.createTyped(renderScript, input.getType());
+                                final ScriptIntrinsicBlur script = ScriptIntrinsicBlur.create(renderScript, Element
+                                        .U8_4(renderScript));
+                                script.setRadius(15f);
+                                script.setInput(input);
+                                script.forEach(output);
+                                output.copyTo(resource);
+
+                                constraintLayout.setBackground(new BitmapDrawable(resource));
+                            }
+                        });
+
 
                 break;
             case ROOM_GROUP_CALL:

+ 66 - 8
app/src/main/java/com/nextcloud/talk/controllers/ChatController.java

@@ -46,6 +46,9 @@ import android.view.ViewGroup;
 import android.view.inputmethod.EditorInfo;
 import android.widget.AbsListView;
 import android.widget.ImageView;
+import android.widget.ProgressBar;
+import android.widget.RelativeLayout;
+import android.widget.TextView;
 
 import com.amulyakhare.textdrawable.TextDrawable;
 import com.bumptech.glide.load.DataSource;
@@ -86,6 +89,7 @@ import com.otaliastudios.autocomplete.AutocompletePresenter;
 import com.otaliastudios.autocomplete.CharPolicy;
 import com.stfalcon.chatkit.commons.ImageLoader;
 import com.stfalcon.chatkit.commons.models.IMessage;
+import com.stfalcon.chatkit.messages.MessageHolders;
 import com.stfalcon.chatkit.messages.MessageInput;
 import com.stfalcon.chatkit.messages.MessagesList;
 import com.stfalcon.chatkit.messages.MessagesListAdapter;
@@ -107,6 +111,7 @@ import javax.inject.Inject;
 
 import autodagger.AutoInjector;
 import butterknife.BindView;
+import butterknife.OnClick;
 import io.reactivex.Observer;
 import io.reactivex.android.schedulers.AndroidSchedulers;
 import io.reactivex.disposables.Disposable;
@@ -133,6 +138,12 @@ public class ChatController extends BaseController implements MessagesListAdapte
     MessageInput messageInputView;
     @BindView(R.id.popupBubbleView)
     PopupBubble popupBubble;
+    @BindView(R.id.emptyLayout)
+    RelativeLayout emptyLayout;
+    @BindView(R.id.sendHiTextView)
+    TextView sendHiTextView;
+    @BindView(R.id.progressBar)
+    ProgressBar loadingProgressBar;
     private List<Disposable> disposableList = new ArrayList<>();
     private String conversationName;
     private String roomToken;
@@ -158,6 +169,9 @@ public class ChatController extends BaseController implements MessagesListAdapte
     private String roomId;
     private boolean voiceOnly;
 
+    private boolean isFirstMessagesProcessing = true;
+    private boolean isHelloClicked;
+
     public ChatController(Bundle args) {
         super(args);
         setHasOptionsMenu(true);
@@ -286,9 +300,13 @@ public class ChatController extends BaseController implements MessagesListAdapte
         getActionBar().show();
         boolean adapterWasNull = false;
 
+        sendHiTextView.setText(String.format(getResources().getString(R.string.nc_chat_empty), getResources()
+                .getString(R.string.nc_hello)));
 
         if (adapter == null) {
 
+            loadingProgressBar.setVisibility(View.VISIBLE);
+
             try {
                 cache.evictAll();
             } catch (IOException e) {
@@ -297,13 +315,11 @@ public class ChatController extends BaseController implements MessagesListAdapte
 
             adapterWasNull = true;
 
-            MessagesListAdapter.HoldersConfig holdersConfig = new MessagesListAdapter.HoldersConfig();
-            holdersConfig.setIncoming(MagicIncomingTextMessageViewHolder.class,
-                    R.layout.item_custom_incoming_text_message);
-            holdersConfig.setOutcoming(MagicOutcomingTextMessageViewHolder.class,
-                    R.layout.item_custom_outcoming_text_message);
+            MessageHolders messageHolders = new MessageHolders();
+            messageHolders.setIncomingTextConfig(MagicIncomingTextMessageViewHolder.class, R.layout.item_custom_incoming_text_message);
+            messageHolders.setOutcomingTextConfig(MagicOutcomingTextMessageViewHolder.class, R.layout.item_custom_outcoming_text_message);
 
-            adapter = new MessagesListAdapter<>(conversationUser.getUserId(), holdersConfig, new ImageLoader() {
+            adapter = new MessagesListAdapter<>(conversationUser.getUserId(), messageHolders, new ImageLoader() {
                 @Override
                 public void loadImage(ImageView imageView, String url) {
                     GlideApp.with(NextcloudTalkApplication.getSharedApplication().getApplicationContext())
@@ -317,7 +333,6 @@ public class ChatController extends BaseController implements MessagesListAdapte
                                 @Override
                                 public boolean onLoadFailed(@Nullable GlideException e, Object model, Target<Bitmap> target, boolean isFirstResource) {
                                     TextDrawable drawable = TextDrawable.builder().beginConfig().bold()
-                                            .width(imageView.getMeasuredWidth()).height(imageView.getMeasuredHeight())
                                             .endConfig().buildRound("?", getResources().getColor(R.color.nc_grey));
                                     imageView.setImageDrawable(drawable);
                                     return true;
@@ -331,6 +346,12 @@ public class ChatController extends BaseController implements MessagesListAdapte
                             .into(imageView);
                 }
             });
+        } else {
+            if (adapter.getItemCount() == 0) {
+                emptyLayout.setVisibility(View.VISIBLE);
+            } else {
+                messagesListView.setVisibility(View.VISIBLE);
+            }
         }
 
 
@@ -480,6 +501,14 @@ public class ChatController extends BaseController implements MessagesListAdapte
 
     }
 
+    @OnClick(R.id.emptyLayout)
+    public void sendHello() {
+        if (!isHelloClicked) {
+            isHelloClicked = true;
+            sendMessage(getResources().getString(R.string.nc_hello) + " 👋", 1);
+        }
+    }
+
     private void joinRoomWithPassword() {
 
         if (currentCall == null) {
@@ -697,7 +726,26 @@ public class ChatController extends BaseController implements MessagesListAdapte
             ChatOverall chatOverall = (ChatOverall) response.body();
             List<ChatMessage> chatMessageList = chatOverall.getOcs().getData();
 
+            if (isFirstMessagesProcessing) {
+                isFirstMessagesProcessing = false;
+                loadingProgressBar.setVisibility(View.GONE);
+
+                if (chatMessageList.size() == 0) {
+                    emptyLayout.setVisibility(View.VISIBLE);
+                } else {
+                    messagesListView.setVisibility(View.VISIBLE);
+                }
+            } else {
+                if (emptyLayout.getVisibility() != View.GONE) {
+                    emptyLayout.setVisibility(View.GONE);
+                }
+                if (messagesListView.getVisibility() != View.VISIBLE) {
+                    messagesListView.setVisibility(View.VISIBLE);
+                }
+            }
+
             if (!isFromTheFuture) {
+
                 for (int i = 0; i < chatMessageList.size(); i++) {
                     chatMessageList.get(i).setBaseUrl(conversationUser.getBaseUrl());
                     if (globalLastKnownPastMessageId == -1 || chatMessageList.get(i).getJsonMessageId() <
@@ -712,8 +760,10 @@ public class ChatController extends BaseController implements MessagesListAdapte
                     }
                 }
 
+
                 adapter.addToEnd(chatMessageList, false);
 
+
             } else {
                 for (int i = 0; i < chatMessageList.size(); i++) {
                     chatMessageList.get(i).setBaseUrl(conversationUser.getBaseUrl());
@@ -754,6 +804,15 @@ public class ChatController extends BaseController implements MessagesListAdapte
                 pullChatMessages(1);
             }
         } else if (response.code() == 304 && !isFromTheFuture) {
+            if (isFirstMessagesProcessing) {
+                isFirstMessagesProcessing  = false;
+                loadingProgressBar.setVisibility(View.GONE);
+
+                if (emptyLayout.getVisibility() != View.VISIBLE) {
+                    emptyLayout.setVisibility(View.VISIBLE);
+                }
+            }
+
             historyRead = true;
 
             if (!lookingIntoFuture) {
@@ -794,7 +853,6 @@ public class ChatController extends BaseController implements MessagesListAdapte
             case android.R.id.home:
                 onDestroy();
                 return true;
-
             case R.id.conversation_video_call:
                 startACall(false);
                 return true;

+ 19 - 52
app/src/main/java/com/nextcloud/talk/controllers/ContactsController.java

@@ -28,7 +28,6 @@ import android.os.Handler;
 import android.support.annotation.NonNull;
 import android.support.annotation.Nullable;
 import android.support.design.widget.BottomNavigationView;
-import android.support.design.widget.CoordinatorLayout;
 import android.support.v4.view.MenuItemCompat;
 import android.support.v4.widget.SwipeRefreshLayout;
 import android.support.v7.widget.RecyclerView;
@@ -43,7 +42,6 @@ import android.view.View;
 import android.view.ViewGroup;
 import android.view.ViewTreeObserver;
 import android.view.inputmethod.EditorInfo;
-import android.widget.Button;
 import android.widget.RelativeLayout;
 
 import com.bluelinelabs.conductor.RouterTransaction;
@@ -68,7 +66,6 @@ import com.nextcloud.talk.models.json.rooms.RoomOverall;
 import com.nextcloud.talk.models.json.sharees.Sharee;
 import com.nextcloud.talk.models.json.sharees.ShareesOverall;
 import com.nextcloud.talk.utils.ApiUtils;
-import com.nextcloud.talk.utils.animations.ViewHidingBehaviourAnimation;
 import com.nextcloud.talk.utils.bundle.BundleKeys;
 import com.nextcloud.talk.utils.database.user.UserUtils;
 
@@ -130,16 +127,9 @@ public class ContactsController extends BaseController implements SearchView.OnQ
     @BindView(R.id.swipe_refresh_layout)
     SwipeRefreshLayout swipeRefreshLayout;
 
-    @BindView(R.id.bottom_buttons_layout)
-    CoordinatorLayout bottomButtonsLinearLayout;
-
     @BindView(R.id.fast_scroller)
     FastScroller fastScroller;
 
-    @Nullable
-    @BindView(R.id.clear_button)
-    Button clearButton;
-
     private UserEntity currentUser;
     private Disposable contactsQueryDisposable;
     private Disposable cacheQueryDisposable;
@@ -165,6 +155,8 @@ public class ContactsController extends BaseController implements SearchView.OnQ
     private boolean canFetchFurther = true;
     private boolean canFetchSearchFurther = true;
 
+    private MenuItem doneMenuItem;
+
     public ContactsController() {
         super();
         setHasOptionsMenu(true);
@@ -195,7 +187,7 @@ public class ContactsController extends BaseController implements SearchView.OnQ
         if (isNewConversationView) {
             toggleNewCallHeaderVisibility(!isPublicCall);
 
-            checkAndHandleBottomButtons();
+            checkAndHandleDoneMenuItem();
 
             if (getActionBar() != null) {
                 getActionBar().setDisplayHomeAsUpEnabled(true);
@@ -257,29 +249,7 @@ public class ContactsController extends BaseController implements SearchView.OnQ
         adapter.addListener(this);
     }
 
-    @Optional
-    @OnClick(R.id.clear_button)
-    public void onClearButtonClick() {
-        if (adapter != null) {
-            List<Integer> selectedPositions = adapter.getSelectedPositions();
-            for (Integer position : selectedPositions) {
-                if (adapter.getItem(position) instanceof UserItem) {
-                    UserItem userItem = (UserItem) adapter.getItem(position);
-                    adapter.toggleSelection(position);
-                    if (userItem != null) {
-                        userItem.flipItemSelection();
-                    }
-                }
-            }
-        }
-
-        checkAndHandleBottomButtons();
-    }
-
-    @Optional
-    @OnClick(R.id.done_button)
-    public void onDoneButtonClick() {
-
+    private void selectionDone() {
         if (!isPublicCall && adapter.getSelectedPositions().size() == 1) {
             RetrofitBucket retrofitBucket = ApiUtils.getRetrofitBucketForCreateRoom(currentUser.getBaseUrl(), "1",
                     ((UserItem) adapter.getItem(adapter.getSelectedPositions().get(0))).getModel().getUserId(), null);
@@ -411,6 +381,9 @@ public class ContactsController extends BaseController implements SearchView.OnQ
             case android.R.id.home:
                 getRouter().popCurrentController();
                 return true;
+            case R.id.contacts_selection_done:
+                selectionDone();
+                return true;
             default:
                 return super.onOptionsItemSelected(item);
         }
@@ -421,7 +394,9 @@ public class ContactsController extends BaseController implements SearchView.OnQ
         super.onCreateOptionsMenu(menu, inflater);
         inflater.inflate(R.menu.menu_conversation_plus_filter, menu);
         searchItem = menu.findItem(R.id.action_search);
+        doneMenuItem = menu.findItem(R.id.contacts_selection_done);
         menu.findItem(R.id.action_new_conversation).setVisible(false);
+
         initSearchView();
     }
 
@@ -576,7 +551,7 @@ public class ContactsController extends BaseController implements SearchView.OnQ
                             }
 
                             if (isNewConversationView) {
-                                checkAndHandleBottomButtons();
+                                checkAndHandleDoneMenuItem();
                             }
                         }
                         ;
@@ -648,9 +623,6 @@ public class ContactsController extends BaseController implements SearchView.OnQ
                 return "";
             }
         });
-
-        CoordinatorLayout.LayoutParams layoutParams = (CoordinatorLayout.LayoutParams) bottomButtonsLinearLayout.getLayoutParams();
-        layoutParams.setBehavior(new ViewHidingBehaviourAnimation());
     }
 
     private void dispose(@Nullable Disposable disposable) {
@@ -725,23 +697,18 @@ public class ContactsController extends BaseController implements SearchView.OnQ
         return onQueryTextChange(query);
     }
 
-    private void checkAndHandleBottomButtons() {
-        if (adapter != null && bottomButtonsLinearLayout != null && clearButton != null) {
+    private void checkAndHandleDoneMenuItem() {
+        if (adapter != null && doneMenuItem != null) {
             if (adapter.getSelectedItemCount() > 0 || isPublicCall) {
-                if (bottomButtonsLinearLayout.getVisibility() != View.VISIBLE) {
-                    bottomButtonsLinearLayout.setVisibility(View.VISIBLE);
+                if (!doneMenuItem.isVisible()) {
+                    doneMenuItem.setVisible(true);
                 }
 
-                if (adapter.getSelectedItemCount() == 0) {
-                    clearButton.setVisibility(View.GONE);
-                } else {
-                    clearButton.setVisibility(View.VISIBLE);
-                }
             } else {
-                bottomButtonsLinearLayout.setVisibility(View.GONE);
+                doneMenuItem.setVisible(false);
             }
-        } else if (bottomButtonsLinearLayout != null) {
-            bottomButtonsLinearLayout.setVisibility(View.GONE);
+        } else if (doneMenuItem != null) {
+            doneMenuItem.setVisible(false);
         }
     }
 
@@ -865,7 +832,7 @@ public class ContactsController extends BaseController implements SearchView.OnQ
                 ((UserItem) adapter.getItem(position)).flipItemSelection();
                 adapter.toggleSelection(position);
 
-                checkAndHandleBottomButtons();
+                checkAndHandleDoneMenuItem();
             }
         }
         return true;
@@ -876,7 +843,7 @@ public class ContactsController extends BaseController implements SearchView.OnQ
     void toggleCallHeader() {
         toggleNewCallHeaderVisibility(isPublicCall);
         isPublicCall = !isPublicCall;
-        checkAndHandleBottomButtons();
+        checkAndHandleDoneMenuItem();
     }
 
     private void toggleNewCallHeaderVisibility(boolean showInitialLayout) {

+ 2 - 7
app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/CallMenuController.java

@@ -125,14 +125,9 @@ public class CallMenuController extends BaseController implements FlexibleAdapte
 
         if (menuType.equals(MenuType.REGULAR)) {
             if (!TextUtils.isEmpty(room.getDisplayName())) {
-                menuItems.add(new MenuItem(
-                        getResources().getString(
-                                R.string.nc_configure_named_room, room.getDisplayName()), 0, null)
-                );
+                menuItems.add(new MenuItem(room.getDisplayName(), 0, null));
             } else if (!TextUtils.isEmpty(room.getName())) {
-                menuItems.add(new MenuItem(getResources().getString(
-                        R.string.nc_configure_named_room, room.getName()), 0, null)
-                );
+                menuItems.add(new MenuItem(room.getName(), 0, null));
             } else {
                 menuItems.add(new MenuItem(getResources().getString(R.string.nc_configure_room), 0, null));
             }

+ 4 - 0
app/src/main/java/com/nextcloud/talk/controllers/bottomsheet/EntryMenuController.java

@@ -184,6 +184,10 @@ public class EntryMenuController extends BaseController {
         super.onViewBound(view);
         NextcloudTalkApplication.getSharedApplication().getComponentApplication().inject(this);
 
+        if (room != null && operationCode == 2) {
+            editText.setText(room.getName());
+        }
+
         editText.setOnEditorActionListener((v, actionId, event) -> {
             if (actionId == EditorInfo.IME_ACTION_DONE && proceedButton != null && proceedButton.isEnabled()) {
                 proceedButton.callOnClick();

+ 1 - 1
app/src/main/java/com/nextcloud/talk/jobs/NotificationJob.java

@@ -242,7 +242,7 @@ public class NotificationJob extends Job {
                                 if (notificationManager != null) {
                                     notificationManager.notify((int) crc32.getValue(), notificationBuilder.build());
 
-                                    if (soundUri != null) {
+                                    if (soundUri != null & !ApplicationWideCurrentRoomHolder.getInstance().isInCall()) {
                                         MediaPlayer mediaPlayer = MediaPlayer.create(context, soundUri);
                                         mediaPlayer.start();
                                         mediaPlayer.setOnCompletionListener(MediaPlayer::release);

+ 5 - 1
app/src/main/java/com/nextcloud/talk/models/json/chat/ChatMessage.java

@@ -91,7 +91,11 @@ public class ChatMessage implements IMessage {
 
             @Override
             public String getAvatar() {
-                return ApiUtils.getUrlForAvatarWithName(getBaseUrl(), actorId, R.dimen.avatar_size);
+                if (getActorType().equals("users")) {
+                    return ApiUtils.getUrlForAvatarWithName(getBaseUrl(), actorId, R.dimen.avatar_size);
+                } else {
+                    return null;
+                }
             }
         };
     }

+ 2 - 0
app/src/main/java/com/nextcloud/talk/models/json/rooms/Room.java

@@ -63,6 +63,8 @@ public class Room {
     @JsonField(name = "sessionId")
     public String sessionId;
     public String password;
+    @JsonField(name = "isFavorite")
+    public boolean isFavorite;
 
     public boolean isPublic() {
         return (RoomType.ROOM_PUBLIC_CALL.equals(type));

+ 4 - 0
app/src/main/java/com/nextcloud/talk/utils/ApiUtils.java

@@ -187,4 +187,8 @@ public class ApiUtils {
         return NextcloudTalkApplication.getSharedApplication().
                 getApplicationContext().getResources().getString(R.string.nc_push_server_url) + "/devices";
     }
+
+    public static String getUrlForConversationFavorite(String baseUrl, String roomToken) {
+        return baseUrl + ocsApiVersion + "/room/" + roomToken + "/favorite";
+    }
 }

+ 89 - 0
app/src/main/java/com/nextcloud/talk/utils/emoticons/EmoticonSpan.java

@@ -0,0 +1,89 @@
+/*
+ * Copyright 2017 Keval Patel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ */
+
+package com.nextcloud.talk.utils.emoticons;
+
+import android.content.Context;
+import android.graphics.Canvas;
+import android.graphics.Paint;
+import android.graphics.drawable.Drawable;
+import android.support.v7.content.res.AppCompatResources;
+import android.text.style.DynamicDrawableSpan;
+import android.text.style.ImageSpan;
+
+/**
+ * Created by Keval Patel on 20/08/17.
+ * The {@link ImageSpan} to display custom emoticon icon based on the unicode.
+ *
+ * @author 'https://github.com/kevalpatel2106'
+ * @see <a href='https://github.com/rockerhieu/emojicon/blob/master/library/src/main/java/io/github/rockerhieu/emojicon/EmojiconSpan.java>EmojiconSpan.java</a>
+ */
+
+final class EmoticonSpan extends DynamicDrawableSpan {
+    private final float mEmoticonSize;
+    private final Context mContext;
+    private final int mEmoticonIcon;
+    private Drawable mDeferredDrawable;
+
+    EmoticonSpan(final Context context, final int emoticonIcon, final float size) {
+        this.mContext = context;
+        this.mEmoticonIcon = emoticonIcon;
+        this.mEmoticonSize = size;
+    }
+
+    @SuppressWarnings("ConstantConditions")
+    @Override
+    public Drawable getDrawable() {
+        if (mDeferredDrawable == null) {
+            mDeferredDrawable = AppCompatResources.getDrawable(mContext, mEmoticonIcon);
+            mDeferredDrawable.setBounds(0, 0, (int) mEmoticonSize, (int) mEmoticonSize);
+        }
+        return mDeferredDrawable;
+    }
+
+    @Override
+    public int getSize(final Paint paint, final CharSequence text, final int start,
+                       final int end, final Paint.FontMetricsInt fontMetrics) {
+        if (fontMetrics != null) {
+            final Paint.FontMetrics paintFontMetrics = paint.getFontMetrics();
+            final float fontHeight = paintFontMetrics.descent - paintFontMetrics.ascent;
+            final float centerY = paintFontMetrics.ascent + fontHeight / 2;
+
+            fontMetrics.ascent = (int) (centerY - mEmoticonSize / 2);
+            fontMetrics.top = fontMetrics.ascent;
+            fontMetrics.bottom = (int) (centerY + mEmoticonSize / 2);
+            fontMetrics.descent = fontMetrics.bottom;
+        }
+
+        return (int) mEmoticonSize;
+    }
+
+    @Override
+    public void draw(final Canvas canvas, final CharSequence text, final int start,
+                     final int end, final float x, final int top, final int y,
+                     final int bottom, final Paint paint) {
+        final Drawable drawable = getDrawable();
+        final Paint.FontMetrics paintFontMetrics = paint.getFontMetrics();
+        final float fontHeight = paintFontMetrics.descent - paintFontMetrics.ascent;
+        final float centerY = y + paintFontMetrics.descent - fontHeight / 2;
+        final float transitionY = centerY - mEmoticonSize / 2;
+
+        canvas.save();
+        canvas.translate(x, transitionY);
+        drawable.draw(canvas);
+        canvas.restore();
+    }
+}

+ 255 - 0
app/src/main/java/com/nextcloud/talk/utils/emoticons/EmoticonUtils.java

@@ -0,0 +1,255 @@
+/*
+ * Copyright 2017 Keval Patel.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ *  Unless required by applicable law or agreed to in writing, software
+ *  distributed under the License is distributed on an "AS IS" BASIS,
+ *  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ *  See the License for the specific language governing permissions and
+ *  limitations under the License.
+ *
+ *  Modified by Mario Danic (mario@lovelyhq.com) - Copyright 2018
+ */
+
+package com.nextcloud.talk.utils.emoticons;
+
+import android.content.Context;
+import android.support.annotation.NonNull;
+import android.support.annotation.Nullable;
+import android.text.Spannable;
+import android.text.TextUtils;
+
+import com.kevalpatel2106.emoticongifkeyboard.R;
+import com.kevalpatel2106.emoticongifkeyboard.emoticons.Emoticon;
+import com.kevalpatel2106.emoticongifkeyboard.emoticons.EmoticonProvider;
+
+import java.io.BufferedReader;
+import java.io.IOException;
+import java.io.InputStream;
+import java.io.InputStreamReader;
+import java.util.ArrayList;
+import java.util.List;
+import java.util.regex.Matcher;
+import java.util.regex.Pattern;
+
+/**
+ * Created by Keval Patel on 20/08/17.
+ * Utils to find emoticons from the string.
+ *
+ * @author 'https://github.com/kevalpatel2106'
+ */
+
+public final class EmoticonUtils {
+    /**
+     * {@link Pattern} to find the supported emoticons unicodes.
+     */
+    private static Pattern sRegexPattern;
+
+    /**
+     * Private constructor.
+     */
+    private EmoticonUtils() {
+        //Do nothing
+    }
+
+    /**
+     * Replace the system emoticons with the provided custom emoticons drawable. This will find the
+     * unicodes with supported emoticons in the provided text and  will replace the emoticons with
+     * appropriate images.
+     *
+     * @param context          instance of caller.
+     * @param text             Text to replace
+     * @param emoticonProvider {@link EmoticonProvider} for emoticons images
+     * @param emoticonSize     Size of the emoticons in dp
+     * @return Modified text.
+     */
+    public static void replaceWithImages(@NonNull final Context context,
+                                         @NonNull final Spannable text,
+                                         @NonNull final EmoticonProvider emoticonProvider,
+                                         final int emoticonSize) {
+
+        final EmoticonSpan[] existingSpans = text.getSpans(0, text.length(), EmoticonSpan.class);
+        final ArrayList<Integer> existingSpanPositions = new ArrayList<>(existingSpans.length);
+        for (EmoticonSpan existingSpan : existingSpans)
+            existingSpanPositions.add(text.getSpanStart(existingSpan));
+
+        //Get location and unicode of all emoticons.
+        final List<EmoticonRange> findAllEmojis = findAllEmoticons(context, text, emoticonProvider);
+
+        //Replace all the emoticons with their relatives.
+        for (int i = 0; i < findAllEmojis.size(); i++) {
+            final EmoticonRange location = findAllEmojis.get(i);
+            if (!existingSpanPositions.contains(location.mStartPos)) {
+                text.setSpan(new EmoticonSpan(context, location.mEmoticon.getIcon(), emoticonSize),
+                        location.mStartPos,
+                        location.mEndPos,
+                        Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
+            }
+        }
+    }
+
+    /**
+     * Find all the unicodes that represents emoticons and location of starting and ending point of that emoticon.
+     *
+     * @param context          Instance of caller.
+     * @param text             Text to replace
+     * @param emoticonProvider {@link EmoticonProvider} for emoticons images
+     * @return List of {@link EmoticonRange}.
+     * @see EmoticonRange
+     */
+    @NonNull
+    private static List<EmoticonRange> findAllEmoticons(@NonNull final Context context,
+                                                        @Nullable final CharSequence text,
+                                                        @NonNull final EmoticonProvider emoticonProvider) {
+        final List<EmoticonRange> result = new ArrayList<>();
+
+        if (!TextUtils.isEmpty(text)) {
+            final Matcher matcher = getRegex(context).matcher(text);
+            while (matcher.find()) {
+                String unicode = text.subSequence(matcher.start(), matcher.end()).toString();
+                if (emoticonProvider.hasEmoticonIcon(unicode)) { //Check if the the emoticon has icon?
+                    final Emoticon found = new Emoticon(unicode, emoticonProvider.getIcon(unicode));
+
+                    //Add this emoticon to change list.
+                    result.add(new EmoticonRange(matcher.start(), matcher.end(), found));
+                }
+            }
+        }
+
+        return result;
+    }
+
+
+    public static boolean isMessageWithSingleEmoticonOnly(@NonNull final Context context,
+                                                          @Nullable final CharSequence text) {
+        final List<EmoticonRange> result = new ArrayList<>();
+
+        if (!TextUtils.isEmpty(text)) {
+
+            // Regexp acquired from https://gist.github.com/sergeychilingaryan/94902985a636658496cb69c300bba05f
+            String unicode10regexString = "(?:[\\u2700-\\u27bf]|" +
+
+                    "(?:[\\ud83c\\udde6-\\ud83c\\uddff]){2}|" +
+                    "[\\ud800\\udc00-\\uDBFF\\uDFFF]|[\\u2600-\\u26FF])[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]|[\\ud83c\\udffb-\\ud83c\\udfff])?" +
+
+                    "(?:\\u200d(?:[^\\ud800-\\udfff]|" +
+
+                    "(?:[\\ud83c\\udde6-\\ud83c\\uddff]){2}|" +
+                    "[\\ud800\\udc00-\\uDBFF\\uDFFF]|[\\u2600-\\u26FF])[\\ufe0e\\ufe0f]?(?:[\\u0300-\\u036f\\ufe20-\\ufe23\\u20d0-\\u20f0]|[\\ud83c\\udffb-\\ud83c\\udfff])?)*|" +
+
+                    "[\\u0023-\\u0039]\\ufe0f?\\u20e3|\\u3299|\\u3297|\\u303d|\\u3030|\\u24c2|[\\ud83c\\udd70-\\ud83c\\udd71]|[\\ud83c\\udd7e-\\ud83c\\udd7f]|\\ud83c\\udd8e|[\\ud83c\\udd91-\\ud83c\\udd9a]|[\\ud83c\\udde6-\\ud83c\\uddff]|[\\ud83c\\ude01-\\ud83c\\ude02]|\\ud83c\\ude1a|\\ud83c\\ude2f|[\\ud83c\\ude32-\\ud83c\\ude3a]|[\\ud83c\\ude50-\\ud83c\\ude51]|\\u203c|\\u2049|[\\u25aa-\\u25ab]|\\u25b6|\\u25c0|[\\u25fb-\\u25fe]|\\u00a9|\\u00ae|\\u2122|\\u2139|\\ud83c\\udc04|[\\u2600-\\u26FF]|\\u2b05|\\u2b06|\\u2b07|\\u2b1b|\\u2b1c|\\u2b50|\\u2b55|\\u231a|\\u231b|\\u2328|\\u23cf|[\\u23e9-\\u23f3]|[\\u23f8-\\u23fa]|\\ud83c\\udccf|\\u2934|\\u2935|[\\u2190-\\u21ff]";
+
+            final Matcher matcher = Pattern.compile(unicode10regexString, Pattern.UNICODE_CASE).matcher(text);
+            while (matcher.find()) {
+                String unicode = text.subSequence(matcher.start(), matcher.end()).toString();
+                // quick hack
+                final Emoticon found = new Emoticon(unicode, R.drawable.emoji_food);
+                //Add this emoticon to change list.
+                result.add(new EmoticonRange(matcher.start(), matcher.end(), found));
+            }
+        } else {
+            return false;
+        }
+
+        return result.size() == 1 && result.get(0).mStartPos == 0 && text.length() == result.get(0).mEndPos;
+    }
+
+    /**
+     * Load the regex to parse unicode from the shared preference if {@link #sRegexPattern} is not
+     * loaded.
+     *
+     * @param context Instance.
+     * @return Regex to find emoticon unicode from string.
+     */
+    @NonNull
+    private static Pattern getRegex(@NonNull final Context context) {
+        if (sRegexPattern == null) {
+            String regex = readTextFile(context, R.raw.regex);
+            sRegexPattern = Pattern.compile(regex);
+        }
+        return sRegexPattern;
+    }
+
+    @NonNull
+    private static String readTextFile(@NonNull Context context, int rowResource) {
+        InputStream inputStream = context.getResources().openRawResource(rowResource); // getting json
+        BufferedReader br = new BufferedReader(new InputStreamReader(inputStream));
+
+        StringBuilder builder = new StringBuilder();
+        try {
+            String sCurrentLine;
+            while ((sCurrentLine = br.readLine()) != null) builder.append(sCurrentLine);
+        } catch (IOException e) {
+            e.printStackTrace();
+        } finally {
+            try {
+                inputStream.close();
+                br.close();
+            } catch (IOException e) {
+                e.printStackTrace();
+            }
+        }
+        return builder.toString();
+    }
+
+
+    /**
+     * Range of the emoticons unicode.
+     */
+    private static final class EmoticonRange {
+
+        /**
+         * Start portion of the emoticon in string.
+         */
+        final int mStartPos;
+
+        /**
+         * End portion of the emoticon in string.
+         */
+        final int mEndPos;
+
+        /**
+         * {@link Emoticon}.
+         */
+        final Emoticon mEmoticon;
+
+        /**
+         * Private constructor.
+         *
+         * @param start    Start portion of the emoticon in string.
+         * @param end      End portion of the emoticon in string.
+         * @param emoticon {@link Emoticon}
+         */
+        private EmoticonRange(final int start,
+                              final int end,
+                              @NonNull final Emoticon emoticon) {
+            this.mStartPos = start;
+            this.mEndPos = end;
+            this.mEmoticon = emoticon;
+        }
+
+        @Override
+        public boolean equals(final Object o) {
+            if (this == o) return true;
+            if (o == null || getClass() != o.getClass()) return false;
+
+            final EmoticonRange that = (EmoticonRange) o;
+            return mStartPos == that.mStartPos
+                    && mEndPos == that.mEndPos
+                    && mEmoticon.equals(that.mEmoticon);
+        }
+
+        @Override
+        public int hashCode() {
+            int result = mStartPos;
+            result = 31 * result + mEndPos;
+            result = 31 * result + mEmoticon.hashCode();
+            return result;
+        }
+    }
+}

+ 0 - 25
app/src/main/res/drawable/ic_hearing_white_24dp.xml

@@ -1,25 +0,0 @@
-<!--
-  ~ 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/>.
-  -->
-
-<vector android:autoMirrored="true" android:height="24dp"
-    android:tint="#FFFFFF" android:viewportHeight="24.0"
-    android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="#FF000000" android:pathData="M17,20c-0.29,0 -0.56,-0.06 -0.76,-0.15 -0.71,-0.37 -1.21,-0.88 -1.71,-2.38 -0.51,-1.56 -1.47,-2.29 -2.39,-3 -0.79,-0.61 -1.61,-1.24 -2.32,-2.53C9.29,10.98 9,9.93 9,9c0,-2.8 2.2,-5 5,-5s5,2.2 5,5h2c0,-3.93 -3.07,-7 -7,-7S7,5.07 7,9c0,1.26 0.38,2.65 1.07,3.9 0.91,1.65 1.98,2.48 2.85,3.15 0.81,0.62 1.39,1.07 1.71,2.05 0.6,1.82 1.37,2.84 2.73,3.55 0.51,0.23 1.07,0.35 1.64,0.35 2.21,0 4,-1.79 4,-4h-2c0,1.1 -0.9,2 -2,2zM7.64,2.64L6.22,1.22C4.23,3.21 3,5.96 3,9s1.23,5.79 3.22,7.78l1.41,-1.41C6.01,13.74 5,11.49 5,9s1.01,-4.74 2.64,-6.36zM11.5,9c0,1.38 1.12,2.5 2.5,2.5s2.5,-1.12 2.5,-2.5 -1.12,-2.5 -2.5,-2.5 -2.5,1.12 -2.5,2.5z"/>
-</vector>

+ 1 - 2
app/src/main/res/drawable/ic_speaker_white_24dp.xml → app/src/main/res/drawable/ic_volume_up_white_24dp.xml

@@ -21,6 +21,5 @@
 <vector android:autoMirrored="true" android:height="24dp"
     android:tint="#FFFFFF" android:viewportHeight="24.0"
     android:viewportWidth="24.0" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
-    <path android:fillColor="#FFFFFF"
-          android:pathData="M17,2L7,2c-1.1,0 -2,0.9 -2,2v16c0,1.1 0.9,1.99 2,1.99L17,22c1.1,0 2,-0.9 2,-2L19,4c0,-1.1 -0.9,-2 -2,-2zM12,4c1.1,0 2,0.9 2,2s-0.9,2 -2,2c-1.11,0 -2,-0.9 -2,-2s0.89,-2 2,-2zM12,20c-2.76,0 -5,-2.24 -5,-5s2.24,-5 5,-5 5,2.24 5,5 -2.24,5 -5,5zM12,12c-1.66,0 -3,1.34 -3,3s1.34,3 3,3 3,-1.34 3,-3 -1.34,-3 -3,-3z"/>
+    <path android:fillColor="#FFFFFF" android:pathData="M3,9v6h4l5,5L12,4L7,9L3,9zM16.5,12c0,-1.77 -1.02,-3.29 -2.5,-4.03v8.05c1.48,-0.73 2.5,-2.25 2.5,-4.02zM14,3.23v2.06c2.89,0.86 5,3.54 5,6.71s-2.11,5.85 -5,6.71v2.06c4.01,-0.91 7,-4.49 7,-8.77s-2.99,-7.86 -7,-8.77z"/>
 </vector>

+ 28 - 0
app/src/main/res/drawable/incoming_gradient.xml

@@ -0,0 +1,28 @@
+<?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="rectangle">
+    <gradient
+        android:angle="-90"
+        android:endColor="@color/transparent"
+        android:startColor="#323232"/>
+</shape>

+ 33 - 0
app/src/main/res/drawable/shape_grouped_incoming_message.xml

@@ -0,0 +1,33 @@
+<?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="rectangle">
+
+    <corners
+        android:topLeftRadius="@dimen/message_bubble_corners_radius"
+        android:bottomRightRadius="@dimen/message_bubble_corners_radius"
+        android:bottomLeftRadius="@dimen/message_bubble_corners_radius"
+        android:topRightRadius="@dimen/message_bubble_corners_radius" />
+
+    <solid android:color="@color/white" />
+
+</shape>

+ 33 - 0
app/src/main/res/drawable/shape_grouped_outcoming_message.xml

@@ -0,0 +1,33 @@
+<?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="rectangle">
+
+    <corners
+        android:bottomLeftRadius="@dimen/message_bubble_corners_radius"
+        android:topRightRadius="@dimen/message_bubble_corners_radius"
+        android:topLeftRadius="@dimen/message_bubble_corners_radius"
+        android:bottomRightRadius="@dimen/message_bubble_corners_radius" />
+
+    <solid android:color="@color/white" />
+
+</shape>

+ 33 - 0
app/src/main/res/drawable/shape_incoming_message.xml

@@ -0,0 +1,33 @@
+<?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="rectangle">
+
+    <corners
+        android:topLeftRadius="0dp"
+        android:bottomRightRadius="@dimen/message_bubble_corners_radius"
+        android:bottomLeftRadius="@dimen/message_bubble_corners_radius"
+        android:topRightRadius="@dimen/message_bubble_corners_radius" />
+
+    <solid android:color="@color/white" />
+
+</shape>

+ 33 - 0
app/src/main/res/drawable/shape_outcoming_message.xml

@@ -0,0 +1,33 @@
+<?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="rectangle">
+
+    <corners
+        android:bottomLeftRadius="@dimen/message_bubble_corners_radius"
+        android:topRightRadius="0dp"
+        android:topLeftRadius="@dimen/message_bubble_corners_radius"
+        android:bottomRightRadius="@dimen/message_bubble_corners_radius" />
+
+    <solid android:color="@color/white" />
+
+</shape>

+ 0 - 59
app/src/main/res/layout/bottom_buttons.xml

@@ -1,59 +0,0 @@
-<?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/>.
-  -->
-
-<android.support.design.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
-              android:id="@+id/bottom_buttons_layout"
-              android:layout_width="match_parent"
-              android:layout_height="48dp"
-              android:layout_gravity="bottom"
-              android:animateLayoutChanges="true"
-              android:gravity="bottom"
-              android:orientation="horizontal"
-              android:visibility="visible">
-
-    <LinearLayout
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content">
-
-        <Button
-            android:id="@+id/clear_button"
-            style="?android:attr/borderlessButtonStyle"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_weight="0.6"
-            android:background="@color/nc_darkRed"
-            android:text="@string/nc_contacts_clear"
-            android:textAlignment="center"
-            android:textColor="@android:color/white"/>
-
-        <Button
-            android:id="@+id/done_button"
-            style="?android:attr/borderlessButtonStyle"
-            android:layout_width="match_parent"
-            android:layout_height="match_parent"
-            android:layout_weight="0.4"
-            android:background="@color/nc_darkGreen"
-            android:text="@string/nc_contacts_done"
-            android:textAlignment="center"
-            android:textColor="@android:color/white"/>
-
-    </LinearLayout>
-</android.support.design.widget.CoordinatorLayout>

+ 4 - 1
app/src/main/res/layout/controller_call.xml

@@ -151,7 +151,10 @@
             android:visibility="gone"
             app:enableInitialAnimation="false"
             app:frontBackgroundColor="@color/colorPrimary"
-            app:frontImage="@drawable/ic_hearing_white_24dp"/>
+            app:animateRearImage="false"
+            app:rearBackgroundColor="@color/colorPrimaryDark"
+            app:frontImage="@drawable/ic_volume_up_white_24dp"
+            app:rearImage="@drawable/ic_volume_up_white_24dp"/>
 
     </LinearLayout>
 

+ 29 - 20
app/src/main/res/layout/controller_call_notification.xml

@@ -21,33 +21,42 @@
 <android.support.constraint.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
                                              xmlns:app="http://schemas.android.com/apk/res-auto"
                                              xmlns:tools="http://schemas.android.com/tools"
+                                             android:id="@+id/constraintLayout"
                                              android:layout_width="match_parent"
                                              android:layout_height="match_parent"
                                              android:background="@color/grey950">
 
-    <TextView
-        android:id="@+id/incomingCallTextView"
+    <RelativeLayout
         android:layout_width="match_parent"
         android:layout_height="wrap_content"
-        android:layout_marginTop="16dp"
-        android:text="@string/nc_incoming_call"
-        android:textAlignment="center"
-        android:textColor="@color/white30"
-        android:textSize="16sp"
-        app:layout_constraintEnd_toEndOf="parent"
-        app:layout_constraintStart_toStartOf="parent"
-        app:layout_constraintTop_toTopOf="parent"/>
+        android:background="@drawable/incoming_gradient">
 
-    <TextView
-        android:id="@+id/conversationNameTextView"
-        android:layout_width="match_parent"
-        android:layout_height="wrap_content"
-        android:ellipsize="marquee"
-        android:textAlignment="center"
-        android:textColor="@color/white"
-        android:textSize="28sp"
-        app:layout_constraintTop_toBottomOf="@+id/incomingCallTextView"
-        tools:text="Victor Gregorius Magnus"/>
+        <TextView
+            android:id="@+id/incomingCallTextView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_marginTop="16dp"
+            android:text="@string/nc_incoming_call"
+            android:textAlignment="center"
+            android:textColor="@color/white30"
+            android:textSize="16sp"
+            app:layout_constraintEnd_toEndOf="parent"
+            app:layout_constraintStart_toStartOf="parent"
+            app:layout_constraintTop_toTopOf="parent"/>
+
+        <TextView
+            android:id="@+id/conversationNameTextView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:ellipsize="marquee"
+            android:textAlignment="center"
+            android:textColor="@color/white"
+            android:textSize="28sp"
+            android:layout_marginBottom="16dp"
+            android:layout_below="@+id/incomingCallTextView"
+            tools:text="Victor Gregorius Magnus"/>
+
+    </RelativeLayout>
 
     <ImageView
         android:id="@+id/avatarImageView"

+ 50 - 11
app/src/main/res/layout/controller_chat.xml

@@ -24,45 +24,84 @@
                 android:layout_width="match_parent"
                 android:layout_height="match_parent">
 
+    <ProgressBar
+        android:id="@+id/progressBar"
+        android:layout_width="@dimen/item_height"
+        android:layout_height="@dimen/item_height"
+        android:layout_centerInParent="true"
+        android:layout_marginEnd="@dimen/activity_horizontal_margin"
+        android:layout_marginLeft="@dimen/activity_horizontal_margin"
+        android:layout_marginRight="@dimen/activity_horizontal_margin"
+        android:layout_marginStart="@dimen/activity_horizontal_margin"
+        android:indeterminate="true"
+        android:indeterminateTint="@color/colorPrimary"
+        android:indeterminateTintMode="src_in"
+        android:visibility="gone"/>
+
+    <RelativeLayout
+        android:id="@+id/emptyLayout"
+        android:layout_width="match_parent"
+        android:layout_height="match_parent"
+        android:visibility="gone">
+
+        <TextView
+            android:id="@+id/wawingTextView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_centerInParent="true"
+            android:text="👋"
+            android:textAlignment="center"
+            android:textSize="72sp"/>
+
+        <TextView
+            android:id="@+id/sendHiTextView"
+            android:layout_width="match_parent"
+            android:layout_height="wrap_content"
+            android:layout_below="@+id/wawingTextView"
+            android:layout_margin="8dp"
+            android:textAlignment="center"
+            android:textSize="20sp"/>
+    </RelativeLayout>
+
     <com.stfalcon.chatkit.messages.MessagesList
         android:id="@+id/messagesListView"
         android:layout_width="match_parent"
         android:layout_height="match_parent"
-        android:paddingBottom="16dp"
         android:layout_above="@+id/messageInputView"
+        android:paddingBottom="16dp"
+        android:visibility="gone"
+        app:dateHeaderTextSize="13sp"
+        app:incomingBubblePaddingBottom="@dimen/message_bubble_corners_padding"
+        app:incomingBubblePaddingLeft="@dimen/message_bubble_corners_padding"
+        app:incomingBubblePaddingRight="@dimen/message_bubble_corners_padding"
+        app:incomingBubblePaddingTop="@dimen/message_bubble_corners_padding"
         app:incomingDefaultBubbleColor="@color/white_two"
         app:incomingDefaultBubblePressedColor="@color/white_two"
-        app:incomingDefaultBubbleSelectedColor="@color/colorPrimaryDark"
+        app:incomingDefaultBubbleSelectedColor="@color/transparent"
         app:incomingTextColor="@color/nc_incoming_text_default"
         app:incomingTextLinkColor="@color/nc_incoming_text_default"
         app:incomingTextSize="@dimen/chat_text_size"
         app:incomingTimeTextSize="12sp"
-        app:incomingBubblePaddingTop="@dimen/message_bubble_corners_padding"
-        app:incomingBubblePaddingBottom="@dimen/message_bubble_corners_padding"
-        app:incomingBubblePaddingLeft="@dimen/message_bubble_corners_padding"
-        app:incomingBubblePaddingRight="@dimen/message_bubble_corners_padding"
-        app:outcomingBubblePaddingTop="@dimen/message_bubble_corners_padding"
         app:outcomingBubblePaddingBottom="@dimen/message_bubble_corners_padding"
         app:outcomingBubblePaddingLeft="@dimen/message_bubble_corners_padding"
         app:outcomingBubblePaddingRight="@dimen/message_bubble_corners_padding"
+        app:outcomingBubblePaddingTop="@dimen/message_bubble_corners_padding"
         app:outcomingDefaultBubbleColor="@color/colorPrimary"
         app:outcomingDefaultBubblePressedColor="@color/colorPrimary"
-        app:outcomingDefaultBubbleSelectedColor="@color/colorPrimaryDark"
+        app:outcomingDefaultBubbleSelectedColor="@color/transparent"
         app:outcomingTextColor="@color/nc_outcoming_text_default"
         app:outcomingTextLinkColor="@color/nc_outcoming_text_default"
         app:outcomingTextSize="@dimen/chat_text_size"
-        app:outcomingTimeTextColor="@color/warm_grey_four"
         app:outcomingTimeTextSize="12sp"
-        app:dateHeaderTextSize="13sp"
         app:textAutoLink="all"/>
 
     <com.webianks.library.PopupBubble
         android:id="@+id/popupBubbleView"
-        android:layout_margin="16dp"
         android:layout_width="wrap_content"
         android:layout_height="wrap_content"
         android:layout_above="@+id/separator"
         android:layout_centerHorizontal="true"
+        android:layout_margin="16dp"
         android:paddingEnd="8dp"
         app:pb_backgroundColor="@color/colorPrimary"
         app:pb_icon="@drawable/ic_baseline_arrow_downward_24px"

+ 0 - 4
app/src/main/res/layout/controller_generic_rv.xml

@@ -45,9 +45,5 @@
 
     </android.support.v4.widget.SwipeRefreshLayout>
 
-    <include
-        layout="@layout/bottom_buttons"
-        android:visibility="gone"/>
-
     <include layout="@layout/fast_scroller"/>
 </android.support.design.widget.CoordinatorLayout>

+ 9 - 10
app/src/main/res/layout/item_custom_incoming_text_message.xml

@@ -33,7 +33,7 @@
         android:id="@id/messageUserAvatar"
         android:layout_width="40dp"
         android:layout_height="40dp"
-        android:layout_alignParentBottom="true"
+        android:layout_alignParentTop="true"
         android:layout_marginEnd="8dp"/>
 
     <com.google.android.flexbox.FlexboxLayout
@@ -64,14 +64,13 @@
             app:layout_wrapBefore="true"/>
 
 
-    </com.google.android.flexbox.FlexboxLayout>
-
-    <TextView
-        android:id="@id/messageTime"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignEnd="@id/bubble"
-        android:layout_below="@id/bubble"
-        android:layout_marginStart="16dp"/>
+        <TextView
+            android:id="@id/messageTime"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/messageText"
+            android:layout_marginStart="8dp"
+            app:layout_alignSelf="center"/>
 
+    </com.google.android.flexbox.FlexboxLayout>
 </RelativeLayout>

+ 8 - 8
app/src/main/res/layout/item_custom_outcoming_text_message.xml

@@ -47,14 +47,14 @@
             android:layout_width="wrap_content"
             android:layout_height="wrap_content"
             android:layout_alignWithParentIfMissing="true"/>
-    </com.google.android.flexbox.FlexboxLayout>
 
-    <TextView
-        android:id="@id/messageTime"
-        android:layout_width="wrap_content"
-        android:layout_height="wrap_content"
-        android:layout_alignEnd="@id/bubble"
-        android:layout_below="@id/bubble"
-        android:layout_marginStart="16dp"/>
+        <TextView
+            android:id="@id/messageTime"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_below="@id/messageText"
+            android:layout_marginStart="8dp"
+            app:layout_alignSelf="center"/>
 
+    </com.google.android.flexbox.FlexboxLayout>
 </RelativeLayout>

+ 47 - 0
app/src/main/res/layout/library_fast_scroller_layout.xml

@@ -0,0 +1,47 @@
+<?xml version="1.0" encoding="utf-8"?>
+<merge xmlns:android="http://schemas.android.com/apk/res/android"
+       xmlns:tools="http://schemas.android.com/tools"
+       android:layout_width="wrap_content"
+       android:layout_height="match_parent">
+
+    <View
+        android:id="@+id/fast_scroller_bar"
+        android:layout_width="7dp"
+        android:layout_height="wrap_content"
+        android:layout_gravity="end"
+        android:background="@color/transparent"/>
+
+    <RelativeLayout
+        android:layout_width="wrap_content"
+        android:layout_height="wrap_content">
+
+        <TextView
+            android:id="@+id/fast_scroller_bubble"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_gravity="end"
+            android:layout_marginEnd="0dp"
+            android:paddingLeft="16dp"
+            android:paddingRight="16dp"
+            android:layout_toStartOf="@+id/fast_scroller_handle"
+            android:background="@drawable/fast_scroller_bubble"
+            android:gravity="center"
+            android:textColor="?android:attr/textColorPrimaryInverse"
+            android:textSize="38sp"
+            android:visibility="gone"
+            tools:visibility="visible"
+            tools:text="A" />
+
+        <ImageView
+            android:id="@+id/fast_scroller_handle"
+            android:alpha="0.5"
+            android:layout_width="wrap_content"
+            android:layout_height="wrap_content"
+            android:layout_alignParentEnd="true"
+            android:paddingStart="6dp"
+            android:contentDescription="@null"
+            android:src="@drawable/fast_scroller_handle"/>
+
+    </RelativeLayout>
+
+</merge>

+ 30 - 0
app/src/main/res/menu/menu_contacts.xml

@@ -0,0 +1,30 @@
+<?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/>.
+  -->
+
+<menu xmlns:android="http://schemas.android.com/apk/res/android"
+      xmlns:app="http://schemas.android.com/apk/res-auto">
+
+    <item
+        android:id="@+id/contacts_selection_done"
+        android:title="@string/nc_contacts_done"
+        app:showAsAction="always"
+        android:visible="false"/>
+</menu>

+ 7 - 0
app/src/main/res/menu/menu_conversation_plus_filter.xml

@@ -35,4 +35,11 @@
           android:title="@string/nc_new_conversation"
           android:icon="@drawable/ic_add_white_24px"
           app:showAsAction="ifRoom"/>
+
+	<item
+		android:id="@+id/contacts_selection_done"
+		android:title="@string/nc_contacts_done"
+		app:showAsAction="always"
+		android:visible="false"/>
+
 </menu>

+ 1 - 1
app/src/main/res/values/dimens.xml

@@ -17,7 +17,7 @@
     <dimen name="avatar_size">40dp</dimen>
     <dimen name="avatar_size_big">80dp</dimen>
     <dimen name="avatar_size_very_big">@dimen/avatar_fetching_size_very_big</dimen>
-    <dimen name="avatar_fetching_size_very_big">140dp</dimen>
+    <dimen name="avatar_fetching_size_very_big">180dp</dimen>
     <dimen name="avatar_corner_radius">20dp</dimen>
 
     <dimen name="chat_text_size">14sp</dimen>

+ 4 - 2
app/src/main/res/values/strings.xml

@@ -85,7 +85,6 @@
     <!-- Room menu -->
     <string name="nc_start_conversation">Start a conversation</string>
     <string name="nc_configure_room">Configure conversation</string>
-    <string name="nc_configure_named_room">%1$s</string>
     <string name="nc_leave">Leave conversation</string>
     <string name="nc_rename">Rename conversation</string>
     <string name="nc_set_password">Set a password</string>
@@ -145,7 +144,6 @@
     <string name="nc_share_text_pass">\nPassword: %1$s</string>
 
     <!-- Magical stuff -->
-    <string name="nc_empty"></string>
     <string name="nc_push_to_talk">Push-to-talk</string>
     <string name="nc_push_to_talk_desc">With microphone disabled, click&amp;hold to use Push-to-talk</string>
     <string name="nc_store_short_desc">Have private video calls and chat using your own server.</string>
@@ -197,4 +195,8 @@ Find Nextcloud on https://nextcloud.com</string>
     <string name="nc_description_more_menu_group">Menu for public conversation %1$s</string>
     <string name="nc_description_send_message_button">Send message</string>
 
+    <!-- Chat empty state -->
+    <string name="nc_chat_empty">Click to be the first to say %1$s!</string>
+    <string name="nc_hello">Hello</string>
+
 </resources>