Skip to content

Commit

Permalink
adding feature tinode/chat/issues/443 item 10
Browse files Browse the repository at this point in the history
  • Loading branch information
or-else committed Dec 27, 2020
1 parent 6d38271 commit fcbda4c
Show file tree
Hide file tree
Showing 18 changed files with 491 additions and 143 deletions.
8 changes: 3 additions & 5 deletions app/src/main/java/co/tinode/tindroid/ChatsAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -67,12 +67,10 @@ void resetContent(Activity activity, final boolean archive, final boolean banned
newTopicIndex.put(t.getName(), newTopicIndex.size());
}

activity.runOnUiThread(() -> {
mTopics = new ArrayList<>(newTopics);
mTopicIndex = newTopicIndex;
mTopics = new ArrayList<>(newTopics);
mTopicIndex = newTopicIndex;

notifyDataSetChanged();
});
activity.runOnUiThread(this::notifyDataSetChanged);
}

@NonNull
Expand Down
14 changes: 14 additions & 0 deletions app/src/main/java/co/tinode/tindroid/db/MessageDb.java
Original file line number Diff line number Diff line change
Expand Up @@ -255,6 +255,20 @@ static Cursor getMessageById(SQLiteDatabase db, long msgId) {
return db.rawQuery(sql, null);
}

/*
* Get a list of the latest message for every topic, sent or received.
* See explanation here: https://stackoverflow.com/a/2111420
*/
static Cursor getLatestMessages(SQLiteDatabase db) {
final String sql = "SELECT m1.* FROM " + TABLE_NAME + " AS m1" +
" LEFT JOIN " + TABLE_NAME + " AS m2" +
" ON (m1." + COLUMN_NAME_TOPIC_ID + "=m2." + COLUMN_NAME_TOPIC_ID +
" AND m1." + COLUMN_NAME_SEQ + "<m2." + COLUMN_NAME_SEQ + ")" +
" WHERE m2." + _ID + " IS NULL";

return db.rawQuery(sql, null);
}

/**
* Query messages which are ready for sending but has not been sent yet.
*
Expand Down
11 changes: 11 additions & 0 deletions app/src/main/java/co/tinode/tindroid/db/SqlStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -492,6 +492,17 @@ public <R extends Iterator<Message> & Closeable> R getQueuedMessages(Topic topic
return (R) list;
}

@SuppressWarnings("unchecked")
@Override
public <R extends Iterator<Message> & Closeable> R getLatestMessages() {
MessageList list = null;
Cursor c = MessageDb.getLatestMessages(mDbh.getReadableDatabase());
if (c != null) {
list = new MessageList(c);
}
return (R) list;
}

@Override
public MsgRange[] getQueuedMessageDeletes(Topic topic, boolean hard) {
StoredTopic st = (StoredTopic) topic.getLocal();
Expand Down
214 changes: 205 additions & 9 deletions app/src/main/java/co/tinode/tindroid/media/PreviewFormatter.java
Original file line number Diff line number Diff line change
@@ -1,24 +1,220 @@
package co.tinode.tindroid.media;

import android.content.Context;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.text.SpannableStringBuilder;
import android.text.Spanned;
import android.text.SpannedString;
import android.text.style.CharacterStyle;
import android.text.style.ForegroundColorSpan;
import android.text.style.ImageSpan;
import android.text.style.StrikethroughSpan;
import android.text.style.StyleSpan;
import android.text.style.TypefaceSpan;
import android.util.Log;
import android.widget.TextView;

import java.util.Map;

import androidx.annotation.DrawableRes;
import androidx.annotation.StringRes;
import androidx.appcompat.content.res.AppCompatResources;
import co.tinode.tindroid.R;
import co.tinode.tinodesdk.model.Drafty;

// Drafty formatter for creating one-line message previews.
public class PreviewFormatter extends SpanFormatter {
private final int mLength;
public class PreviewFormatter extends AbstractDraftyFormatter<PreviewFormatter.MeasuredTreeNode> {
private final float mFontSize;

PreviewFormatter(final TextView container) {
super(container);

mFontSize = container.getTextSize();
}

public static Spanned toSpanned(final TextView container, final Drafty content, final int maxLength) {
if (content == null) {
return new SpannedString("");
}
if (content.isPlain()) {
String text = content.toString();
if (text.length() > maxLength) {
text = text.substring(0, maxLength) + "…";
}
return new SpannedString(text);
}

AbstractDraftyFormatter.TreeNode result = content.format(new PreviewFormatter(container));
if (result instanceof MeasuredTreeNode) {
try {
return ((MeasuredTreeNode) result).toSpanned(maxLength);
} catch (LengthExceededException ex) {
return new SpannableStringBuilder(ex.tail).append("…");
}
}

return new SpannedString("");
}

@Override
protected MeasuredTreeNode handleStrong(Object content) {
return new MeasuredTreeNode(new StyleSpan(Typeface.BOLD), content);
}

@Override
protected MeasuredTreeNode handleEmphasized(Object content) {
return new MeasuredTreeNode(new StyleSpan(Typeface.ITALIC), content);
}

@Override
protected MeasuredTreeNode handleDeleted(Object content) {
return new MeasuredTreeNode(new StrikethroughSpan(), content);
}

@Override
protected MeasuredTreeNode handleCode(Object content) {
return new MeasuredTreeNode(new TypefaceSpan("monospace"), content);
}

@Override
protected MeasuredTreeNode handleHidden(Object content) {
return null;
}

@Override
protected MeasuredTreeNode handleLineBreak() {
return null;
}

@Override
protected MeasuredTreeNode handleLink(Context ctx, Object content, Map<String, Object> data) {
return new MeasuredTreeNode(new ForegroundColorSpan(ctx.getResources().getColor(R.color.colorAccent)), content);
}

@Override
protected MeasuredTreeNode handleMention(Context ctx, Object content, Map<String, Object> data) {
return new MeasuredTreeNode(content);
}

PreviewFormatter(final TextView container, final int length) {
super(container, null);
mLength = length;
@Override
protected MeasuredTreeNode handleHashtag(Context ctx, Object content, Map<String, Object> data) {
return new MeasuredTreeNode(content);
}

public static Spanned toSpanned(final TextView container, final Drafty content) {
return toSpanned(container, content, null);
private MeasuredTreeNode annotatedIcon(Context ctx, @DrawableRes int iconId, @StringRes int stringId) {
MeasuredTreeNode node = null;
Drawable icon = AppCompatResources.getDrawable(ctx, iconId);
if (icon != null) {
icon.setTint(ctx.getResources().getColor(R.color.colorDarkGray));
icon.setBounds(0, 0, (int) mFontSize, (int) mFontSize);
node = new MeasuredTreeNode();
node.addNode(new ImageSpan(icon, ImageSpan.ALIGN_BOTTOM), " ");
node.addNode(" " + ctx.getResources().getString(stringId));
}
return node;
}

@Override
protected MeasuredTreeNode handleImage(Context ctx, Object content, Map<String, Object> data) {
return annotatedIcon(ctx, R.drawable.ic_image, R.string.picture);
}

@Override
protected MeasuredTreeNode handleAttachment(Context ctx, Map<String, Object> data) {
return annotatedIcon(ctx, R.drawable.ic_attach, R.string.attachment);
}

@Override
protected MeasuredTreeNode handleButton(Context ctx, Map<String, Object> data, Object content) {
MeasuredTreeNode node = new MeasuredTreeNode("[");
node.addNode(new MeasuredTreeNode(content));
node.addNode(new MeasuredTreeNode("]"));
return node;
}

@Override
protected MeasuredTreeNode handleFormRow(Context ctx, Map<String, Object> data, Object content) {
return new MeasuredTreeNode(content);
}

@Override
protected MeasuredTreeNode handleForm(Context ctx, Map<String, Object> data, Object content) {
MeasuredTreeNode node = annotatedIcon(ctx, R.drawable.ic_form, R.string.form);
node.addNode(new MeasuredTreeNode(": "));
node.addNode(new MeasuredTreeNode(content));
return node;
}

@Override
protected MeasuredTreeNode handleUnknown(Context ctx, Object content, Map<String, Object> data) {
return annotatedIcon(ctx, R.drawable.ic_unkn_type, R.string.unknown);
}

static class MeasuredTreeNode extends StyledTreeNode {
private static final String TAG = "MeasuredTreeNode";

MeasuredTreeNode() {
super();
}

MeasuredTreeNode(CharSequence content) {
super(content);
}

MeasuredTreeNode(Object content) {
super(content);
}

MeasuredTreeNode(CharacterStyle style, Object content) {
super(style, content);
}

protected Spanned toSpanned(final int maxLength) {
SpannableStringBuilder spanned = new SpannableStringBuilder();
boolean exceeded = false;

if (isPlain()) {
CharSequence text = getText();
if (text.length() > maxLength) {
text = text.subSequence(0, maxLength);
exceeded = true;
}
spanned.append(text);
} else if (hasChildren()) {
try {
for (AbstractDraftyFormatter.TreeNode child : getChildren()) {
if (child == null) {
Log.w(TAG, "NULL child. Should not happen!!!");
} else if (child instanceof MeasuredTreeNode) {
spanned.append(((MeasuredTreeNode) child).toSpanned(maxLength - spanned.length()));
} else {
Log.w(TAG, "Wrong child class: " + child.getClass().getSimpleName());
}
}
} catch (LengthExceededException ex) {
exceeded = true;
spanned.append(ex.tail);
}
}

if (spanned.length() > 0 && (cStyle != null || pStyle != null)) {
spanned.setSpan(cStyle != null ? cStyle : pStyle,
0, spanned.length(), Spanned.SPAN_EXCLUSIVE_EXCLUSIVE);
}

if (exceeded) {
throw new LengthExceededException(spanned);
}

return spanned;
}
}

public static boolean hasClickableSpans(final Drafty content) {
return false;
static class LengthExceededException extends RuntimeException {
private final Spanned tail;
LengthExceededException(Spanned tail) {
this.tail = tail;
}
}
}
Loading

0 comments on commit fcbda4c

Please sign in to comment.