1 /* 2 * Copyright 2017 Google Inc. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.example.android.wearable.wear.messaging.chat; 17 18 import android.content.Context; 19 import android.graphics.Bitmap; 20 import android.support.percent.PercentRelativeLayout; 21 import android.support.v4.content.ContextCompat; 22 import android.support.v4.graphics.drawable.RoundedBitmapDrawable; 23 import android.support.v4.graphics.drawable.RoundedBitmapDrawableFactory; 24 import android.support.v7.util.SortedList; 25 import android.support.v7.widget.RecyclerView; 26 import android.support.v7.widget.util.SortedListAdapterCallback; 27 import android.support.wearable.view.WearableRecyclerView; 28 import android.view.LayoutInflater; 29 import android.view.View; 30 import android.view.ViewGroup; 31 import android.widget.ImageView; 32 import android.widget.TextView; 33 import com.bumptech.glide.Glide; 34 import com.bumptech.glide.request.animation.GlideAnimation; 35 import com.bumptech.glide.request.target.SimpleTarget; 36 import com.example.android.wearable.wear.messaging.R; 37 import com.example.android.wearable.wear.messaging.model.Chat; 38 import com.example.android.wearable.wear.messaging.model.Message; 39 import com.example.android.wearable.wear.messaging.model.Profile; 40 import java.util.Calendar; 41 import java.util.Collection; 42 import java.util.Locale; 43 44 /** 45 * Adapter for chat view. Uses a SortedList of Messages by sent time. Determines if the senderId is 46 * the current user and chooses the corresponding senderId layout. 47 * 48 * <p>The adapter will tint the background of a message so that there is an alternating visual 49 * difference to make reading messages easier by providing better visual clues 50 */ 51 class ChatAdapter extends WearableRecyclerView.Adapter<ChatAdapter.MessageViewHolder> { 52 53 private final Context mContext; 54 private final Chat mChat; 55 private final Profile mUser; 56 private final SortedList<Message> mMessages; 57 58 private final int mBlue30; 59 private final int mBlue15; 60 61 ChatAdapter(Context context, Chat chat, Profile user) { 62 this.mContext = context; 63 this.mChat = chat; 64 this.mUser = user; 65 66 mBlue15 = ContextCompat.getColor(mContext, R.color.blue_15); 67 mBlue30 = ContextCompat.getColor(mContext, R.color.blue_30); 68 69 mMessages = 70 new SortedList<>( 71 Message.class, 72 new SortedListAdapterCallback<Message>(this) { 73 @Override 74 public int compare(Message m1, Message m2) { 75 return (int) (m1.getSentTime() - m2.getSentTime()); 76 } 77 78 @Override 79 public boolean areContentsTheSame(Message oldItem, Message newItem) { 80 return oldItem.equals(newItem); 81 } 82 83 @Override 84 public boolean areItemsTheSame(Message item1, Message item2) { 85 return item1.getId().equals(item2.getId()); 86 } 87 }); 88 } 89 90 @Override 91 public MessageViewHolder onCreateViewHolder(ViewGroup parent, int viewType) { 92 return new MessageViewHolder( 93 LayoutInflater.from(mContext).inflate(R.layout.chat_message, parent, false)); 94 } 95 96 @Override 97 public void onBindViewHolder(final MessageViewHolder holder, int position) { 98 Message message = mMessages.get(position); 99 Profile sender = mChat.getParticipants().get(mMessages.get(position).getSenderId()); 100 if (sender == null) { 101 sender = mUser; 102 } 103 104 Glide.with(mContext) 105 .load(sender.getProfileImageSource()) 106 .asBitmap() 107 .placeholder(R.drawable.ic_face_white_24dp) 108 .into( 109 new SimpleTarget<Bitmap>(100, 100) { 110 @Override 111 public void onResourceReady( 112 Bitmap resource, 113 GlideAnimation<? super Bitmap> glideAnimation) { 114 RoundedBitmapDrawable circularBitmapDrawable = 115 RoundedBitmapDrawableFactory.create( 116 mContext.getResources(), resource); 117 circularBitmapDrawable.setCircular(true); 118 holder.profileImage.setImageDrawable(circularBitmapDrawable); 119 } 120 }); 121 122 // Convert to just the first name of the sender or short hand it if the sender is you. 123 String name; 124 if (isUser(sender.getId())) { 125 name = "You"; 126 } else { 127 name = sender.getName().split("\\s")[0]; 128 } 129 holder.textName.setText(name); 130 131 // Odd messages have a darker background. 132 if (position % 2 == 0) { 133 holder.parentLayout.setBackgroundColor(mBlue15); 134 } else { 135 holder.parentLayout.setBackgroundColor(mBlue30); 136 } 137 138 holder.textContent.setText(message.getText()); 139 holder.textTime.setText(millisToDateTime(message.getSentTime())); 140 } 141 142 @Override 143 public int getItemCount() { 144 return mMessages.size(); 145 } 146 147 public void addMessage(Message message) { 148 mMessages.add(message); 149 } 150 151 public void addMessages(Collection<Message> messages) { 152 // There is a bug with {@link SortedList#addAll} that will add new items to the end 153 // of the list allowing duplications in the view which is unexpected behavior 154 // https://code.google.com/p/android/issues/detail?id=201618 155 // so we will mimic the add all operation (add individually but execute in one batch) 156 mMessages.beginBatchedUpdates(); 157 for (Message message : messages) { 158 mMessages.add(message); 159 } 160 mMessages.endBatchedUpdates(); 161 } 162 163 /** 164 * Converts time since epoch to Month Date Time. 165 * 166 * @param time since epoch 167 * @return String formatted in Month Date HH:MM 168 */ 169 private String millisToDateTime(long time) { 170 Calendar cal = Calendar.getInstance(); 171 cal.setTimeInMillis(time); 172 String month = cal.getDisplayName(Calendar.MONTH, Calendar.SHORT, Locale.US); 173 int date = cal.get(Calendar.DAY_OF_MONTH); 174 int hour = cal.get(Calendar.HOUR_OF_DAY); 175 int minute = cal.get(Calendar.MINUTE); 176 177 return month + " " + date + " " + hour + ":" + String.format(Locale.US, "%02d", minute); 178 } 179 180 private boolean isUser(String id) { 181 return id.equals(mUser.getId()); 182 } 183 184 /** View holder to encapsulate the details of a chat message. */ 185 class MessageViewHolder extends RecyclerView.ViewHolder { 186 187 final ViewGroup parentLayout; 188 final TextView textContent; 189 final TextView textName; 190 final ImageView profileImage; 191 final TextView textTime; 192 193 public MessageViewHolder(View itemView) { 194 super(itemView); 195 196 parentLayout = (PercentRelativeLayout) itemView.findViewById(R.id.layout_container); 197 textContent = (TextView) itemView.findViewById(R.id.text_content); 198 textName = (TextView) itemView.findViewById(R.id.text_name); 199 textTime = (TextView) itemView.findViewById(R.id.text_time); 200 profileImage = (ImageView) itemView.findViewById(R.id.profile_img); 201 } 202 } 203 } 204