smoother scrolling (first step)

This commit is contained in:
Daniel Gultsch 2014-04-25 16:24:56 +02:00
parent 18c183a767
commit bf2d0d5596
9 changed files with 243 additions and 62 deletions

View file

@ -22,24 +22,39 @@
android:id="@+id/conversation_name" android:id="@+id/conversation_name"
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_alignLeft="@+id/conversation_lastmsg" android:layout_alignLeft="@+id/conversation_lastwrapper"
android:layout_toLeftOf="@+id/conversation_lastupdate" android:layout_toLeftOf="@+id/conversation_lastupdate"
android:singleLine="true" android:singleLine="true"
android:textColor="#636363" android:textColor="#636363"
android:textSize="20sp" android:textSize="20sp"
android:typeface="sans" /> android:typeface="sans" />
<LinearLayout
android:orientation="vertical"
android:id="@+id/conversation_lastwrapper"
android:layout_width="fill_parent"
android:layout_height="wrap_content"
android:layout_below="@id/conversation_name"
android:paddingTop="3dp"
>
<TextView <TextView
android:id="@+id/conversation_lastmsg" android:id="@+id/conversation_lastmsg"
android:layout_width="fill_parent" android:layout_width="fill_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_below="@id/conversation_name"
android:textColor="#636363" android:textColor="#636363"
android:textSize="14sp" android:textSize="14sp"
android:singleLine="true" android:singleLine="true"
android:scrollHorizontally="false" android:scrollHorizontally="false"
android:paddingTop="3dp"/> />
<ImageView
android:id="@+id/conversation_lastimage"
android:layout_width="fill_parent"
android:layout_height="36dp"
android:scaleType="centerCrop" />
</LinearLayout>
<TextView <TextView
android:id="@+id/conversation_lastupdate" android:id="@+id/conversation_lastupdate"
android:layout_width="wrap_content" android:layout_width="wrap_content"

View file

@ -25,8 +25,6 @@
android:layout_width="wrap_content" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:adjustViewBounds="true" android:adjustViewBounds="true"
android:maxHeight="288dp"
android:maxWidth="288dp"
android:paddingBottom="2dp" android:paddingBottom="2dp"
/> />

View file

@ -66,4 +66,6 @@
<string name="send_pgp_message">Send openPGP encrypted message</string> <string name="send_pgp_message">Send openPGP encrypted message</string>
<string name="your_nick_has_been_changed">Your nickname has been changed</string> <string name="your_nick_has_been_changed">Your nickname has been changed</string>
<string name="download_image">Download Image</string> <string name="download_image">Download Image</string>
<string name="error_loading_image">Error loading image (File not found)</string>
<string name="image_offered_for_download"><i>Image file offered for download</i></string>
</resources> </resources>

View file

@ -116,7 +116,9 @@ public class Conversation extends AbstractEntity {
message.setTime(getCreated()); message.setTime(getCreated());
return message; return message;
} else { } else {
return this.messages.get(this.messages.size() - 1); Message message = this.messages.get(this.messages.size() - 1);
message.setConversation(this);
return message;
} }
} }

View file

@ -6,6 +6,7 @@ import java.io.FileOutputStream;
import java.io.IOException; import java.io.IOException;
import java.io.InputStream; import java.io.InputStream;
import java.io.OutputStream; import java.io.OutputStream;
import java.lang.ref.WeakReference;
import android.content.Context; import android.content.Context;
import android.graphics.Bitmap; import android.graphics.Bitmap;
@ -13,6 +14,9 @@ import android.graphics.BitmapFactory;
import android.net.Uri; import android.net.Uri;
import android.util.Log; import android.util.Log;
import android.util.LruCache; import android.util.LruCache;
import android.view.View;
import android.widget.ImageView;
import android.widget.TextView;
import eu.siacs.conversations.entities.Account; import eu.siacs.conversations.entities.Account;
import eu.siacs.conversations.entities.Conversation; import eu.siacs.conversations.entities.Conversation;
@ -40,6 +44,10 @@ public class FileBackend {
} }
public LruCache<String, Bitmap> getThumbnailCache() {
return thumbnailCache;
}
public JingleFile getJingleFile(Message message) { public JingleFile getJingleFile(Message message) {
Conversation conversation = message.getConversation(); Conversation conversation = message.getConversation();
String prefix = context.getFilesDir().getAbsolutePath(); String prefix = context.getFilesDir().getAbsolutePath();
@ -49,7 +57,7 @@ public class FileBackend {
return new JingleFile(path + "/" + filename); return new JingleFile(path + "/" + filename);
} }
private Bitmap resize(Bitmap originalBitmap, int size) { public Bitmap resize(Bitmap originalBitmap, int size) {
int w = originalBitmap.getWidth(); int w = originalBitmap.getWidth();
int h = originalBitmap.getHeight(); int h = originalBitmap.getHeight();
if (Math.max(w, h) > size) { if (Math.max(w, h) > size) {
@ -87,7 +95,12 @@ public class FileBackend {
if (!success) { if (!success) {
// Log.d("xmppService", "couldnt compress"); // Log.d("xmppService", "couldnt compress");
} }
os.flush();
os.close(); os.close();
long size = file.getSize();
int width = scalledBitmap.getWidth();
int height = scalledBitmap.getHeight();
message.setBody(""+size+","+width+","+height);
return file; return file;
} catch (FileNotFoundException e) { } catch (FileNotFoundException e) {
// TODO Auto-generated catch block // TODO Auto-generated catch block
@ -105,7 +118,7 @@ public class FileBackend {
.getAbsolutePath()); .getAbsolutePath());
} }
public Bitmap getThumbnailFromMessage(Message message, int size) public Bitmap getThumbnail(Message message, int size)
throws FileNotFoundException { throws FileNotFoundException {
Bitmap thumbnail = thumbnailCache.get(message.getUuid()); Bitmap thumbnail = thumbnailCache.get(message.getUuid());
if (thumbnail == null) { if (thumbnail == null) {
@ -120,6 +133,45 @@ public class FileBackend {
return thumbnail; return thumbnail;
} }
public void getThumbnailAsync(final Message message, final int size, ImageView imageView, TextView textView) {
Bitmap thumbnail = thumbnailCache.get(message.getUuid());
if (thumbnail == null) {
final WeakReference<ImageView> image = new WeakReference<ImageView>(imageView);
final WeakReference<TextView> text = new WeakReference<TextView>(textView);
new Thread(new Runnable() {
@Override
public void run() {
if (image.get()!=null) {
image.get().setVisibility(View.GONE);
}
if (text.get()!=null) {
text.get().setVisibility(View.VISIBLE);
text.get().setText("loading image");
}
Bitmap fullsize = BitmapFactory.decodeFile(getJingleFile(message)
.getAbsolutePath());
if (fullsize!=null) {
Bitmap thumbnail = resize(fullsize, size);
thumbnailCache.put(message.getUuid(), thumbnail);
if (image.get()!=null) {
image.get().setVisibility(View.VISIBLE);
image.get().setImageBitmap(thumbnail);
}
if (text.get()!=null) {
text.get().setVisibility(View.GONE);
}
}
}
}).start();
} else {
textView.setVisibility(View.GONE);
imageView.setVisibility(View.VISIBLE);
imageView.setImageBitmap(thumbnail);
}
}
public void removeFiles(Conversation conversation) { public void removeFiles(Conversation conversation) {
String prefix = context.getFilesDir().getAbsolutePath(); String prefix = context.getFilesDir().getAbsolutePath();
String path = prefix + "/" + conversation.getAccount().getJid() + "/" String path = prefix + "/" + conversation.getAccount().getJid() + "/"

View file

@ -423,8 +423,8 @@ public class XmppConnectionService extends Service {
convChangedListener.onConversationListChanged(); convChangedListener.onConversationListChanged();
} }
getFileBackend().copyImageToPrivateStorage(message, uri); getFileBackend().copyImageToPrivateStorage(message, uri);
databaseBackend.createMessage(message);
message.setStatus(Message.STATUS_OFFERED); message.setStatus(Message.STATUS_OFFERED);
databaseBackend.createMessage(message);
if (convChangedListener!=null) { if (convChangedListener!=null) {
convChangedListener.onConversationListChanged(); convChangedListener.onConversationListChanged();
} }

View file

@ -1,5 +1,7 @@
package eu.siacs.conversations.ui; package eu.siacs.conversations.ui;
import java.io.FileNotFoundException;
import java.lang.ref.WeakReference;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Hashtable; import java.util.Hashtable;
import java.util.List; import java.util.List;
@ -11,20 +13,26 @@ import eu.siacs.conversations.entities.Conversation;
import eu.siacs.conversations.entities.Message; import eu.siacs.conversations.entities.Message;
import eu.siacs.conversations.utils.ExceptionHelper; import eu.siacs.conversations.utils.ExceptionHelper;
import eu.siacs.conversations.utils.UIHelper; import eu.siacs.conversations.utils.UIHelper;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.preference.PreferenceManager; import android.preference.PreferenceManager;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.app.AlertDialog.Builder;
import android.app.FragmentTransaction; import android.app.FragmentTransaction;
import android.content.Context; import android.content.Context;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.DialogInterface.OnClickListener; import android.content.DialogInterface.OnClickListener;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences; import android.content.SharedPreferences;
import android.content.res.Resources;
import android.graphics.Bitmap;
import android.graphics.Color; import android.graphics.Color;
import android.graphics.Typeface; import android.graphics.Typeface;
import android.graphics.drawable.BitmapDrawable;
import android.graphics.drawable.Drawable;
import android.support.v4.widget.SlidingPaneLayout; import android.support.v4.widget.SlidingPaneLayout;
import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener; import android.support.v4.widget.SlidingPaneLayout.PanelSlideListener;
import android.util.DisplayMetrics;
import android.util.Log;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.Menu; import android.view.Menu;
@ -91,6 +99,7 @@ public class ConversationActivity extends XmppActivity {
}; };
protected ConversationActivity activity = this; protected ConversationActivity activity = this;
private DisplayMetrics metrics;
public List<Conversation> getConversationList() { public List<Conversation> getConversationList() {
return this.conversationList; return this.conversationList;
@ -115,6 +124,8 @@ public class ConversationActivity extends XmppActivity {
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
metrics = getResources().getDisplayMetrics();
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
setContentView(R.layout.fragment_conversations_overview); setContentView(R.layout.fragment_conversations_overview);
@ -150,7 +161,35 @@ public class ConversationActivity extends XmppActivity {
convName.setText(conv.getName(useSubject)); convName.setText(conv.getName(useSubject));
TextView convLastMsg = (TextView) view TextView convLastMsg = (TextView) view
.findViewById(R.id.conversation_lastmsg); .findViewById(R.id.conversation_lastmsg);
convLastMsg.setText(conv.getLatestMessage().getBody()); ImageView imagePreview = (ImageView) view.findViewById(R.id.conversation_lastimage);
Message latestMessage = conv.getLatestMessage();
if (latestMessage.getType() == Message.TYPE_TEXT) {
convLastMsg.setText(conv.getLatestMessage().getBody());
convLastMsg.setVisibility(View.VISIBLE);
imagePreview.setVisibility(View.GONE);
} else if (latestMessage.getType() == Message.TYPE_IMAGE) {
if ((latestMessage.getStatus() >= Message.STATUS_RECIEVED)&&(latestMessage.getStatus() != Message.STATUS_PREPARING)) {
convLastMsg.setVisibility(View.GONE);
imagePreview.setVisibility(View.VISIBLE);
loadBitmap(latestMessage, imagePreview);
} else {
convLastMsg.setVisibility(View.VISIBLE);
imagePreview.setVisibility(View.GONE);
if (latestMessage.getStatus() == Message.STATUS_PREPARING) {
convLastMsg.setText(getText(R.string.preparing_image));
} else if (latestMessage.getStatus() == Message.STATUS_RECEIVED_OFFER) {
convLastMsg.setText(getText(R.string.image_offered_for_download));
} else if (latestMessage.getStatus() == Message.STATUS_RECIEVING) {
convLastMsg.setText(getText(R.string.receiving_image));
} else {
convLastMsg.setText("");
}
}
}
if (!conv.isRead()) { if (!conv.isRead()) {
convName.setTypeface(null, Typeface.BOLD); convName.setTypeface(null, Typeface.BOLD);
@ -164,10 +203,11 @@ public class ConversationActivity extends XmppActivity {
.setText(UIHelper.readableTimeDifference(conv .setText(UIHelper.readableTimeDifference(conv
.getLatestMessage().getTimeSent())); .getLatestMessage().getTimeSent()));
ImageView imageView = (ImageView) view ImageView profilePicture = (ImageView) view
.findViewById(R.id.conversation_image); .findViewById(R.id.conversation_image);
imageView.setImageBitmap(UIHelper.getContactPicture( profilePicture.setImageBitmap(UIHelper.getContactPicture(
conv, 56, activity.getApplicationContext(), false)); conv, 56, activity.getApplicationContext(), false));
return view; return view;
} }
@ -602,4 +642,92 @@ public class ConversationActivity extends XmppActivity {
}); });
builder.create().show(); builder.create().show();
} }
class BitmapWorkerTask extends AsyncTask<Message, Void, Bitmap> {
private final WeakReference<ImageView> imageViewReference;
private Message message = null;
public BitmapWorkerTask(ImageView imageView) {
// Use a WeakReference to ensure the ImageView can be garbage collected
imageViewReference = new WeakReference<ImageView>(imageView);
}
// Decode image in background.
@Override
protected Bitmap doInBackground(Message... params) {
message = params[0];
try {
return xmppConnectionService.getFileBackend().getThumbnail(message, (int) (metrics.density * 288));
} catch (FileNotFoundException e) {
Log.d("xmppService","file not found!");
return null;
}
}
// Once complete, see if ImageView is still around and set bitmap.
@Override
protected void onPostExecute(Bitmap bitmap) {
if (imageViewReference != null && bitmap != null) {
final ImageView imageView = imageViewReference.get();
if (imageView != null) {
imageView.setImageBitmap(bitmap);
}
}
}
}
public void loadBitmap(Message message, ImageView imageView) {
if (cancelPotentialWork(message, imageView)) {
final BitmapWorkerTask task = new BitmapWorkerTask(imageView);
final AsyncDrawable asyncDrawable =
new AsyncDrawable(getResources(), null, task);
imageView.setImageDrawable(asyncDrawable);
task.execute(message);
}
}
public static boolean cancelPotentialWork(Message message, ImageView imageView) {
final BitmapWorkerTask bitmapWorkerTask = getBitmapWorkerTask(imageView);
if (bitmapWorkerTask != null) {
final Message oldMessage = bitmapWorkerTask.message;
// If bitmapData is not yet set or it differs from the new data
if (oldMessage == null || message != oldMessage) {
// Cancel previous task
bitmapWorkerTask.cancel(true);
} else {
// The same work is already in progress
return false;
}
}
// No task associated with the ImageView, or an existing task was cancelled
return true;
}
private static BitmapWorkerTask getBitmapWorkerTask(ImageView imageView) {
if (imageView != null) {
final Drawable drawable = imageView.getDrawable();
if (drawable instanceof AsyncDrawable) {
final AsyncDrawable asyncDrawable = (AsyncDrawable) drawable;
return asyncDrawable.getBitmapWorkerTask();
}
}
return null;
}
static class AsyncDrawable extends BitmapDrawable {
private final WeakReference<BitmapWorkerTask> bitmapWorkerTaskReference;
public AsyncDrawable(Resources res, Bitmap bitmap,
BitmapWorkerTask bitmapWorkerTask) {
super(res, bitmap);
bitmapWorkerTaskReference =
new WeakReference<BitmapWorkerTask>(bitmapWorkerTask);
}
public BitmapWorkerTask getBitmapWorkerTask() {
return bitmapWorkerTaskReference.get();
}
}
} }

View file

@ -297,27 +297,38 @@ public class ConversationFragment extends Fragment {
} }
}); });
} else { } else {
try { viewHolder.messageBody.setVisibility(View.GONE);
Bitmap thumbnail = activity.xmppConnectionService.getFileBackend().getThumbnailFromMessage(item,(int) (metrics.density * 288)); viewHolder.image.setVisibility(View.VISIBLE);
viewHolder.image.setImageBitmap(thumbnail); String[] params = item.getBody().split(",");
viewHolder.messageBody.setVisibility(View.GONE); if (params.length==3) {
viewHolder.image.setVisibility(View.VISIBLE); int target = (int) (metrics.density * 288);
viewHolder.image.setOnClickListener(new OnClickListener() { int w = Integer.parseInt(params[1]);
int h = Integer.parseInt(params[2]);
int scalledW;
int scalledH;
if (w <= h) {
scalledW = (int) (w / ((double) h / target));
scalledH = target;
} else {
scalledW = target;
scalledH = (int) (h / ((double) w / target));
}
viewHolder.image.setLayoutParams(new LinearLayout.LayoutParams(scalledW, scalledH));
} else {
Log.d("xmppService","message body has less than 3 params");
}
activity.loadBitmap(item, viewHolder.image);
viewHolder.image.setOnClickListener(new OnClickListener() {
@Override @Override
public void onClick(View v) { public void onClick(View v) {
Uri uri = Uri.parse("content://eu.siacs.conversations.images/"+item.getConversationUuid()+"/"+item.getUuid()); Uri uri = Uri.parse("content://eu.siacs.conversations.images/"+item.getConversationUuid()+"/"+item.getUuid());
Log.d("xmppService","staring intent with uri:"+uri.toString()); Log.d("xmppService","staring intent with uri:"+uri.toString());
Intent intent = new Intent(Intent.ACTION_VIEW); Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setDataAndType(uri, "image/*"); intent.setDataAndType(uri, "image/*");
startActivity(intent); startActivity(intent);
} }
}); });
} catch (FileNotFoundException e) {
viewHolder.image.setVisibility(View.GONE);
viewHolder.messageBody.setText("error loading image file");
viewHolder.messageBody.setVisibility(View.VISIBLE);
}
} }
} else { } else {
viewHolder.image.setVisibility(View.GONE); viewHolder.image.setVisibility(View.GONE);
@ -686,13 +697,6 @@ public class ConversationFragment extends Fragment {
return bm; return bm;
} }
} }
public Bitmap getError() {
if (error == null) {
error = UIHelper.getErrorPicture(200);
}
return error;
}
} }
class DecryptMessage extends AsyncTask<Message, Void, Boolean> { class DecryptMessage extends AsyncTask<Message, Void, Boolean> {

View file

@ -302,26 +302,6 @@ public class UIHelper {
bgColor, fgColor); bgColor, fgColor);
} }
public static Bitmap getErrorPicture(int size) {
Bitmap bitmap = Bitmap
.createBitmap(size, size, Bitmap.Config.ARGB_8888);
Canvas canvas = new Canvas(bitmap);
bitmap.eraseColor(0xFFe92727);
Paint paint = new Paint();
paint.setColor(0xffe5e5e5);
paint.setTextSize((float) (size * 0.9));
paint.setAntiAlias(true);
Rect rect = new Rect();
paint.getTextBounds("!", 0, 1, rect);
float width = paint.measureText("!");
canvas.drawText("!", (size / 2) - (width / 2),
(size / 2) + (rect.height() / 2), paint);
return bitmap;
}
public static void showErrorNotification(Context context, List<Account> accounts) { public static void showErrorNotification(Context context, List<Account> accounts) {
NotificationManager mNotificationManager = (NotificationManager) context NotificationManager mNotificationManager = (NotificationManager) context
.getSystemService(Context.NOTIFICATION_SERVICE); .getSystemService(Context.NOTIFICATION_SERVICE);