commands tab in conversation
This commit is contained in:
parent
43870114d9
commit
f2012bc7f5
|
@ -91,6 +91,8 @@ dependencies {
|
|||
implementation 'com.splitwise:tokenautocomplete:3.0.2'
|
||||
|
||||
implementation 'com.github.kizitonwose.colorpreference:support:1.1.0'
|
||||
implementation 'com.caverock:androidsvg-aar:1.4'
|
||||
implementation 'com.github.singpolyma:Better-Link-Movement-Method:4df081e1e4'
|
||||
}
|
||||
|
||||
ext {
|
||||
|
|
|
@ -294,6 +294,13 @@ public class Contact implements ListItem, Blockable {
|
|||
return this.presences.getShownStatus();
|
||||
}
|
||||
|
||||
public Jid resourceWhichSupport(final String namespace) {
|
||||
final String resource = getPresences().firstWhichSupport(namespace);
|
||||
if (resource == null) return null;
|
||||
|
||||
return resource.equals("") ? getJid() : getJid().withResource(resource);
|
||||
}
|
||||
|
||||
public boolean setPhotoUri(String uri) {
|
||||
if (uri != null && !uri.equals(this.photoUri)) {
|
||||
this.photoUri = uri;
|
||||
|
|
File diff suppressed because it is too large
Load diff
|
@ -48,7 +48,7 @@ public class Presence implements Comparable<Presence> {
|
|||
private final String node;
|
||||
private final String message;
|
||||
|
||||
private Presence(Status status, String ver, String hash, String node, String message) {
|
||||
public Presence(Status status, String ver, String hash, String node, String message) {
|
||||
this.status = status;
|
||||
this.ver = ver;
|
||||
this.hash = hash;
|
||||
|
|
|
@ -149,6 +149,19 @@ public class Presences {
|
|||
return false;
|
||||
}
|
||||
|
||||
public String firstWhichSupport(final String namespace) {
|
||||
for (Map.Entry<String, Presence> entry : this.presences.entrySet()) {
|
||||
String resource = entry.getKey();
|
||||
Presence presence = entry.getValue();
|
||||
ServiceDiscoveryResult disco = presence.getServiceDiscoveryResult();
|
||||
if (disco != null && disco.getFeatures().contains(namespace)) {
|
||||
return resource;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean anyIdentity(final String category, final String type) {
|
||||
synchronized (this.presences) {
|
||||
if (this.presences.size() == 0) {
|
||||
|
|
|
@ -564,7 +564,14 @@ public class IqGenerator extends AbstractGenerator {
|
|||
public IqPacket queryDiscoItems(Jid jid) {
|
||||
IqPacket packet = new IqPacket(IqPacket.TYPE.GET);
|
||||
packet.setTo(jid);
|
||||
packet.addChild("query",Namespace.DISCO_ITEMS);
|
||||
packet.query(Namespace.DISCO_ITEMS);
|
||||
return packet;
|
||||
}
|
||||
|
||||
public IqPacket queryDiscoItems(Jid jid, String node) {
|
||||
IqPacket packet = queryDiscoItems(jid);
|
||||
final Element query = packet.query(Namespace.DISCO_ITEMS);
|
||||
query.setAttribute("node", node);
|
||||
return packet;
|
||||
}
|
||||
|
||||
|
|
|
@ -2,14 +2,19 @@ package eu.siacs.conversations.persistance;
|
|||
|
||||
import android.content.ContentResolver;
|
||||
import android.content.Context;
|
||||
import android.content.res.Resources;
|
||||
import android.database.Cursor;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.BitmapFactory;
|
||||
import android.graphics.Canvas;
|
||||
import android.graphics.Color;
|
||||
import android.graphics.ImageDecoder;
|
||||
import android.graphics.Matrix;
|
||||
import android.graphics.Paint;
|
||||
import android.graphics.Rect;
|
||||
import android.graphics.RectF;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.graphics.pdf.PdfRenderer;
|
||||
import android.media.MediaMetadataRetriever;
|
||||
import android.media.MediaScannerConnection;
|
||||
|
@ -32,6 +37,8 @@ import androidx.annotation.StringRes;
|
|||
import androidx.core.content.FileProvider;
|
||||
import androidx.exifinterface.media.ExifInterface;
|
||||
|
||||
import com.caverock.androidsvg.SVG;
|
||||
import com.caverock.androidsvg.SVGParseException;
|
||||
import com.google.common.base.Strings;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
@ -987,6 +994,108 @@ public class FileBackend {
|
|||
return thumbnail;
|
||||
}
|
||||
|
||||
public Drawable getThumbnail(DownloadableFile file, Resources res, int size, boolean cacheOnly) throws IOException {
|
||||
return getThumbnail(file, res, size, cacheOnly, file.getAbsolutePath());
|
||||
}
|
||||
|
||||
public Drawable getThumbnail(DownloadableFile file, Resources res, int size, boolean cacheOnly, String cacheKey) throws IOException {
|
||||
final LruCache<String, Drawable> cache = mXmppConnectionService.getDrawableCache();
|
||||
Drawable thumbnail = cache.get(cacheKey);
|
||||
if ((thumbnail == null) && (!cacheOnly) && file.exists()) {
|
||||
synchronized (THUMBNAIL_LOCK) {
|
||||
thumbnail = cache.get(cacheKey);
|
||||
if (thumbnail != null) {
|
||||
return thumbnail;
|
||||
}
|
||||
final String mime = file.getMimeType();
|
||||
if ("image/svg+xml".equals(mime)) {
|
||||
thumbnail = getSVG(file, size);
|
||||
} else if ("application/pdf".equals(mime)) {
|
||||
thumbnail = new BitmapDrawable(res, getPdfDocumentPreview(file, size));
|
||||
} else if (mime.startsWith("video/")) {
|
||||
thumbnail = new BitmapDrawable(res, getVideoPreview(file, size));
|
||||
} else {
|
||||
thumbnail = getImagePreview(file, res, size, mime);
|
||||
if (thumbnail == null) {
|
||||
throw new FileNotFoundException();
|
||||
}
|
||||
}
|
||||
if (cacheKey != null && thumbnail != null) cache.put(cacheKey, thumbnail);
|
||||
}
|
||||
}
|
||||
return thumbnail;
|
||||
}
|
||||
|
||||
public Drawable getSVG(File file, int size) {
|
||||
try {
|
||||
SVG svg = SVG.getFromInputStream(new FileInputStream(file));
|
||||
svg.setDocumentPreserveAspectRatio(com.caverock.androidsvg.PreserveAspectRatio.TOP);
|
||||
|
||||
float w = svg.getDocumentWidth();
|
||||
float h = svg.getDocumentHeight();
|
||||
Rect r = rectForSize((int) w, (int) h, size);
|
||||
svg.setDocumentWidth("100%");
|
||||
svg.setDocumentHeight("100%");
|
||||
|
||||
Bitmap output = Bitmap.createBitmap(r.width(), r.height(), Bitmap.Config.ARGB_8888);
|
||||
Canvas canvas = new Canvas(output);
|
||||
svg.renderToCanvas(canvas);
|
||||
|
||||
return new SVGDrawable(output);
|
||||
} catch (final IOException | SVGParseException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
public static Rect rectForSize(int w, int h, int size) {
|
||||
int scalledW;
|
||||
int scalledH;
|
||||
if (w <= h) {
|
||||
scalledW = Math.max((int) (w / ((double) h / size)), 1);
|
||||
scalledH = size;
|
||||
} else {
|
||||
scalledW = size;
|
||||
scalledH = Math.max((int) (h / ((double) w / size)), 1);
|
||||
}
|
||||
|
||||
if (scalledW > w || scalledH > h) return new Rect(0, 0, w, h);
|
||||
|
||||
return new Rect(0, 0, scalledW, scalledH);
|
||||
}
|
||||
|
||||
private Drawable getImagePreview(File file, Resources res, int size, final String mime) throws IOException {
|
||||
if (android.os.Build.VERSION.SDK_INT >= 28) {
|
||||
ImageDecoder.Source source = ImageDecoder.createSource(file);
|
||||
return ImageDecoder.decodeDrawable(source, (decoder, info, src) -> {
|
||||
int w = info.getSize().getWidth();
|
||||
int h = info.getSize().getHeight();
|
||||
Rect r = rectForSize(w, h, size);
|
||||
decoder.setTargetSize(r.width(), r.height());
|
||||
});
|
||||
} else {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inSampleSize = calcSampleSize(file, size);
|
||||
Bitmap bitmap = null;
|
||||
try {
|
||||
bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||
} catch (OutOfMemoryError e) {
|
||||
options.inSampleSize *= 2;
|
||||
bitmap = BitmapFactory.decodeFile(file.getAbsolutePath(), options);
|
||||
}
|
||||
if (bitmap == null) return null;
|
||||
|
||||
bitmap = resize(bitmap, size);
|
||||
bitmap = rotate(bitmap, getRotation(file));
|
||||
if (mime.equals("image/gif")) {
|
||||
Bitmap withGifOverlay = bitmap.copy(Bitmap.Config.ARGB_8888, true);
|
||||
drawOverlay(withGifOverlay, paintOverlayBlack(withGifOverlay) ? R.drawable.play_gif_black : R.drawable.play_gif_white, 1.0f);
|
||||
bitmap.recycle();
|
||||
bitmap = withGifOverlay;
|
||||
}
|
||||
return new BitmapDrawable(res, bitmap);
|
||||
}
|
||||
}
|
||||
|
||||
private Bitmap getFullSizeImagePreview(File file, int size) {
|
||||
BitmapFactory.Options options = new BitmapFactory.Options();
|
||||
options.inSampleSize = calcSampleSize(file, size);
|
||||
|
@ -1700,4 +1809,8 @@ public class FileBackend {
|
|||
return resId;
|
||||
}
|
||||
}
|
||||
|
||||
public static class SVGDrawable extends BitmapDrawable {
|
||||
public SVGDrawable(Bitmap bm) { super(bm); }
|
||||
}
|
||||
}
|
||||
|
|
|
@ -21,6 +21,8 @@ import android.content.SharedPreferences;
|
|||
import android.content.pm.PackageManager;
|
||||
import android.database.ContentObserver;
|
||||
import android.graphics.Bitmap;
|
||||
import android.graphics.drawable.BitmapDrawable;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.media.AudioManager;
|
||||
import android.net.ConnectivityManager;
|
||||
import android.net.Network;
|
||||
|
@ -489,6 +491,7 @@ public class XmppConnectionService extends Service {
|
|||
private PgpEngine mPgpEngine = null;
|
||||
private WakeLock wakeLock;
|
||||
private LruCache<String, Bitmap> mBitmapCache;
|
||||
private LruCache<String, Drawable> mDrawableCache;
|
||||
private final BroadcastReceiver mInternalEventReceiver = new InternalEventReceiver();
|
||||
private final BroadcastReceiver mInternalScreenEventReceiver = new InternalEventReceiver();
|
||||
|
||||
|
@ -1187,6 +1190,21 @@ public class XmppConnectionService extends Service {
|
|||
return bitmap.getByteCount() / 1024;
|
||||
}
|
||||
};
|
||||
|
||||
this.mDrawableCache = new LruCache<String, Drawable>(cacheSize) {
|
||||
@Override
|
||||
protected int sizeOf(final String key, final Drawable drawable) {
|
||||
if (drawable instanceof BitmapDrawable) {
|
||||
Bitmap bitmap = ((BitmapDrawable) drawable).getBitmap();
|
||||
if (bitmap == null) return 1024;
|
||||
|
||||
return bitmap.getByteCount() / 1024;
|
||||
} else {
|
||||
return drawable.getIntrinsicWidth() * drawable.getIntrinsicHeight() * 40 / 1024;
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
if (mLastActivity == 0) {
|
||||
mLastActivity = getPreferences().getLong(SETTING_LAST_ACTIVITY_TS, System.currentTimeMillis());
|
||||
}
|
||||
|
@ -4285,7 +4303,7 @@ public class XmppConnectionService extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
private SharedPreferences getPreferences() {
|
||||
public SharedPreferences getPreferences() {
|
||||
return PreferenceManager.getDefaultSharedPreferences(getApplicationContext());
|
||||
}
|
||||
|
||||
|
@ -4536,6 +4554,10 @@ public class XmppConnectionService extends Service {
|
|||
return this.mBitmapCache;
|
||||
}
|
||||
|
||||
public LruCache<String, Drawable> getDrawableCache() {
|
||||
return this.mDrawableCache;
|
||||
}
|
||||
|
||||
public Collection<String> getKnownHosts() {
|
||||
final Set<String> hosts = new HashSet<>();
|
||||
for (final Account account : getAccounts()) {
|
||||
|
@ -4601,9 +4623,13 @@ public class XmppConnectionService extends Service {
|
|||
}
|
||||
|
||||
public void sendIqPacket(final Account account, final IqPacket packet, final OnIqPacketReceived callback) {
|
||||
sendIqPacket(account, packet, callback, null);
|
||||
}
|
||||
|
||||
public void sendIqPacket(final Account account, final IqPacket packet, final OnIqPacketReceived callback, Long timeout) {
|
||||
final XmppConnection connection = account.getXmppConnection();
|
||||
if (connection != null) {
|
||||
connection.sendIqPacket(packet, callback);
|
||||
connection.sendIqPacket(packet, callback, timeout);
|
||||
} else if (callback != null) {
|
||||
callback.onIqPacketReceived(account, new IqPacket(IqPacket.TYPE.TIMEOUT));
|
||||
}
|
||||
|
@ -4855,8 +4881,13 @@ public class XmppConnectionService extends Service {
|
|||
}
|
||||
|
||||
public void fetchCaps(Account account, final Jid jid, final Presence presence) {
|
||||
final Pair<String, String> key = new Pair<>(presence.getHash(), presence.getVer());
|
||||
final ServiceDiscoveryResult disco = getCachedServiceDiscoveryResult(key);
|
||||
fetchCaps(account, jid, presence, null);
|
||||
}
|
||||
|
||||
public void fetchCaps(Account account, final Jid jid, final Presence presence, Runnable cb) {
|
||||
final Pair<String, String> key = presence == null ? null : new Pair<>(presence.getHash(), presence.getVer());
|
||||
final ServiceDiscoveryResult disco = key == null ? null : getCachedServiceDiscoveryResult(key);
|
||||
|
||||
if (disco != null) {
|
||||
presence.setServiceDiscoveryResult(disco);
|
||||
final Contact contact = account.getRoster().getContact(jid);
|
||||
|
@ -4866,19 +4897,21 @@ public class XmppConnectionService extends Service {
|
|||
} else {
|
||||
final IqPacket request = new IqPacket(IqPacket.TYPE.GET);
|
||||
request.setTo(jid);
|
||||
final String node = presence.getNode();
|
||||
final String ver = presence.getVer();
|
||||
final String node = presence == null ? null : presence.getNode();
|
||||
final String ver = presence == null ? null : presence.getVer();
|
||||
final Element query = request.query(Namespace.DISCO_INFO);
|
||||
if (node != null && ver != null) {
|
||||
query.setAttribute("node", node + "#" + ver);
|
||||
}
|
||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": making disco request for " + key.second + " to " + jid);
|
||||
Log.d(Config.LOGTAG, account.getJid().asBareJid() + ": making disco request for " + (key == null ? "" : key.second) + " to " + jid);
|
||||
sendIqPacket(account, request, (a, response) -> {
|
||||
if (response.getType() == IqPacket.TYPE.RESULT) {
|
||||
final ServiceDiscoveryResult discoveryResult = new ServiceDiscoveryResult(response);
|
||||
if (presence.getVer().equals(discoveryResult.getVer())) {
|
||||
if (presence == null || presence.getVer() == null || presence.getVer().equals(discoveryResult.getVer())) {
|
||||
databaseBackend.insertDiscoveryResult(discoveryResult);
|
||||
injectServiceDiscoveryResult(a.getRoster(), presence.getHash(), presence.getVer(), discoveryResult);
|
||||
injectServiceDiscoveryResult(a.getRoster(), presence == null ? null : presence.getHash(), presence == null ? null : presence.getVer(), jid.getResource(), discoveryResult);
|
||||
|
||||
if (cb != null) cb.run();
|
||||
} else {
|
||||
Log.d(Config.LOGTAG, a.getJid().asBareJid() + ": mismatch in caps for contact " + jid + " " + presence.getVer() + " vs " + discoveryResult.getVer());
|
||||
}
|
||||
|
@ -4889,16 +4922,32 @@ public class XmppConnectionService extends Service {
|
|||
}
|
||||
}
|
||||
|
||||
private void injectServiceDiscoveryResult(Roster roster, String hash, String ver, ServiceDiscoveryResult disco) {
|
||||
public void fetchCommands(Account account, final Jid jid, OnIqPacketReceived callback) {
|
||||
final IqPacket request = mIqGenerator.queryDiscoItems(jid, "http://jabber.org/protocol/commands");
|
||||
sendIqPacket(account, request, callback, 5000L);
|
||||
}
|
||||
private void injectServiceDiscoveryResult(Roster roster, String hash, String ver, String resource, ServiceDiscoveryResult disco) {
|
||||
boolean rosterNeedsSync = false;
|
||||
for (final Contact contact : roster.getContacts()) {
|
||||
boolean serviceDiscoverySet = false;
|
||||
Presence onePresence = contact.getPresences().get(resource == null ? "" : resource);
|
||||
if (onePresence != null) {
|
||||
onePresence.setServiceDiscoveryResult(disco);
|
||||
serviceDiscoverySet = true;
|
||||
} else if (resource == null && hash == null && ver == null) {
|
||||
Presence p = new Presence(Presence.Status.OFFLINE, null, null, null, "");
|
||||
p.setServiceDiscoveryResult(disco);
|
||||
contact.updatePresence("", p);
|
||||
serviceDiscoverySet = true;
|
||||
}
|
||||
if (hash != null && ver != null) {
|
||||
for (final Presence presence : contact.getPresences().getPresences()) {
|
||||
if (hash.equals(presence.getHash()) && ver.equals(presence.getVer())) {
|
||||
presence.setServiceDiscoveryResult(disco);
|
||||
serviceDiscoverySet = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (serviceDiscoverySet) {
|
||||
rosterNeedsSync |= contact.refreshRtpCapability();
|
||||
}
|
||||
|
|
|
@ -49,6 +49,7 @@ import android.view.MotionEvent;
|
|||
import android.view.View;
|
||||
import android.view.View.OnClickListener;
|
||||
import android.view.ViewGroup;
|
||||
import android.view.ViewParent;
|
||||
import android.view.inputmethod.EditorInfo;
|
||||
import android.view.inputmethod.InputMethodManager;
|
||||
import android.widget.AbsListView;
|
||||
|
@ -70,12 +71,14 @@ import androidx.appcompat.content.res.AppCompatResources;
|
|||
import androidx.core.view.inputmethod.InputConnectionCompat;
|
||||
import androidx.core.view.inputmethod.InputContentInfoCompat;
|
||||
import androidx.databinding.DataBindingUtil;
|
||||
import androidx.viewpager.widget.PagerAdapter;
|
||||
|
||||
import com.google.common.base.Optional;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
|
||||
import java.util.AbstractMap;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
@ -84,6 +87,7 @@ import java.util.Comparator;
|
|||
import java.util.HashSet;
|
||||
import java.util.Iterator;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
@ -112,6 +116,7 @@ import eu.siacs.conversations.persistance.FileBackend;
|
|||
import eu.siacs.conversations.services.MessageArchiveService;
|
||||
import eu.siacs.conversations.services.QuickConversationsService;
|
||||
import eu.siacs.conversations.services.XmppConnectionService;
|
||||
import eu.siacs.conversations.ui.adapter.CommandAdapter;
|
||||
import eu.siacs.conversations.ui.adapter.MediaPreviewAdapter;
|
||||
import eu.siacs.conversations.ui.adapter.MessageAdapter;
|
||||
import eu.siacs.conversations.ui.util.ActivityResult;
|
||||
|
@ -130,6 +135,7 @@ import eu.siacs.conversations.ui.util.SendButtonTool;
|
|||
import eu.siacs.conversations.ui.util.ShareUtil;
|
||||
import eu.siacs.conversations.ui.util.ViewUtil;
|
||||
import eu.siacs.conversations.ui.widget.EditMessage;
|
||||
import eu.siacs.conversations.ui.widget.TabLayout;
|
||||
import eu.siacs.conversations.utils.AccountUtils;
|
||||
import eu.siacs.conversations.utils.Compatibility;
|
||||
import eu.siacs.conversations.utils.Emoticons;
|
||||
|
@ -141,6 +147,7 @@ import eu.siacs.conversations.utils.QuickLoader;
|
|||
import eu.siacs.conversations.utils.StylingHelper;
|
||||
import eu.siacs.conversations.utils.TimeFrameUtils;
|
||||
import eu.siacs.conversations.utils.UIHelper;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
import eu.siacs.conversations.xml.Namespace;
|
||||
import eu.siacs.conversations.xmpp.Jid;
|
||||
import eu.siacs.conversations.xmpp.XmppConnection;
|
||||
|
@ -151,6 +158,7 @@ import eu.siacs.conversations.xmpp.jingle.JingleFileTransferConnection;
|
|||
import eu.siacs.conversations.xmpp.jingle.Media;
|
||||
import eu.siacs.conversations.xmpp.jingle.OngoingRtpSession;
|
||||
import eu.siacs.conversations.xmpp.jingle.RtpCapability;
|
||||
import eu.siacs.conversations.xmpp.stanzas.IqPacket;
|
||||
|
||||
public class ConversationFragment extends XmppFragment
|
||||
implements EditMessage.KeyboardListener,
|
||||
|
@ -199,6 +207,7 @@ public class ConversationFragment extends XmppFragment
|
|||
public Uri mPendingEditorContent = null;
|
||||
protected MessageAdapter messageListAdapter;
|
||||
private MediaPreviewAdapter mediaPreviewAdapter;
|
||||
protected CommandAdapter commandAdapter;
|
||||
private String lastMessageUuid = null;
|
||||
private Conversation conversation;
|
||||
private FragmentConversationBinding binding;
|
||||
|
@ -650,6 +659,13 @@ public class ConversationFragment extends XmppFragment
|
|||
}
|
||||
};
|
||||
|
||||
private TabLayout.VisibilityChangeListener visibiltyChangeListener = new TabLayout.VisibilityChangeListener() {
|
||||
@Override
|
||||
public void onVisibilityChanged(int visibility) {
|
||||
applyTabElevationFix(visibility == View.VISIBLE);
|
||||
}
|
||||
};
|
||||
|
||||
private int completionIndex = 0;
|
||||
private int lastCompletionLength = 0;
|
||||
private String incomplete;
|
||||
|
@ -1385,6 +1401,8 @@ public class ConversationFragment extends XmppFragment
|
|||
new EditMessageActionModeCallback(this.binding.textinput));
|
||||
}
|
||||
|
||||
binding.tabLayout.setListener(visibiltyChangeListener);
|
||||
|
||||
return binding.getRoot();
|
||||
}
|
||||
|
||||
|
@ -1394,6 +1412,16 @@ public class ConversationFragment extends XmppFragment
|
|||
Log.d(Config.LOGTAG, "ConversationFragment.onDestroyView()");
|
||||
messageListAdapter.setOnContactPictureClicked(null);
|
||||
messageListAdapter.setOnContactPictureLongClicked(null);
|
||||
binding.conversationViewPager.setAdapter(null);
|
||||
if (conversation != null) conversation.setupViewPager(null, null, null);
|
||||
binding.tabLayout.setListener(null);
|
||||
applyTabElevationFix(false);
|
||||
}
|
||||
|
||||
private void applyTabElevationFix(boolean shouldElevate) {
|
||||
View fragmentHostView = activity.getFragmentHostView();
|
||||
if (fragmentHostView == null) return;
|
||||
fragmentHostView. setElevation(shouldElevate ? activity.getResources().getDimension(R.dimen.toolbar_elevation) : 0);
|
||||
}
|
||||
|
||||
private void quoteText(String text) {
|
||||
|
@ -1699,12 +1727,33 @@ public class ConversationFragment extends XmppFragment
|
|||
case R.id.action_toggle_pinned:
|
||||
togglePinned();
|
||||
break;
|
||||
case R.id.action_refresh_feature_discovery:
|
||||
refreshFeatureDiscovery();
|
||||
default:
|
||||
break;
|
||||
}
|
||||
return super.onOptionsItemSelected(item);
|
||||
}
|
||||
|
||||
private void refreshFeatureDiscovery() {
|
||||
Set<Map.Entry<String, Presence>> presences = conversation.getContact().getPresences().getPresencesMap().entrySet();
|
||||
if (presences.isEmpty()) {
|
||||
presences = new HashSet<>();
|
||||
presences.add(new AbstractMap.SimpleEntry("", null));
|
||||
}
|
||||
for (Map.Entry<String, Presence> entry : presences) {
|
||||
Jid jid = conversation.getContact().getJid();
|
||||
if (!entry.getKey().equals("")) jid = jid.withResource(entry.getKey());
|
||||
activity.xmppConnectionService.fetchCaps(conversation.getAccount(), jid, entry.getValue(), () -> {
|
||||
if (activity == null) return;
|
||||
activity.runOnUiThread(() -> {
|
||||
refresh();
|
||||
refreshCommands(true);
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void startSearch() {
|
||||
final Intent intent = new Intent(getActivity(), SearchActivity.class);
|
||||
intent.putExtra(SearchActivity.EXTRA_CONVERSATION_UUID, conversation.getUuid());
|
||||
|
@ -2615,6 +2664,8 @@ public class ConversationFragment extends XmppFragment
|
|||
if (conversation == null) {
|
||||
return false;
|
||||
}
|
||||
|
||||
final Conversation originalConversation = this.conversation;
|
||||
this.conversation = conversation;
|
||||
// once we set the conversation all is good and it will automatically do the right thing in
|
||||
// onStart()
|
||||
|
@ -2687,9 +2738,65 @@ public class ConversationFragment extends XmppFragment
|
|||
activity.xmppConnectionService
|
||||
.getNotificationService()
|
||||
.setOpenConversation(this.conversation);
|
||||
|
||||
if (commandAdapter != null && conversation != originalConversation) {
|
||||
conversation.setupViewPager(binding.conversationViewPager, binding.tabLayout, originalConversation);
|
||||
refreshCommands(false);
|
||||
}
|
||||
if (commandAdapter == null && conversation != null) {
|
||||
conversation.setupViewPager(binding.conversationViewPager, binding.tabLayout, null);
|
||||
commandAdapter = new CommandAdapter((XmppActivity) getActivity());
|
||||
binding.commandsView.setAdapter(commandAdapter);
|
||||
binding.commandsView.setOnItemClickListener((parent, view, position, id) -> {
|
||||
if (activity == null) return;
|
||||
|
||||
final Element command = commandAdapter.getItem(position);
|
||||
activity.startCommand(conversation.getAccount(), command.getAttributeAsJid("jid"), command.getAttribute("node"));
|
||||
});
|
||||
refreshCommands(false);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
public void refreshForNewCaps() {
|
||||
refreshCommands(true);
|
||||
}
|
||||
|
||||
protected void refreshCommands(boolean delayShow) {
|
||||
if (commandAdapter == null) return;
|
||||
|
||||
Jid commandJid = conversation.getContact().resourceWhichSupport(Namespace.COMMANDS);
|
||||
if (commandJid == null && conversation.getJid().isDomainJid()) {
|
||||
commandJid = conversation.getJid();
|
||||
}
|
||||
if (commandJid == null) {
|
||||
conversation.hideViewPager();
|
||||
} else {
|
||||
if (!delayShow) conversation.showViewPager();
|
||||
activity.xmppConnectionService.fetchCommands(conversation.getAccount(), commandJid, (a, iq) -> {
|
||||
if (activity == null) return;
|
||||
|
||||
activity.runOnUiThread(() -> {
|
||||
if (iq.getType() == IqPacket.TYPE.RESULT) {
|
||||
binding.commandsViewProgressbar.setVisibility(View.GONE);
|
||||
commandAdapter.clear();
|
||||
for (Element child : iq.query().getChildren()) {
|
||||
if (!"item".equals(child.getName()) || !Namespace.DISCO_ITEMS.equals(child.getNamespace())) continue;
|
||||
commandAdapter.add(child);
|
||||
}
|
||||
}
|
||||
|
||||
if (commandAdapter.getCount() < 1) {
|
||||
conversation.hideViewPager();
|
||||
} else if (delayShow) {
|
||||
conversation.showViewPager();
|
||||
}
|
||||
});
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
private void resetUnreadMessagesCount() {
|
||||
lastMessageUuid = null;
|
||||
hideUnreadMessagesCount();
|
||||
|
@ -2719,6 +2826,7 @@ public class ConversationFragment extends XmppFragment
|
|||
final String downloadUuid = extras.getString(ConversationsActivity.EXTRA_DOWNLOAD_UUID);
|
||||
final String text = extras.getString(Intent.EXTRA_TEXT);
|
||||
final String nick = extras.getString(ConversationsActivity.EXTRA_NICK);
|
||||
final String node = extras.getString(ConversationsActivity.EXTRA_NODE);
|
||||
final String postInitAction =
|
||||
extras.getString(ConversationsActivity.EXTRA_POST_INIT_ACTION);
|
||||
final boolean asQuote = extras.getBoolean(ConversationsActivity.EXTRA_AS_QUOTE);
|
||||
|
@ -2770,6 +2878,36 @@ public class ConversationFragment extends XmppFragment
|
|||
attachFile(ATTACHMENT_CHOICE_RECORD_VOICE, false);
|
||||
return;
|
||||
}
|
||||
|
||||
if ("message".equals(postInitAction)) {
|
||||
binding.conversationViewPager.post(() -> {
|
||||
binding.conversationViewPager.setCurrentItem(0);
|
||||
});
|
||||
}
|
||||
if ("command".equals(postInitAction)) {
|
||||
binding.conversationViewPager.post(() -> {
|
||||
PagerAdapter adapter = binding.conversationViewPager.getAdapter();
|
||||
if (adapter != null && adapter.getCount() > 1) {
|
||||
binding.conversationViewPager.setCurrentItem(1);
|
||||
}
|
||||
final String jid = extras.getString(ConversationsActivity.EXTRA_JID);
|
||||
Jid commandJid = null;
|
||||
if (jid != null) {
|
||||
try {
|
||||
commandJid = Jid.of(jid);
|
||||
} catch (final IllegalArgumentException e) { }
|
||||
}
|
||||
if (commandJid == null || !commandJid.isFullJid()) {
|
||||
final Jid discoJid = conversation.getContact().resourceWhichSupport(Namespace.COMMANDS);
|
||||
if (discoJid != null) commandJid = discoJid;
|
||||
}
|
||||
if (node != null && commandJid != null) {
|
||||
conversation.startCommand(commandFor(commandJid, node), activity.xmppConnectionService, activity);
|
||||
}
|
||||
});
|
||||
return;
|
||||
}
|
||||
|
||||
final Message message =
|
||||
downloadUuid == null ? null : conversation.findMessageWithFileAndUuid(downloadUuid);
|
||||
if (message != null) {
|
||||
|
@ -2777,6 +2915,23 @@ public class ConversationFragment extends XmppFragment
|
|||
}
|
||||
}
|
||||
|
||||
private Element commandFor(final Jid jid, final String node) {
|
||||
if (commandAdapter != null) {
|
||||
for (int i = 0; i < commandAdapter.getCount(); i++) {
|
||||
Element command = commandAdapter.getItem(i);
|
||||
final String commandNode = command.getAttribute("node");
|
||||
if (commandNode == null || !commandNode.equals(node)) continue;
|
||||
|
||||
final Jid commandJid = command.getAttributeAsJid("jid");
|
||||
if (commandJid != null && !commandJid.asBareJid().equals(jid.asBareJid())) continue;
|
||||
|
||||
return command;
|
||||
}
|
||||
}
|
||||
|
||||
return new Element("command", Namespace.COMMANDS).setAttribute("name", node).setAttribute("node", node).setAttribute("jid", jid);
|
||||
}
|
||||
|
||||
private List<Uri> extractUris(final Bundle extras) {
|
||||
final List<Uri> uris = extras.getParcelableArrayList(Intent.EXTRA_STREAM);
|
||||
if (uris != null) {
|
||||
|
@ -2998,6 +3153,7 @@ public class ConversationFragment extends XmppFragment
|
|||
}
|
||||
updateSendButton();
|
||||
updateEditablity();
|
||||
conversation.refreshSessions();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -50,10 +50,12 @@ import android.util.Log;
|
|||
import android.view.KeyEvent;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuItem;
|
||||
import android.view.View;
|
||||
import android.widget.Toast;
|
||||
|
||||
import androidx.annotation.IdRes;
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.appcompat.app.ActionBar;
|
||||
import androidx.appcompat.app.AlertDialog;
|
||||
import androidx.core.app.ActivityCompat;
|
||||
|
@ -62,7 +64,9 @@ import androidx.databinding.DataBindingUtil;
|
|||
import org.openintents.openpgp.util.OpenPgpApi;
|
||||
|
||||
import java.util.Arrays;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
|
@ -85,10 +89,12 @@ import eu.siacs.conversations.ui.util.ConversationMenuConfigurator;
|
|||
import eu.siacs.conversations.ui.util.MenuDoubleTabUtil;
|
||||
import eu.siacs.conversations.ui.util.PendingItem;
|
||||
import eu.siacs.conversations.utils.ExceptionHelper;
|
||||
import eu.siacs.conversations.utils.PhoneNumberUtilWrapper;
|
||||
import eu.siacs.conversations.utils.SignupUtils;
|
||||
import eu.siacs.conversations.utils.XmppUri;
|
||||
import eu.siacs.conversations.xmpp.Jid;
|
||||
import eu.siacs.conversations.xmpp.OnUpdateBlocklist;
|
||||
import io.michaelrocks.libphonenumber.android.NumberParseException;
|
||||
|
||||
public class ConversationsActivity extends XmppActivity implements OnConversationSelected, OnConversationArchived, OnConversationsListItemUpdated, OnConversationRead, XmppConnectionService.OnAccountUpdate, XmppConnectionService.OnConversationUpdate, XmppConnectionService.OnRosterUpdate, OnUpdateBlocklist, XmppConnectionService.OnShowErrorToast, XmppConnectionService.OnAffiliationChanged {
|
||||
|
||||
|
@ -102,6 +108,8 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
|||
public static final String EXTRA_POST_INIT_ACTION = "post_init_action";
|
||||
public static final String POST_ACTION_RECORD_VOICE = "record_voice";
|
||||
public static final String EXTRA_TYPE = "type";
|
||||
public static final String EXTRA_NODE = "node";
|
||||
public static final String EXTRA_JID = "jid";
|
||||
|
||||
private static final List<String> VIEW_AND_SHARE_ACTIONS = Arrays.asList(
|
||||
ACTION_VIEW_CONVERSATION,
|
||||
|
@ -268,6 +276,7 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
|||
final Fragment fragment = getFragmentManager().findFragmentById(id);
|
||||
if (fragment instanceof XmppFragment) {
|
||||
((XmppFragment) fragment).refresh();
|
||||
// if (refreshForNewCaps) ((XmppFragment) fragment).refreshForNewCaps();
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -420,6 +429,15 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
|||
}
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public View getFragmentHostView() {
|
||||
if (binding.secondaryFragment != null) {
|
||||
return binding.secondaryFragment;
|
||||
} else {
|
||||
return binding.mainFragment;
|
||||
}
|
||||
}
|
||||
|
||||
private void displayToast(final String msg) {
|
||||
runOnUiThread(() -> Toast.makeText(ConversationsActivity.this, msg, Toast.LENGTH_SHORT).show());
|
||||
}
|
||||
|
@ -481,13 +499,50 @@ public class ConversationsActivity extends XmppActivity implements OnConversatio
|
|||
if (xmppUri.isValidJid() && !xmppUri.hasFingerprints()) {
|
||||
final Conversation conversation = xmppConnectionService.findUniqueConversationByJid(xmppUri);
|
||||
if (conversation != null) {
|
||||
openConversation(conversation, null);
|
||||
if (xmppUri.isAction("command")) {
|
||||
startCommand(conversation.getAccount(), xmppUri.getJid(), xmppUri.getParameter("node"));
|
||||
} else {
|
||||
Bundle extras = new Bundle();
|
||||
extras.putString(Intent.EXTRA_TEXT, xmppUri.getBody());
|
||||
if (xmppUri.isAction("message")) extras.putString(EXTRA_POST_INIT_ACTION, "message");
|
||||
openConversation(conversation, extras);
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public boolean onTelUriClicked(Uri uri, Account acct) {
|
||||
final String tel;
|
||||
try {
|
||||
tel = PhoneNumberUtilWrapper.normalize(this, uri.getSchemeSpecificPart());
|
||||
} catch (final IllegalArgumentException | NumberParseException | NullPointerException e) {
|
||||
return false;
|
||||
}
|
||||
|
||||
Set<String> gateways = new HashSet<>();
|
||||
for (Account account : (acct == null ? xmppConnectionService.getAccounts() : List.of(acct))) {
|
||||
for (Contact contact : account.getRoster().getContacts()) {
|
||||
if (contact.getPresences().anyIdentity("gateway", "pstn") || contact.getPresences().anyIdentity("gateway", "sms")) {
|
||||
if (acct == null) acct = account;
|
||||
gateways.add(contact.getJid().asBareJid().toEscapedString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
for (String gateway : gateways) {
|
||||
if (onXmppUriClicked(Uri.parse("xmpp:" + tel + "@" + gateway))) return true;
|
||||
}
|
||||
|
||||
if (gateways.size() == 1 && acct != null) {
|
||||
openConversation(xmppConnectionService.findOrCreateConversation(acct, Jid.ofLocalAndDomain(tel, gateways.iterator().next()), null, false, false, true, null), null);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onOptionsItemSelected(MenuItem item) {
|
||||
if (MenuDoubleTabUtil.shouldIgnoreTap()) {
|
||||
|
|
|
@ -546,6 +546,17 @@ public abstract class XmppActivity extends ActionBarActivity {
|
|||
return getPreferences().getBoolean(name, getResources().getBoolean(res));
|
||||
}
|
||||
|
||||
public void startCommand(final Account account, final Jid jid, final String node) {
|
||||
Intent intent = new Intent(this, ConversationsActivity.class);
|
||||
intent.setAction(ConversationsActivity.ACTION_VIEW_CONVERSATION);
|
||||
intent.putExtra(ConversationsActivity.EXTRA_CONVERSATION, xmppConnectionService.findOrCreateConversation(account, jid, null, false, false, false, null).getUuid());
|
||||
intent.putExtra(ConversationsActivity.EXTRA_POST_INIT_ACTION, "command");
|
||||
intent.putExtra(ConversationsActivity.EXTRA_NODE, node);
|
||||
intent.putExtra(ConversationsActivity.EXTRA_JID, (CharSequence) jid);
|
||||
intent.setFlags(intent.getFlags() | Intent.FLAG_ACTIVITY_CLEAR_TOP);
|
||||
startActivity(intent);
|
||||
}
|
||||
|
||||
public void switchToConversation(Conversation conversation) {
|
||||
switchToConversation(conversation, null);
|
||||
}
|
||||
|
|
|
@ -38,6 +38,8 @@ public abstract class XmppFragment extends Fragment implements OnBackendConnecte
|
|||
|
||||
abstract void refresh();
|
||||
|
||||
public void refreshForNewCaps() { }
|
||||
|
||||
protected void runOnUiThread(Runnable runnable) {
|
||||
final Activity activity = getActivity();
|
||||
if (activity != null) {
|
||||
|
|
|
@ -0,0 +1,27 @@
|
|||
package eu.siacs.conversations.ui.adapter;
|
||||
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.view.ViewGroup;
|
||||
import android.widget.ArrayAdapter;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.databinding.DataBindingUtil;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.databinding.CommandRowBinding;
|
||||
import eu.siacs.conversations.ui.XmppActivity;
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
|
||||
public class CommandAdapter extends ArrayAdapter<Element> {
|
||||
public CommandAdapter(XmppActivity activity) {
|
||||
super(activity, 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public View getView(int position, View view, @NonNull ViewGroup parent) {
|
||||
CommandRowBinding binding = DataBindingUtil.inflate(LayoutInflater.from(parent.getContext()), R.layout.command_row, parent, false);
|
||||
binding.command.setText(getItem(position).getAttribute("name"));
|
||||
return binding.getRoot();
|
||||
}
|
||||
}
|
|
@ -44,14 +44,21 @@ import android.widget.Toast;
|
|||
import java.util.Arrays;
|
||||
|
||||
import eu.siacs.conversations.R;
|
||||
import eu.siacs.conversations.entities.Account;
|
||||
import eu.siacs.conversations.ui.ConversationsActivity;
|
||||
|
||||
|
||||
@SuppressLint("ParcelCreator")
|
||||
public class FixedURLSpan extends URLSpan {
|
||||
|
||||
private FixedURLSpan(String url) {
|
||||
protected final Account account;
|
||||
|
||||
public FixedURLSpan(String url) {
|
||||
this(url, null);
|
||||
}
|
||||
|
||||
public FixedURLSpan(String url, Account account) {
|
||||
super(url);
|
||||
this.account = account;
|
||||
}
|
||||
|
||||
public static void fix(final Editable editable) {
|
||||
|
@ -74,6 +81,14 @@ public class FixedURLSpan extends URLSpan {
|
|||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (("sms".equals(uri.getScheme()) || "tel".equals(uri.getScheme())) && context instanceof ConversationsActivity) {
|
||||
if (((ConversationsActivity) context).onTelUriClicked(uri, account)) {
|
||||
widget.playSoundEffect(0);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
final Intent intent = new Intent(Intent.ACTION_VIEW, uri);
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP) {
|
||||
intent.setFlags(Intent.FLAG_ACTIVITY_NEW_DOCUMENT);
|
||||
|
|
|
@ -30,6 +30,9 @@
|
|||
package eu.siacs.conversations.ui.util;
|
||||
|
||||
import android.content.ActivityNotFoundException;
|
||||
import android.content.ClipData;
|
||||
import android.content.ClipboardManager;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.net.Uri;
|
||||
import android.text.SpannableStringBuilder;
|
||||
|
@ -185,6 +188,33 @@ public class ShareUtil {
|
|||
}
|
||||
}
|
||||
|
||||
public static void copyLinkToClipboard(final Context context, final String url) {
|
||||
final Uri uri = Uri.parse(url);
|
||||
if ("xmpp".equals(uri.getScheme())) {
|
||||
try {
|
||||
final Jid jid = new XmppUri(uri).getJid();
|
||||
if (copyTextToClipboard(context, jid.asBareJid().toString(), R.string.account_settings_jabber_id)) {
|
||||
Toast.makeText(context, R.string.jabber_id_copied_to_clipboard, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
} catch (final Exception e) { }
|
||||
} else {
|
||||
if (copyTextToClipboard(context, url, R.string.web_address)) {
|
||||
Toast.makeText(context, R.string.url_copied_to_clipboard, Toast.LENGTH_SHORT).show();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public static boolean copyTextToClipboard(Context context, String text, int labelResId) {
|
||||
ClipboardManager mClipBoardManager = (ClipboardManager) context.getSystemService(Context.CLIPBOARD_SERVICE);
|
||||
String label = context.getResources().getString(labelResId);
|
||||
if (mClipBoardManager != null) {
|
||||
ClipData mClipData = ClipData.newPlainText(label, text);
|
||||
mClipBoardManager.setPrimaryClip(mClipData);
|
||||
return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public static void copyLinkToClipboard(final XmppActivity activity, final Message message) {
|
||||
final SpannableStringBuilder body = message.getBodyForDisplaying();
|
||||
for (final String url : MyLinkify.extractLinks(body)) {
|
||||
|
|
37
src/main/java/eu/siacs/conversations/ui/widget/GridView.java
Normal file
37
src/main/java/eu/siacs/conversations/ui/widget/GridView.java
Normal file
|
@ -0,0 +1,37 @@
|
|||
package eu.siacs.conversations.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
public class GridView extends android.widget.GridView {
|
||||
public GridView(Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public GridView(Context context, AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public GridView(Context context, AttributeSet attrs, int defStyle) {
|
||||
super(context, attrs, defStyle);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int heightSpec;
|
||||
|
||||
if (getLayoutParams().height == LayoutParams.WRAP_CONTENT) {
|
||||
// The great Android "hackatlon", the love, the magic.
|
||||
// The two leftmost bits in the height measure spec have
|
||||
// a special meaning, hence we can't use them to describe height.
|
||||
heightSpec = MeasureSpec.makeMeasureSpec(
|
||||
Integer.MAX_VALUE >> 2, MeasureSpec.AT_MOST);
|
||||
}
|
||||
else {
|
||||
// Any other height should be respected as is.
|
||||
heightSpec = heightMeasureSpec;
|
||||
}
|
||||
|
||||
super.onMeasure(widthMeasureSpec, heightSpec);
|
||||
}
|
||||
}
|
|
@ -0,0 +1,40 @@
|
|||
package eu.siacs.conversations.ui.widget;
|
||||
|
||||
import android.content.Context;
|
||||
import android.util.AttributeSet;
|
||||
|
||||
import androidx.annotation.NonNull;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
public class TabLayout extends com.google.android.material.tabs.TabLayout {
|
||||
|
||||
private VisibilityChangeListener listener = null;
|
||||
|
||||
public TabLayout(@NonNull Context context) {
|
||||
super(context);
|
||||
}
|
||||
|
||||
public TabLayout(@NonNull Context context, @Nullable AttributeSet attrs) {
|
||||
super(context, attrs);
|
||||
}
|
||||
|
||||
public TabLayout(@NonNull Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
|
||||
super(context, attrs, defStyleAttr);
|
||||
}
|
||||
|
||||
public void setListener(VisibilityChangeListener listener) {
|
||||
this.listener = listener;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setVisibility(int visibility) {
|
||||
super.setVisibility(visibility);
|
||||
if (listener != null) {
|
||||
listener.onVisibilityChanged(visibility);
|
||||
}
|
||||
}
|
||||
|
||||
public interface VisibilityChangeListener {
|
||||
void onVisibilityChanged(int visibility);
|
||||
}
|
||||
}
|
|
@ -47,6 +47,9 @@ import java.util.List;
|
|||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.CountDownLatch;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.ScheduledFuture;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
@ -148,7 +151,7 @@ public class XmppConnection implements Runnable {
|
|||
private final HashMap<Jid, ServiceDiscoveryResult> disco = new HashMap<>();
|
||||
private final HashMap<String, Jid> commands = new HashMap<>();
|
||||
private final SparseArray<AbstractAcknowledgeableStanza> mStanzaQueue = new SparseArray<>();
|
||||
private final Hashtable<String, Pair<IqPacket, OnIqPacketReceived>> packetCallbacks =
|
||||
private final Hashtable<String, Pair<IqPacket, Pair<OnIqPacketReceived, ScheduledFuture>>> packetCallbacks =
|
||||
new Hashtable<>();
|
||||
private final Set<OnAdvancedStreamFeaturesLoaded> advancedStreamFeaturesLoadedListeners =
|
||||
new HashSet<>();
|
||||
|
@ -191,6 +194,7 @@ public class XmppConnection implements Runnable {
|
|||
private String verifiedHostname = null;
|
||||
private volatile Thread mThread;
|
||||
private CountDownLatch mStreamCountDownLatch;
|
||||
private static ScheduledExecutorService SCHEDULER = Executors.newScheduledThreadPool(1);
|
||||
|
||||
public XmppConnection(final Account account, final XmppConnectionService service) {
|
||||
this.account = account;
|
||||
|
@ -1172,13 +1176,13 @@ public class XmppConnection implements Runnable {
|
|||
} else {
|
||||
OnIqPacketReceived callback = null;
|
||||
synchronized (this.packetCallbacks) {
|
||||
final Pair<IqPacket, OnIqPacketReceived> packetCallbackDuple =
|
||||
final Pair<IqPacket, Pair<OnIqPacketReceived, ScheduledFuture>> packetCallbackDuple =
|
||||
packetCallbacks.get(packet.getId());
|
||||
if (packetCallbackDuple != null) {
|
||||
// Packets to the server should have responses from the server
|
||||
if (packetCallbackDuple.first.toServer(account)) {
|
||||
if (packet.fromServer(account)) {
|
||||
callback = packetCallbackDuple.second;
|
||||
callback = packetCallbackDuple.second.first;
|
||||
packetCallbacks.remove(packet.getId());
|
||||
} else {
|
||||
Log.e(
|
||||
|
@ -1189,7 +1193,7 @@ public class XmppConnection implements Runnable {
|
|||
} else {
|
||||
if (packet.getFrom() != null
|
||||
&& packet.getFrom().equals(packetCallbackDuple.first.getTo())) {
|
||||
callback = packetCallbackDuple.second;
|
||||
callback = packetCallbackDuple.second.first;
|
||||
packetCallbacks.remove(packet.getId());
|
||||
} else {
|
||||
Log.e(
|
||||
|
@ -1833,11 +1837,13 @@ public class XmppConnection implements Runnable {
|
|||
+ ": clearing "
|
||||
+ this.packetCallbacks.size()
|
||||
+ " iq callbacks");
|
||||
final Iterator<Pair<IqPacket, OnIqPacketReceived>> iterator =
|
||||
final Iterator<Pair<IqPacket, Pair<OnIqPacketReceived, ScheduledFuture>>> iterator =
|
||||
this.packetCallbacks.values().iterator();
|
||||
while (iterator.hasNext()) {
|
||||
Pair<IqPacket, OnIqPacketReceived> entry = iterator.next();
|
||||
callbacks.add(entry.second);
|
||||
Pair<IqPacket, Pair<OnIqPacketReceived, ScheduledFuture>> entry = iterator.next();
|
||||
if (entry.second.second == null || entry.second.second.cancel(false)) {
|
||||
callbacks.add(entry.second.first);
|
||||
}
|
||||
iterator.remove();
|
||||
}
|
||||
}
|
||||
|
@ -2250,18 +2256,36 @@ public class XmppConnection implements Runnable {
|
|||
}
|
||||
|
||||
public String sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback) {
|
||||
return sendIqPacket(packet, callback, null);
|
||||
}
|
||||
|
||||
public String sendIqPacket(final IqPacket packet, final OnIqPacketReceived callback, Long timeout) {
|
||||
packet.setFrom(account.getJid());
|
||||
return this.sendUnmodifiedIqPacket(packet, callback, false);
|
||||
return this.sendUnmodifiedIqPacket(packet, callback, false, timeout);
|
||||
}
|
||||
|
||||
public synchronized String sendUnmodifiedIqPacket(final IqPacket packet, final OnIqPacketReceived callback, boolean force) {
|
||||
return sendUnmodifiedIqPacket(packet, callback, force, null);
|
||||
}
|
||||
|
||||
public synchronized String sendUnmodifiedIqPacket(
|
||||
final IqPacket packet, final OnIqPacketReceived callback, boolean force) {
|
||||
final IqPacket packet, final OnIqPacketReceived callback, boolean force, Long timeout) {
|
||||
if (packet.getId() == null) {
|
||||
packet.setAttribute("id", nextRandomId());
|
||||
}
|
||||
if (callback != null) {
|
||||
synchronized (this.packetCallbacks) {
|
||||
packetCallbacks.put(packet.getId(), new Pair<>(packet, callback));
|
||||
ScheduledFuture timeoutFuture = null;
|
||||
if (timeout != null) {
|
||||
timeoutFuture = SCHEDULER.schedule(() -> {
|
||||
synchronized (this.packetCallbacks) {
|
||||
final IqPacket failurePacket = new IqPacket(IqPacket.TYPE.TIMEOUT);
|
||||
final Pair<IqPacket, Pair<OnIqPacketReceived, ScheduledFuture>> removedCallback = packetCallbacks.remove(packet.getId());
|
||||
if (removedCallback != null) removedCallback.second.first.onIqPacketReceived(account, failurePacket);
|
||||
}
|
||||
}, timeout, TimeUnit.SECONDS);
|
||||
}
|
||||
packetCallbacks.put(packet.getId(), new Pair<>(packet, new Pair<>(callback, timeoutFuture)));
|
||||
}
|
||||
}
|
||||
this.sendPacket(packet, force);
|
||||
|
|
|
@ -75,4 +75,8 @@ public class Field extends Element {
|
|||
public boolean isRequired() {
|
||||
return hasChild("required");
|
||||
}
|
||||
|
||||
public List<Option> getOptions() {
|
||||
return Option.forField(this);
|
||||
}
|
||||
}
|
||||
|
|
66
src/main/java/eu/siacs/conversations/xmpp/forms/Option.java
Normal file
66
src/main/java/eu/siacs/conversations/xmpp/forms/Option.java
Normal file
|
@ -0,0 +1,66 @@
|
|||
package eu.siacs.conversations.xmpp.forms;
|
||||
|
||||
import com.caverock.androidsvg.SVG;
|
||||
import com.caverock.androidsvg.SVGParseException;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
|
||||
import eu.siacs.conversations.xml.Element;
|
||||
|
||||
public class Option {
|
||||
protected final String value;
|
||||
protected final String label;
|
||||
protected final SVG icon;
|
||||
|
||||
public static List<Option> forField(Element field) {
|
||||
List<Option> options = new ArrayList<>();
|
||||
for (Element el : field.getChildren()) {
|
||||
if (el.getNamespace() == null || !el.getNamespace().equals("jabber:x:data")) continue;
|
||||
if (!el.getName().equals("option")) continue;
|
||||
options.add(new Option(el));
|
||||
}
|
||||
return options;
|
||||
}
|
||||
|
||||
public Option(final Element option) {
|
||||
this(
|
||||
option.findChildContent("value", "jabber:x:data"),
|
||||
option.getAttribute("label"),
|
||||
parseSVG(option.findChild("svg", "http://www.w3.org/2000/svg"))
|
||||
);
|
||||
}
|
||||
|
||||
public Option(final String value, final String label) {
|
||||
this(value, label, null);
|
||||
}
|
||||
|
||||
public Option(final String value, final String label, final SVG icon) {
|
||||
this.value = value;
|
||||
this.label = label == null ? value : label;
|
||||
this.icon = icon;
|
||||
}
|
||||
|
||||
public boolean equals(Object o) {
|
||||
if (!(o instanceof Option)) return false;
|
||||
|
||||
if (value == ((Option) o).value) return true;
|
||||
if (value == null || ((Option) o).value == null) return false;
|
||||
return value.equals(((Option) o).value);
|
||||
}
|
||||
|
||||
public String toString() { return label; }
|
||||
|
||||
public String getValue() { return value; }
|
||||
|
||||
public SVG getIcon() { return icon; }
|
||||
|
||||
private static SVG parseSVG(final Element svg) {
|
||||
if (svg == null) return null;
|
||||
try {
|
||||
return SVG.getFromString(svg.toString());
|
||||
} catch (final SVGParseException e) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
9
src/main/res/drawable/list_choice.xml
Normal file
9
src/main/res/drawable/list_choice.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<selector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:enterFadeDuration="@android:integer/config_shortAnimTime"
|
||||
android:exitFadeDuration="@android:integer/config_shortAnimTime">
|
||||
|
||||
<item android:state_pressed="true" android:drawable="@color/grey500" />
|
||||
<item android:state_activated="true" android:drawable="@color/grey500" />
|
||||
<item android:drawable="@android:color/transparent" />
|
||||
</selector>
|
|
@ -40,6 +40,6 @@
|
|||
<FrameLayout
|
||||
android:id="@+id/main_fragment"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"/>
|
||||
android:layout_height="match_parent" />
|
||||
</LinearLayout>
|
||||
</layout>
|
||||
|
|
12
src/main/res/layout/button_grid_item.xml
Normal file
12
src/main/res/layout/button_grid_item.xml
Normal file
|
@ -0,0 +1,12 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<Button
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="16dp"
|
||||
android:textAllCaps="false" />
|
||||
|
||||
</layout>
|
8
src/main/res/layout/command_button.xml
Normal file
8
src/main/res/layout/command_button.xml
Normal file
|
@ -0,0 +1,8 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<TextView
|
||||
android:id="@+id/command"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
style="?android:attr/buttonStyleSmall" />
|
||||
</layout>
|
67
src/main/res/layout/command_button_grid_field.xml
Normal file
67
src/main/res/layout/command_button_grid_field.xml
Normal file
|
@ -0,0 +1,67 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="13dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Subhead"
|
||||
android:textColor="?attr/edit_text_color" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/desc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="8dp"
|
||||
android:gravity="center"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Status"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/default_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="40dp"
|
||||
android:layout_marginLeft="40dp"
|
||||
android:layout_marginBottom="24dp"
|
||||
android:layout_marginTop="24dp"
|
||||
android:gravity="center"
|
||||
android:textAllCaps="false"
|
||||
android:minHeight="75dp" />
|
||||
|
||||
<eu.siacs.conversations.ui.widget.GridView
|
||||
android:id="@+id/buttons"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginRight="16dp"
|
||||
android:paddingLeft="16dp"
|
||||
android:horizontalSpacing="0dp"
|
||||
android:verticalSpacing="0dp"
|
||||
android:gravity="center"
|
||||
android:numColumns="auto_fit" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/open_button"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="8dp"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginTop="40dp"
|
||||
android:gravity="center"
|
||||
android:text="other / custom"
|
||||
style="@style/Widget.Conversations.Button.Borderless" />
|
||||
|
||||
</LinearLayout>
|
||||
</layout>
|
49
src/main/res/layout/command_checkbox_field.xml
Normal file
49
src/main/res/layout/command_checkbox_field.xml
Normal file
|
@ -0,0 +1,49 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<RelativeLayout
|
||||
android:id="@+id/row"
|
||||
android:background="?selectableItemBackground"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="16dp"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_centerVertical="true"
|
||||
android:layout_toLeftOf="@+id/checkbox"
|
||||
android:layout_toStartOf="@+id/checkbox"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="13dp"
|
||||
android:scrollHorizontally="false"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Subhead" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/desc"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Status"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
<CheckBox
|
||||
android:id="@+id/checkbox"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentRight="true"
|
||||
android:layout_centerVertical="true"
|
||||
android:paddingRight="16dp"
|
||||
android:layout_marginLeft="16dp" />
|
||||
|
||||
</RelativeLayout>
|
||||
</layout>
|
22
src/main/res/layout/command_item_card.xml
Normal file
22
src/main/res/layout/command_item_card.xml
Normal file
|
@ -0,0 +1,22 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<androidx.cardview.widget.CardView
|
||||
xmlns:card_view="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_gravity="center"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
card_view:cardCornerRadius="4dp">
|
||||
<GridLayout
|
||||
android:id="@+id/fields"
|
||||
android:orientation="horizontal"
|
||||
android:columnCount="2"
|
||||
android:useDefaultMargins="true"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content" />
|
||||
</androidx.cardview.widget.CardView>
|
||||
|
||||
</layout>
|
31
src/main/res/layout/command_note.xml
Normal file
31
src/main/res/layout/command_note.xml
Normal file
|
@ -0,0 +1,31 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ImageView
|
||||
android:visibility="gone"
|
||||
android:id="@+id/error_icon"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="50dp"
|
||||
android:layout_centerHorizontal="true"
|
||||
android:scaleType="fitCenter"
|
||||
android:src="@drawable/ic_send_cancel_dnd" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/message"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Body1"
|
||||
android:textColor="?attr/edit_text_color" />
|
||||
|
||||
</LinearLayout>
|
||||
</layout>
|
30
src/main/res/layout/command_page.xml
Normal file
30
src/main/res/layout/command_page.xml
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<RelativeLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<androidx.recyclerview.widget.RecyclerView
|
||||
android:id="@+id/form"
|
||||
android:paddingTop="8dp"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_above="@+id/actions"
|
||||
android:orientation="vertical"
|
||||
app:layoutManager="androidx.recyclerview.widget.GridLayoutManager" />
|
||||
|
||||
<GridView
|
||||
android:id="@+id/actions"
|
||||
android:background="?colorPrimary"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentBottom="true"
|
||||
android:horizontalSpacing="0dp"
|
||||
android:verticalSpacing="0dp"
|
||||
android:numColumns="2" />
|
||||
|
||||
</RelativeLayout>
|
||||
</layout>
|
34
src/main/res/layout/command_progress_bar.xml
Normal file
34
src/main/res/layout/command_progress_bar.xml
Normal file
|
@ -0,0 +1,34 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="130dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingBottom="16dp" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Body1"
|
||||
android:text="Please be patient..."
|
||||
android:textColor="?attr/edit_text_color" />
|
||||
|
||||
</LinearLayout>
|
||||
|
||||
</layout>
|
53
src/main/res/layout/command_radio_edit_field.xml
Normal file
53
src/main/res/layout/command_radio_edit_field.xml
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="13dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Subhead"
|
||||
android:textColor="?attr/edit_text_color" />
|
||||
|
||||
<eu.siacs.conversations.ui.widget.GridView
|
||||
android:id="@+id/radios"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="8dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:horizontalSpacing="0dp"
|
||||
android:verticalSpacing="0dp"
|
||||
android:numColumns="auto_fit" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/open"
|
||||
android:visibility="gone"
|
||||
style="@style/Widget.Conversations.EditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:ems="10"
|
||||
android:imeOptions="actionNext"
|
||||
android:inputType="textWebEditText"
|
||||
android:minLines="1" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/desc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="8dp"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Status"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
|
||||
</LinearLayout>
|
||||
</layout>
|
16
src/main/res/layout/command_result_cell.xml
Normal file
16
src/main/res/layout/command_result_cell.xml
Normal file
|
@ -0,0 +1,16 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/text"
|
||||
android:textIsSelectable="true"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Body1"
|
||||
android:textColor="?attr/edit_text_color" />
|
||||
|
||||
</layout>
|
53
src/main/res/layout/command_result_field.xml
Normal file
53
src/main/res/layout/command_result_field.xml
Normal file
|
@ -0,0 +1,53 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Caption" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/media_image"
|
||||
android:visibility="gone"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_gravity="center"
|
||||
android:adjustViewBounds="true"
|
||||
android:background="@color/black87"
|
||||
android:longClickable="false"
|
||||
android:scaleType="centerCrop"/>
|
||||
|
||||
<ListView
|
||||
android:id="@+id/values"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:background="?attr/color_background_tertiary"
|
||||
android:divider="@android:color/transparent"
|
||||
android:dividerHeight="0dp"></ListView>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/desc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Status"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
|
||||
</LinearLayout>
|
||||
</layout>
|
17
src/main/res/layout/command_row.xml
Normal file
17
src/main/res/layout/command_row.xml
Normal file
|
@ -0,0 +1,17 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/command"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center_vertical"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:paddingLeft="@dimen/avatar_item_distance"
|
||||
android:paddingRight="@dimen/avatar_item_distance"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Body1"
|
||||
android:textColor="?attr/edit_text_color"
|
||||
android:background="@drawable/list_choice" />
|
||||
|
||||
</layout>
|
47
src/main/res/layout/command_search_list_field.xml
Normal file
47
src/main/res/layout/command_search_list_field.xml
Normal file
|
@ -0,0 +1,47 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="13dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Subhead"
|
||||
android:textColor="?attr/edit_text_color" />
|
||||
|
||||
<EditText
|
||||
android:id="@+id/search"
|
||||
style="@style/Widget.Conversations.EditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginLeft="8dp"
|
||||
android:layout_marginRight="8dp"
|
||||
android:ems="10"
|
||||
android:imeOptions="actionNext"
|
||||
android:minLines="1" />
|
||||
|
||||
<ListView
|
||||
android:id="@+id/list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="200dp"
|
||||
android:choiceMode="singleChoice" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/desc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="8dp"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Status"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
|
||||
</LinearLayout>
|
||||
</layout>
|
38
src/main/res/layout/command_spinner_field.xml
Normal file
38
src/main/res/layout/command_spinner_field.xml
Normal file
|
@ -0,0 +1,38 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
<LinearLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingBottom="16dp"
|
||||
android:orientation="vertical">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="13dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingBottom="8dp"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Subhead"
|
||||
android:textColor="?attr/edit_text_color" />
|
||||
|
||||
<Spinner
|
||||
android:id="@+id/spinner"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginRight="8dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingBottom="8dp" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/desc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="16dp"
|
||||
android:paddingRight="8dp"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Status"
|
||||
android:textColor="?android:textColorSecondary" />
|
||||
|
||||
</LinearLayout>
|
||||
</layout>
|
30
src/main/res/layout/command_text_field.xml
Normal file
30
src/main/res/layout/command_text_field.xml
Normal file
|
@ -0,0 +1,30 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<com.google.android.material.textfield.TextInputLayout
|
||||
android:id="@+id/textinput_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingBottom="16dp"
|
||||
app:suffixTextAppearance="@style/Widget.Conversations.EditText"
|
||||
app:errorTextAppearance="@style/TextAppearance.Conversations.Design.Error"
|
||||
app:hintTextAppearance="@style/TextAppearance.Conversations.Design.Hint"
|
||||
app:helperTextTextAppearance="@style/TextAppearance.Conversations.Status"
|
||||
app:helperTextTextColor="?android:textColorSecondary">
|
||||
|
||||
<com.google.android.material.textfield.TextInputEditText
|
||||
android:id="@+id/textinput"
|
||||
style="@style/Widget.Conversations.EditText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:ems="10"
|
||||
android:imeOptions="actionNext"
|
||||
android:inputType="textWebEditText"
|
||||
android:minLines="1" />
|
||||
|
||||
</com.google.android.material.textfield.TextInputLayout>
|
||||
|
||||
</layout>
|
36
src/main/res/layout/command_webview.xml
Normal file
36
src/main/res/layout/command_webview.xml
Normal file
|
@ -0,0 +1,36 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/desc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:gravity="center"
|
||||
android:minHeight="?android:attr/listPreferredItemHeightSmall"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Body1"
|
||||
android:textColor="?attr/edit_text_color" />
|
||||
|
||||
<WebView
|
||||
android:id="@+id/webview"
|
||||
android:layout_below="@+id/desc"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/progressbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="130dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingBottom="16dp" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</layout>
|
|
@ -7,6 +7,30 @@
|
|||
android:layout_height="match_parent"
|
||||
android:background="?attr/color_background_secondary">
|
||||
|
||||
<eu.siacs.conversations.ui.widget.TabLayout
|
||||
android:visibility="gone"
|
||||
android:id="@+id/tab_layout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="?attr/colorPrimary"
|
||||
android:elevation="@dimen/toolbar_elevation"
|
||||
android:theme="@style/ThemeOverlay.AppCompat.Dark.ActionBar"
|
||||
app:tabGravity="fill"
|
||||
app:tabIndicatorColor="@color/white87"
|
||||
app:tabMode="scrollable"
|
||||
app:tabSelectedTextColor="@color/white"
|
||||
app:tabTextColor="@color/white70" />
|
||||
|
||||
<androidx.viewpager.widget.ViewPager
|
||||
android:id="@+id/conversation_view_pager"
|
||||
android:layout_below="@id/tab_layout"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent"
|
||||
android:background="?attr/color_background_secondary">
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
<ListView
|
||||
android:id="@+id/messages_view"
|
||||
android:layout_width="fill_parent"
|
||||
|
@ -217,6 +241,34 @@
|
|||
android:textAppearance="@style/TextAppearance.Conversations.Body1.OnDark"
|
||||
android:textStyle="bold" />
|
||||
</RelativeLayout>
|
||||
</RelativeLayout>
|
||||
|
||||
<RelativeLayout
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="fill_parent">
|
||||
|
||||
<ListView
|
||||
android:id="@+id/commands_view"
|
||||
android:layout_width="fill_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_alignParentStart="true"
|
||||
android:layout_alignParentLeft="true"
|
||||
android:layout_alignParentTop="true"
|
||||
android:background="?attr/color_background_secondary"
|
||||
android:divider="@android:color/transparent"
|
||||
android:dividerHeight="0dp"></ListView>
|
||||
|
||||
<ProgressBar
|
||||
android:id="@+id/commands_view_progressbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="130dp"
|
||||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:paddingBottom="16dp" />
|
||||
|
||||
</RelativeLayout>
|
||||
|
||||
</androidx.viewpager.widget.ViewPager>
|
||||
|
||||
</RelativeLayout>
|
||||
</layout>
|
9
src/main/res/layout/radio_grid_item.xml
Normal file
9
src/main/res/layout/radio_grid_item.xml
Normal file
|
@ -0,0 +1,9 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<layout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||
|
||||
<RadioButton
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content" />
|
||||
|
||||
</layout>
|
|
@ -22,4 +22,5 @@
|
|||
android:paddingLeft="8dp"
|
||||
android:paddingRight="8dp"
|
||||
android:textAppearance="@style/TextAppearance.Conversations.Body1"
|
||||
android:textColor="?attr/edit_text_color" />
|
||||
android:textColor="?attr/edit_text_color"
|
||||
android:background="@drawable/list_choice" />
|
||||
|
|
|
@ -135,6 +135,12 @@
|
|||
android:orderInCategory="73"
|
||||
android:title="@string/add_to_favorites"
|
||||
app:showAsAction="never" />
|
||||
|
||||
<item
|
||||
android:id="@+id/action_refresh_feature_discovery"
|
||||
android:orderInCategory="74"
|
||||
android:title="@string/refresh_feature_discovery"
|
||||
app:showAsAction="never" />
|
||||
</menu>
|
||||
</item>
|
||||
|
||||
|
|
|
@ -740,6 +740,7 @@
|
|||
<string name="action_copy_location">Copy Location</string>
|
||||
<string name="action_share_location">Share Location</string>
|
||||
<string name="action_directions">Directions</string>
|
||||
<string name="action_execute">Go</string>
|
||||
<string name="title_activity_share_location">Share location</string>
|
||||
<string name="title_activity_show_location">Show location</string>
|
||||
<string name="share">Share</string>
|
||||
|
@ -1053,4 +1054,5 @@
|
|||
<string name="message_selection_title">%1$d selected</string>
|
||||
<string name="contact_tag_general">General</string>
|
||||
<string name="contact_tag_with_total">%1$s (%2$d)</string>
|
||||
<string name="refresh_feature_discovery">Refresh Feature Discovery</string>
|
||||
</resources>
|
||||
|
|
Loading…
Reference in a new issue