forked from mirror/monocles_chat_clean
Basic link previews + include opengraph metadata about HTML links + Links in a quote are not really in this message + OpenGraph library not using tor right now, so disable when we are + disable Gradle daemon + Better defaults for OpenGraphParser + Eventually we need to move on + Empty result as an error instead of hanging + re-activate map preview (Experimental) + modify mime type detection for shared files
This commit is contained in:
parent
7af92975cc
commit
bc249aaa70
26 changed files with 304 additions and 113 deletions
|
@ -128,8 +128,9 @@ dependencies {
|
|||
implementation "com.daimajia.swipelayout:library:1.2.0@aar"
|
||||
implementation 'com.nineoldandroids:library:2.4.0'
|
||||
implementation "androidx.core:core-ktx:1.12.0"
|
||||
implementation "androidx.compose.material3:material3-android:1.2.0-beta02"
|
||||
implementation "androidx.compose.material3:material3-android:1.2.0-rc01"
|
||||
implementation "androidx.emoji2:emoji2-emojipicker:1.4.0"
|
||||
implementation 'com.github.Priyansh-Kedia:OpenGraphParser:2.5.6'
|
||||
}
|
||||
|
||||
ext {
|
||||
|
|
|
@ -15,19 +15,6 @@
|
|||
"versionName": "1.7.8.8",
|
||||
"outputFile": "monocles chat-1.7.8.8-git-universal-release.apk"
|
||||
},
|
||||
{
|
||||
"type": "ONE_OF_MANY",
|
||||
"filters": [
|
||||
{
|
||||
"filterType": "ABI",
|
||||
"value": "armeabi-v7a"
|
||||
}
|
||||
],
|
||||
"attributes": [],
|
||||
"versionCode": 15901,
|
||||
"versionName": "1.7.8.8",
|
||||
"outputFile": "monocles chat-1.7.8.8-git-armeabi-v7a-release.apk"
|
||||
},
|
||||
{
|
||||
"type": "ONE_OF_MANY",
|
||||
"filters": [
|
||||
|
@ -41,6 +28,19 @@
|
|||
"versionName": "1.7.8.8",
|
||||
"outputFile": "monocles chat-1.7.8.8-git-arm64-v8a-release.apk"
|
||||
},
|
||||
{
|
||||
"type": "ONE_OF_MANY",
|
||||
"filters": [
|
||||
{
|
||||
"filterType": "ABI",
|
||||
"value": "armeabi-v7a"
|
||||
}
|
||||
],
|
||||
"attributes": [],
|
||||
"versionCode": 15901,
|
||||
"versionName": "1.7.8.8",
|
||||
"outputFile": "monocles chat-1.7.8.8-git-armeabi-v7a-release.apk"
|
||||
},
|
||||
{
|
||||
"type": "ONE_OF_MANY",
|
||||
"filters": [
|
||||
|
|
|
@ -18,4 +18,5 @@ android.enableJetifier=true
|
|||
android.useAndroidX=true
|
||||
org.gradle.gradle-args=--max-workers=32
|
||||
android.nonTransitiveRClass=true
|
||||
android.nonFinalResIds=false
|
||||
android.nonFinalResIds=false
|
||||
org.gradle.daemon=false
|
|
@ -15,6 +15,8 @@ import android.util.Log;
|
|||
import android.util.Base64;
|
||||
import android.util.Pair;
|
||||
import android.view.View;
|
||||
|
||||
import eu.siacs.conversations.ui.util.MyLinkify;
|
||||
import eu.siacs.conversations.utils.Compatibility;
|
||||
import android.graphics.drawable.Drawable;
|
||||
import android.text.Html;
|
||||
|
@ -918,7 +920,7 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
|||
return false;
|
||||
} else {
|
||||
String body, otherBody;
|
||||
if (this.hasFileOnRemoteHost()) {
|
||||
if (this.hasFileOnRemoteHost() && (this.body == null || "".equals(this.body))) {
|
||||
body = getFileParams().url;
|
||||
otherBody = message.body == null ? null : message.body.trim();
|
||||
} else {
|
||||
|
@ -1203,6 +1205,19 @@ public class Message extends AbstractEntity implements AvatarService.Avatarable
|
|||
}
|
||||
}
|
||||
|
||||
public List<URI> getLinks() {
|
||||
SpannableStringBuilder text = new SpannableStringBuilder(
|
||||
getBody().replaceAll("^>.*", "") // Remove quotes
|
||||
);
|
||||
return MyLinkify.extractLinks(text).stream().map((url) -> {
|
||||
try {
|
||||
return new URI(url);
|
||||
} catch (final URISyntaxException e) {
|
||||
return null;
|
||||
}
|
||||
}).filter(x -> x != null).collect(Collectors.toList());
|
||||
}
|
||||
|
||||
public URI getOob() {
|
||||
final String url = getFileParams().url;
|
||||
try {
|
||||
|
|
|
@ -150,17 +150,19 @@ public class MessageGenerator extends AbstractGenerator {
|
|||
if (message.hasFileOnRemoteHost()) {
|
||||
final Message.FileParams fileParams = message.getFileParams();
|
||||
|
||||
if (message.getBody().equals("")) {
|
||||
message.setBody(fileParams.url);
|
||||
packet.addChild("fallback", "urn:xmpp:fallback:0").setAttribute("for", Namespace.OOB)
|
||||
.addChild("body", "urn:xmpp:fallback:0");
|
||||
} else {
|
||||
long start = message.getQuoteableBody().length();
|
||||
message.appendBody(fileParams.url);
|
||||
packet.addChild("fallback", "urn:xmpp:fallback:0").setAttribute("for", Namespace.OOB)
|
||||
.addChild("body", "urn:xmpp:fallback:0")
|
||||
.setAttribute("start", String.valueOf(start))
|
||||
.setAttribute("end", String.valueOf(start + fileParams.url.length()));
|
||||
if (message.getFallbacks(Namespace.OOB).isEmpty()) {
|
||||
if (message.getBody().equals("")) {
|
||||
message.setBody(fileParams.url);
|
||||
packet.addChild("fallback", "urn:xmpp:fallback:0").setAttribute("for", Namespace.OOB)
|
||||
.addChild("body", "urn:xmpp:fallback:0");
|
||||
} else {
|
||||
long start = message.getQuoteableBody().length();
|
||||
message.appendBody(fileParams.url);
|
||||
packet.addChild("fallback", "urn:xmpp:fallback:0").setAttribute("for", Namespace.OOB)
|
||||
.addChild("body", "urn:xmpp:fallback:0")
|
||||
.setAttribute("start", String.valueOf(start))
|
||||
.setAttribute("end", String.valueOf(start + fileParams.url.length()));
|
||||
}
|
||||
}
|
||||
|
||||
packet.addChild("x", Namespace.OOB).addChild("url").setContent(fileParams.url);
|
||||
|
|
|
@ -129,11 +129,11 @@ public class HttpConnectionManager extends AbstractConnectionManager {
|
|||
}
|
||||
}
|
||||
|
||||
OkHttpClient buildHttpClient(final HttpUrl url, final Account account, boolean interactive) {
|
||||
public OkHttpClient buildHttpClient(final HttpUrl url, final Account account, boolean interactive) {
|
||||
return buildHttpClient(url, account, 30, interactive);
|
||||
}
|
||||
|
||||
OkHttpClient buildHttpClient(final HttpUrl url, final Account account, int readTimeout, boolean interactive) {
|
||||
public OkHttpClient buildHttpClient(final HttpUrl url, final Account account, int readTimeout, boolean interactive) {
|
||||
final String slotHostname = url.host();
|
||||
final boolean onionSlot = slotHostname.endsWith(".onion");
|
||||
final boolean I2PSlot = slotHostname.endsWith(".i2p");
|
||||
|
@ -182,4 +182,31 @@ public class HttpConnectionManager extends AbstractConnectionManager {
|
|||
}
|
||||
return body.byteStream();
|
||||
}
|
||||
|
||||
public static String extractFilenameFromResponse(okhttp3.Response response) {
|
||||
String filename = null;
|
||||
|
||||
// Try to extract filename from the Content-Disposition header
|
||||
String contentDisposition = response.header("Content-Disposition");
|
||||
if (contentDisposition != null && contentDisposition.contains("filename=")) {
|
||||
String[] parts = contentDisposition.split(";");
|
||||
for (String part : parts) {
|
||||
if (part.trim().startsWith("filename=")) {
|
||||
filename = part.substring("filename=".length()).trim().replace("\"", "");
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// If filename is not found in the Content-Disposition header, try to get it from the URL
|
||||
if (filename == null || filename.isEmpty()) {
|
||||
HttpUrl httpUrl = response.request().url();
|
||||
List<String> pathSegments = httpUrl.pathSegments();
|
||||
if (!pathSegments.isEmpty()) {
|
||||
filename = pathSegments.get(pathSegments.size() - 1);
|
||||
}
|
||||
}
|
||||
|
||||
return filename;
|
||||
}
|
||||
}
|
|
@ -232,7 +232,6 @@ public class HttpDownloadConnection implements Transferable {
|
|||
message.setDeleted(true);
|
||||
}
|
||||
message.setTransferable(null);
|
||||
if (cb != null) cb.accept(file);
|
||||
mXmppConnectionService.updateMessage(message);
|
||||
mHttpConnectionManager.finishConnection(this);
|
||||
final boolean notifyAfterScan = notify;
|
||||
|
@ -419,6 +418,7 @@ public class HttpDownloadConnection implements Transferable {
|
|||
decryptIfNeeded();
|
||||
finish();
|
||||
updateImageBounds();
|
||||
if (cb != null) cb.accept(file);
|
||||
} catch (final SSLHandshakeException e) {
|
||||
changeStatus(STATUS_OFFER);
|
||||
} catch (final Exception e) {
|
||||
|
|
|
@ -23,12 +23,17 @@ import android.graphics.drawable.AnimatedImageDrawable;
|
|||
import android.provider.DocumentsContract;
|
||||
import com.google.common.io.Files;
|
||||
|
||||
import com.kedia.ogparser.JsoupProxy;
|
||||
import com.kedia.ogparser.OpenGraphCallback;
|
||||
import com.kedia.ogparser.OpenGraphParser;
|
||||
import com.kedia.ogparser.OpenGraphResult;
|
||||
|
||||
import eu.siacs.conversations.ui.ConversationsActivity;
|
||||
import eu.siacs.conversations.utils.FileUtils;
|
||||
import eu.siacs.conversations.persistance.UnifiedPushDatabase;
|
||||
import eu.siacs.conversations.xmpp.OnGatewayResult;
|
||||
import eu.siacs.conversations.utils.Consumer;
|
||||
|
||||
import java.net.URI;
|
||||
import static eu.siacs.conversations.utils.Compatibility.s;
|
||||
import android.Manifest;
|
||||
import androidx.annotation.RequiresApi;
|
||||
|
@ -138,6 +143,7 @@ import java.util.concurrent.CountDownLatch;
|
|||
import java.util.concurrent.Executor;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.ScheduledExecutorService;
|
||||
import java.util.concurrent.Semaphore;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import java.util.concurrent.atomic.AtomicLong;
|
||||
|
@ -232,6 +238,9 @@ import eu.siacs.conversations.xmpp.stanzas.MessagePacket;
|
|||
import eu.siacs.conversations.xmpp.stanzas.PresencePacket;
|
||||
import io.ipfs.cid.Cid;
|
||||
import me.leolin.shortcutbadger.ShortcutBadger;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import okhttp3.HttpUrl;
|
||||
import okhttp3.OkHttpClient;
|
||||
|
||||
public class XmppConnectionService extends Service {
|
||||
|
||||
|
@ -2097,10 +2106,10 @@ public class XmppConnectionService extends Service {
|
|||
}
|
||||
|
||||
public void sendMessage(final Message message) {
|
||||
sendMessage(message, false, false);
|
||||
sendMessage(message, false, false, false);
|
||||
}
|
||||
|
||||
private void sendMessage(final Message message, final boolean resend, final boolean delay) {
|
||||
private void sendMessage(final Message message, final boolean resend, final boolean previewedLinks, final boolean delay) {
|
||||
if (resend) {
|
||||
message.setTime(System.currentTimeMillis());
|
||||
}
|
||||
|
@ -2140,7 +2149,108 @@ public class XmppConnectionService extends Service {
|
|||
message.setCounterpart(message.getConversation().getJid().asBareJid());
|
||||
}
|
||||
|
||||
if (account.isOnlineAndConnected() && !inProgressJoin) {
|
||||
boolean waitForPreview = false;
|
||||
if (getPreferences().getBoolean("send_link_previews", true) && !previewedLinks && !message.needsUploading()) {
|
||||
final List<URI> links = message.getLinks();
|
||||
if (!links.isEmpty()) {
|
||||
waitForPreview = true;
|
||||
if (account.isOnlineAndConnected()) {
|
||||
FILE_ATTACHMENT_EXECUTOR.execute(() -> {
|
||||
for (URI link : links) {
|
||||
if ("https".equals(link.getScheme())) {
|
||||
try {
|
||||
HttpUrl url = HttpUrl.parse(link.toString());
|
||||
OkHttpClient http = getHttpConnectionManager().buildHttpClient(url, account, false);
|
||||
okhttp3.Response response = http.newCall(new okhttp3.Request.Builder().url(url).head().build()).execute();
|
||||
final String mimeType = response.header("Content-Type") == null ? "" : response.header("Content-Type");
|
||||
final boolean image = mimeType.startsWith("image/");
|
||||
final boolean audio = mimeType.startsWith("audio/");
|
||||
final boolean video = mimeType.startsWith("video/");
|
||||
final boolean pdf = mimeType.equals("application/pdf");
|
||||
final boolean html = mimeType.startsWith("text/html") || mimeType.startsWith("application/xhtml+xml");
|
||||
if (response.isSuccessful() && (image || audio || video || pdf)) {
|
||||
Message.FileParams params = message.getFileParams();
|
||||
params.url = url.toString();
|
||||
if (response.header("Content-Length") != null) params.size = Long.parseLong(response.header("Content-Length"), 10);
|
||||
if (!Message.configurePrivateFileMessage(message)) {
|
||||
message.setType(image ? Message.TYPE_IMAGE : Message.TYPE_FILE);
|
||||
}
|
||||
params.setName(HttpConnectionManager.extractFilenameFromResponse(response));
|
||||
|
||||
if (link.toString().equals(message.getQuoteableBody())) {
|
||||
Element fallback = new Element("fallback", "urn:xmpp:fallback:0").setAttribute("for", Namespace.OOB);
|
||||
fallback.addChild("body", "urn:xmpp:fallback:0");
|
||||
message.addPayload(fallback);
|
||||
} else if (message.getQuoteableBody().indexOf(link.toString()) >= 0) {
|
||||
// Part of the real body, not just a fallback
|
||||
Element fallback = new Element("fallback", "urn:xmpp:fallback:0").setAttribute("for", Namespace.OOB);
|
||||
fallback.addChild("body", "urn:xmpp:fallback:0")
|
||||
.setAttribute("start", "0")
|
||||
.setAttribute("end", "0");
|
||||
message.addPayload(fallback);
|
||||
}
|
||||
|
||||
getHttpConnectionManager().createNewDownloadConnection(message, false, (file) -> {
|
||||
synchronized (message.getConversation()) {
|
||||
if (message.getStatus() == Message.STATUS_WAITING) sendMessage(message, true, true, false);
|
||||
}
|
||||
});
|
||||
return;
|
||||
} else if (response.isSuccessful() && html && !useI2PToConnect()) {
|
||||
Semaphore waiter = new Semaphore(0);
|
||||
OpenGraphParser.Builder openGraphBuilder = new OpenGraphParser.Builder(new OpenGraphCallback() {
|
||||
@Override
|
||||
public void onPostResponse(OpenGraphResult result) {
|
||||
Element rdf = new Element("Description", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
|
||||
rdf.setAttribute("xmlns:rdf", "http://www.w3.org/1999/02/22-rdf-syntax-ns#");
|
||||
rdf.setAttribute("rdf:about", link.toString());
|
||||
if (result.getTitle() != null && !"".equals(result.getTitle())) {
|
||||
rdf.addChild("title", "https://ogp.me/ns#").setContent(result.getTitle());
|
||||
}
|
||||
if (result.getDescription() != null && !"".equals(result.getDescription())) {
|
||||
rdf.addChild("description", "https://ogp.me/ns#").setContent(result.getDescription());
|
||||
}
|
||||
if (result.getUrl() != null) {
|
||||
rdf.addChild("url", "https://ogp.me/ns#").setContent(result.getUrl());
|
||||
}
|
||||
if (result.getImage() != null) {
|
||||
rdf.addChild("image", "https://ogp.me/ns#").setContent(result.getImage());
|
||||
}
|
||||
if (result.getType() != null) {
|
||||
rdf.addChild("type", "https://ogp.me/ns#").setContent(result.getType());
|
||||
}
|
||||
if (result.getSiteName() != null) {
|
||||
rdf.addChild("site_name", "https://ogp.me/ns#").setContent(result.getSiteName());
|
||||
}
|
||||
message.addPayload(rdf);
|
||||
waiter.release();
|
||||
}
|
||||
|
||||
public void onError(String error) {
|
||||
waiter.release();
|
||||
}
|
||||
})
|
||||
.showNullOnEmpty(true)
|
||||
.maxBodySize(4000)
|
||||
.timeout(5000);
|
||||
if (useTorToConnect()) {
|
||||
openGraphBuilder = openGraphBuilder.jsoupProxy(new JsoupProxy("127.0.0.1", 8118));
|
||||
}
|
||||
openGraphBuilder.build().parse(link.toString());
|
||||
waiter.tryAcquire(10L, TimeUnit.SECONDS);
|
||||
}
|
||||
} catch (final IOException | InterruptedException e) { }
|
||||
}
|
||||
}
|
||||
synchronized (message.getConversation()) {
|
||||
if (message.getStatus() == Message.STATUS_WAITING) sendMessage(message, true, true, false);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (account.isOnlineAndConnected() && !inProgressJoin && !waitForPreview) {
|
||||
switch (message.getEncryption()) {
|
||||
case Message.ENCRYPTION_NONE:
|
||||
if (message.needsUploading()) {
|
||||
|
@ -2317,11 +2427,9 @@ public class XmppConnectionService extends Service {
|
|||
}
|
||||
|
||||
private void sendUnsentMessages(final Conversation conversation) {
|
||||
final Runnable runnable = () -> {
|
||||
synchronized (conversation) {
|
||||
conversation.findWaitingMessages(message -> resendMessage(message, true));
|
||||
};
|
||||
mDatabaseWriterExecutor.execute((runnable));
|
||||
|
||||
}
|
||||
}
|
||||
private void resendFailedMessages(final Conversation conversation) {
|
||||
final Runnable runnable = () -> {
|
||||
|
@ -2348,7 +2456,7 @@ public class XmppConnectionService extends Service {
|
|||
}
|
||||
|
||||
public void resendMessage(final Message message, final boolean delay) {
|
||||
sendMessage(message, true, delay);
|
||||
sendMessage(message, true, false, delay);
|
||||
}
|
||||
|
||||
public void requestEasyOnboardingInvite(final Account account, final EasyOnboardingInvite.OnInviteRequested callback) {
|
||||
|
|
|
@ -88,6 +88,8 @@ public class MediaAdapter extends RecyclerView.Adapter<MediaAdapter.MediaViewHol
|
|||
Log.d(Config.LOGTAG, "mime=" + mime);
|
||||
if (mime == null) {
|
||||
attr = R.attr.media_preview_unknown;
|
||||
} else if (mime.equals("audio/x-m4b")) {
|
||||
attr = R.attr.media_preview_audiobook;
|
||||
} else if (mime.startsWith("audio/")) {
|
||||
attr = R.attr.media_preview_audio;
|
||||
} else if (mime.equals("text/calendar") || (mime.equals("text/x-vcalendar"))) {
|
||||
|
|
|
@ -168,7 +168,7 @@ public class MediaPreviewAdapter extends RecyclerView.Adapter<MediaPreviewAdapte
|
|||
}
|
||||
}
|
||||
|
||||
class MediaPreviewViewHolder extends RecyclerView.ViewHolder {
|
||||
static class MediaPreviewViewHolder extends RecyclerView.ViewHolder {
|
||||
|
||||
private final MediaPreviewBinding binding;
|
||||
|
||||
|
|
|
@ -32,6 +32,8 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Properties;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collection;
|
||||
|
||||
import eu.siacs.conversations.Config;
|
||||
import eu.siacs.conversations.R;
|
||||
|
@ -262,6 +264,7 @@ public final class MimeUtils {
|
|||
add("audio/mpeg", "mpega");
|
||||
add("audio/mpeg", "mp2");
|
||||
add("audio/mp4", "m4a");
|
||||
add("audio/x-m4b", "m4b");
|
||||
add("audio/mpegurl", "m3u");
|
||||
add("audio/ogg", "ogg");
|
||||
add("audio/ogg", "oga");
|
||||
|
@ -427,6 +430,8 @@ public final class MimeUtils {
|
|||
"text/plain"
|
||||
);
|
||||
|
||||
// mime types that are more reliant by path
|
||||
private static final Collection<String> PATH_PRECEDENCE_MIME_TYPE = Arrays.asList("audio/x-m4b");
|
||||
|
||||
private static void add(String mimeType, String extension) {
|
||||
// If we have an existing x -> y mapping, we do not want to
|
||||
|
@ -552,46 +557,49 @@ public final class MimeUtils {
|
|||
}
|
||||
|
||||
public static String guessMimeTypeFromUriAndMime(final Context context, final Uri uri, final String mime) {
|
||||
Log.d(Config.LOGTAG, "guessMimeTypeFromUriAndMime " + uri + " and mime=" + mime);
|
||||
if (mime == null || mime.equals("application/octet-stream")) {
|
||||
final String guess = guessMimeTypeFromUri(context, uri);
|
||||
if (guess != null) {
|
||||
return guess;
|
||||
} else {
|
||||
return mime;
|
||||
}
|
||||
Log.d(Config.LOGTAG, "guessMimeTypeFromUriAndMime(" + uri + "," + mime+")");
|
||||
final String mimeFromUri = guessMimeTypeFromUri(context, uri);
|
||||
Log.d(Config.LOGTAG,"mimeFromUri:"+mimeFromUri);
|
||||
if (PATH_PRECEDENCE_MIME_TYPE.contains(mimeFromUri)) {
|
||||
return mimeFromUri;
|
||||
} else if (mime == null || mime.equals("application/octet-stream")) {
|
||||
return mimeFromUri;
|
||||
} else {
|
||||
return mime;
|
||||
}
|
||||
return guessMimeTypeFromUri(context, uri);
|
||||
}
|
||||
|
||||
public static String guessMimeTypeFromUri(Context context, Uri uri) {
|
||||
// try the content resolver
|
||||
String mimeType;
|
||||
public static String guessMimeTypeFromUri(final Context context, final Uri uri) {
|
||||
final String mimeTypeContentResolver = guessFromContentResolver(context, uri);
|
||||
final String mimeTypeFromQueryParameter = uri.getQueryParameter("mimeType");
|
||||
final String name = "content".equals(uri.getScheme()) ? getDisplayName(context, uri) : null;
|
||||
final String mimeTypeFromName = Strings.isNullOrEmpty(name) ? null : guessFromPath(name);
|
||||
final String path = uri.getPath();
|
||||
final String mimeTypeFromPath = Strings.isNullOrEmpty(path) ? null : guessFromPath(path);
|
||||
if (PATH_PRECEDENCE_MIME_TYPE.contains(mimeTypeFromName)) {
|
||||
return mimeTypeFromName;
|
||||
}
|
||||
if (PATH_PRECEDENCE_MIME_TYPE.contains(mimeTypeFromPath)) {
|
||||
return mimeTypeFromPath;
|
||||
}
|
||||
if (mimeTypeContentResolver != null && !"application/octet-stream".equals(mimeTypeContentResolver)) {
|
||||
return mimeTypeContentResolver;
|
||||
}
|
||||
if (mimeTypeFromName != null) {
|
||||
return mimeTypeFromName;
|
||||
}
|
||||
if (mimeTypeFromQueryParameter != null) {
|
||||
return mimeTypeFromQueryParameter;
|
||||
}
|
||||
return mimeTypeFromPath;
|
||||
}
|
||||
|
||||
private static String guessFromContentResolver(final Context context, final Uri uri) {
|
||||
try {
|
||||
mimeType = context.getContentResolver().getType(uri);
|
||||
} catch (final Throwable throwable) {
|
||||
mimeType = null;
|
||||
return context.getContentResolver().getType(uri);
|
||||
} catch (final Throwable e) {
|
||||
return null;
|
||||
}
|
||||
// try the extension
|
||||
if (mimeType == null || mimeType.equals("application/octet-stream")) {
|
||||
final String path = uri.getPath();
|
||||
if (path != null) {
|
||||
mimeType = guessFromPath(path);
|
||||
}
|
||||
}
|
||||
if (mimeType == null && "content".equals(uri.getScheme())) {
|
||||
final String name = getDisplayName(context, uri);
|
||||
if (name != null) {
|
||||
mimeType = guessFromPath(name);
|
||||
}
|
||||
}
|
||||
// sometimes this works (as with the commit content api)
|
||||
if (mimeType == null) {
|
||||
try {
|
||||
mimeType = uri.getQueryParameter("mimeType");
|
||||
} catch (final Throwable throwable) { }
|
||||
}
|
||||
return mimeType;
|
||||
}
|
||||
|
||||
private static String getDisplayName(final Context context, final Uri uri) {
|
||||
|
|
|
@ -541,6 +541,8 @@ public class UIHelper {
|
|||
return context.getString(R.string.file);
|
||||
} else if (MimeUtils.AMBIGUOUS_CONTAINER_FORMATS.contains(mime)) {
|
||||
return context.getString(R.string.multimedia_file);
|
||||
} else if (mime.equals("audio/x-m4b")) {
|
||||
return context.getString(R.string.audiobook);
|
||||
} else if (mime.startsWith("audio/")) {
|
||||
return context.getString(R.string.audio);
|
||||
} else if (mime.startsWith("video/")) {
|
||||
|
|
6
src/main/res/drawable/ic_play_lesson_black_24.xml
Normal file
6
src/main/res/drawable/ic_play_lesson_black_24.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<vector android:height="48dp" android:tint="#000000"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M18,11c0.34,0 0.67,0.03 1,0.08V4c0,-1.1 -0.9,-2 -2,-2H5C3.9,2 3,2.9 3,4v16c0,1.1 0.9,2 2,2h7.26C11.47,20.87 11,19.49 11,18C11,14.13 14.13,11 18,11zM7,11V4h5v7L9.5,9.5L7,11z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M18,13c-2.76,0 -5,2.24 -5,5s2.24,5 5,5s5,-2.24 5,-5S20.76,13 18,13zM16.75,20.5v-5l4,2.5L16.75,20.5z"/>
|
||||
</vector>
|
6
src/main/res/drawable/ic_play_lesson_white_48dp.xml
Normal file
6
src/main/res/drawable/ic_play_lesson_white_48dp.xml
Normal file
|
@ -0,0 +1,6 @@
|
|||
<vector android:height="48dp" android:tint="#FFFFFF"
|
||||
android:viewportHeight="24" android:viewportWidth="24"
|
||||
android:width="48dp" xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<path android:fillColor="@android:color/white" android:pathData="M18,11c0.34,0 0.67,0.03 1,0.08V4c0,-1.1 -0.9,-2 -2,-2H5C3.9,2 3,2.9 3,4v16c0,1.1 0.9,2 2,2h7.26C11.47,20.87 11,19.49 11,18C11,14.13 14.13,11 18,11zM7,11V4h5v7L9.5,9.5L7,11z"/>
|
||||
<path android:fillColor="@android:color/white" android:pathData="M18,13c-2.76,0 -5,2.24 -5,5s2.24,5 5,5s5,-2.24 5,-5S20.76,13 18,13zM16.75,20.5v-5l4,2.5L16.75,20.5z"/>
|
||||
</vector>
|
|
@ -2,13 +2,13 @@
|
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<corners
|
||||
android:topLeftRadius="2dp"
|
||||
android:topRightRadius="15dp"
|
||||
android:bottomRightRadius="15dp"
|
||||
android:bottomLeftRadius="15dp" />
|
||||
android:topRightRadius="20dp"
|
||||
android:bottomRightRadius="20dp"
|
||||
android:bottomLeftRadius="20dp" />
|
||||
<padding
|
||||
android:bottom="2dp"
|
||||
android:left="6dp"
|
||||
android:right="6dp"
|
||||
android:bottom="0dp"
|
||||
android:left="10dp"
|
||||
android:right="10dp"
|
||||
android:top="2dp" />
|
||||
<solid android:color="@color/lightblue" />
|
||||
</shape>
|
|
@ -3,13 +3,13 @@
|
|||
<stroke android:width="2dp" android:color="@color/accent"/>
|
||||
<corners
|
||||
android:topLeftRadius="2dp"
|
||||
android:topRightRadius="15dp"
|
||||
android:bottomRightRadius="15dp"
|
||||
android:bottomLeftRadius="15dp" />
|
||||
android:topRightRadius="20dp"
|
||||
android:bottomRightRadius="20dp"
|
||||
android:bottomLeftRadius="20dp" />
|
||||
<padding
|
||||
android:bottom="2dp"
|
||||
android:left="6dp"
|
||||
android:right="6dp"
|
||||
android:bottom="0dp"
|
||||
android:left="10dp"
|
||||
android:right="10dp"
|
||||
android:top="2dp" />
|
||||
<solid android:color="@color/lightblue" />
|
||||
</shape>
|
|
@ -2,13 +2,13 @@
|
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<corners
|
||||
android:topLeftRadius="2dp"
|
||||
android:topRightRadius="15dp"
|
||||
android:bottomRightRadius="15dp"
|
||||
android:bottomLeftRadius="15dp" />
|
||||
android:topRightRadius="20dp"
|
||||
android:bottomRightRadius="20dp"
|
||||
android:bottomLeftRadius="20dp" />
|
||||
<padding
|
||||
android:bottom="4dp"
|
||||
android:left="6dp"
|
||||
android:right="6dp"
|
||||
android:bottom="2dp"
|
||||
android:left="10dp"
|
||||
android:right="10dp"
|
||||
android:top="4dp" />
|
||||
<solid android:color="@color/lightred" />
|
||||
</shape>
|
|
@ -3,13 +3,13 @@
|
|||
<stroke android:width="2dp" android:color="@color/accent"/>
|
||||
<corners
|
||||
android:topLeftRadius="2dp"
|
||||
android:topRightRadius="15dp"
|
||||
android:bottomRightRadius="15dp"
|
||||
android:bottomLeftRadius="15dp" />
|
||||
android:topRightRadius="20dp"
|
||||
android:bottomRightRadius="20dp"
|
||||
android:bottomLeftRadius="20dp" />
|
||||
<padding
|
||||
android:bottom="4dp"
|
||||
android:left="6dp"
|
||||
android:right="6dp"
|
||||
android:bottom="2dp"
|
||||
android:left="10dp"
|
||||
android:right="10dp"
|
||||
android:top="4dp" />
|
||||
<solid android:color="@color/lightred" />
|
||||
</shape>
|
|
@ -1,14 +1,14 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<corners
|
||||
android:topLeftRadius="15dp"
|
||||
android:topRightRadius="15dp"
|
||||
android:topLeftRadius="20dp"
|
||||
android:topRightRadius="20dp"
|
||||
android:bottomRightRadius="2dp"
|
||||
android:bottomLeftRadius="15dp" />
|
||||
android:bottomLeftRadius="20dp" />
|
||||
<padding
|
||||
android:bottom="4dp"
|
||||
android:left="6dp"
|
||||
android:right="6dp"
|
||||
android:bottom="2dp"
|
||||
android:left="10dp"
|
||||
android:right="10dp"
|
||||
android:top="4dp" />
|
||||
<solid android:color="@color/darkblue" />
|
||||
</shape>
|
|
@ -2,14 +2,14 @@
|
|||
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="rectangle">
|
||||
<stroke android:width="2dp" android:color="@color/accent"/>
|
||||
<corners
|
||||
android:topLeftRadius="15dp"
|
||||
android:topRightRadius="15dp"
|
||||
android:topLeftRadius="20dp"
|
||||
android:topRightRadius="20dp"
|
||||
android:bottomRightRadius="2dp"
|
||||
android:bottomLeftRadius="15dp" />
|
||||
android:bottomLeftRadius="20dp" />
|
||||
<padding
|
||||
android:bottom="4dp"
|
||||
android:left="6dp"
|
||||
android:right="6dp"
|
||||
android:bottom="2dp"
|
||||
android:left="10dp"
|
||||
android:right="10dp"
|
||||
android:top="4dp" />
|
||||
<solid android:color="@color/darkblue" />
|
||||
</shape>
|
|
@ -409,6 +409,7 @@
|
|||
<string name="image">Bild</string>
|
||||
<string name="pdf_document">PDF-Dokument</string>
|
||||
<string name="apk">Android App</string>
|
||||
<string name="audiobook">Audiobuch</string>
|
||||
<string name="vcard">Kontakt</string>
|
||||
<string name="avatar_has_been_published">Profilbild wurde gespeichert</string>
|
||||
<string name="sending_x_file">%s wird gesendet</string>
|
||||
|
|
|
@ -67,6 +67,7 @@
|
|||
<attr name="media_preview_tour" format="reference" />
|
||||
<attr name="media_preview_contact" format="reference" />
|
||||
<attr name="media_preview_app" format="reference" />
|
||||
<attr name="media_preview_audiobook" format="reference" />
|
||||
<attr name="media_preview_calendar" format="reference" />
|
||||
<attr name="media_preview_archive" format="reference" />
|
||||
<attr name="media_preview_ebook" format="reference" />
|
||||
|
|
|
@ -10,4 +10,5 @@
|
|||
<bool name="set_text_collapsable">false</bool>
|
||||
<bool name="enforce_dane">false</bool>
|
||||
<bool name="hide_donation_snackbar">false</bool>
|
||||
<bool name="send_link_previews">true</bool>
|
||||
</resources>
|
|
@ -392,6 +392,7 @@
|
|||
<string name="image">image</string>
|
||||
<string name="pdf_document">PDF document</string>
|
||||
<string name="apk">Android App</string>
|
||||
<string name="audiobook">Audiobook</string>
|
||||
<string name="vcard">Contact</string>
|
||||
<string name="avatar_has_been_published">Avatar has been published!</string>
|
||||
<string name="sending_x_file">Sending %s</string>
|
||||
|
@ -1405,4 +1406,6 @@
|
|||
<string name="grey">Grey</string>
|
||||
<string name="blue">Blue</string>
|
||||
<string name="green_and_blue">Green and blue</string>
|
||||
<string name="pref_send_link_previews">Send link previews</string>
|
||||
<string name="pref_send_link_previews_summary">Attach metadata about links when sending a message</string>
|
||||
</resources>
|
||||
|
|
|
@ -114,6 +114,7 @@
|
|||
<item name="media_preview_tour" type="reference">@drawable/baseline_tour_black_48</item>
|
||||
<item name="media_preview_contact" type="reference">@drawable/ic_person_black_48dp</item>
|
||||
<item name="media_preview_app" type="reference">@drawable/ic_android_black_48dp</item>
|
||||
<item name="media_preview_audiobook" type="reference">@drawable/ic_play_lesson_black_24</item>
|
||||
<item name="media_preview_calendar" type="reference">@drawable/ic_event_black_48dp</item>
|
||||
<item name="media_preview_archive" type="reference">@drawable/ic_archive_black_48dp</item>
|
||||
<item name="media_preview_ebook" type="reference">@drawable/ic_book_black_48dp</item>
|
||||
|
@ -352,6 +353,7 @@
|
|||
<item name="media_preview_tour" type="reference">@drawable/baseline_tour_white_48</item>
|
||||
<item name="media_preview_contact" type="reference">@drawable/ic_person_white_48dp</item>
|
||||
<item name="media_preview_app" type="reference">@drawable/ic_android_white_48dp</item>
|
||||
<item name="media_preview_audiobook" type="reference">@drawable/ic_play_lesson_white_48dp</item>
|
||||
<item name="media_preview_calendar" type="reference">@drawable/ic_event_white_48dp</item>
|
||||
<item name="media_preview_archive" type="reference">@drawable/ic_archive_white_48dp</item>
|
||||
<item name="media_preview_ebook" type="reference">@drawable/ic_book_white_48dp</item>
|
||||
|
|
|
@ -482,6 +482,11 @@
|
|||
android:key="last_activity"
|
||||
android:summary="@string/pref_broadcast_last_activity_summary"
|
||||
android:title="@string/pref_broadcast_last_activity" />
|
||||
<SwitchPreference
|
||||
android:defaultValue="@bool/send_link_previews"
|
||||
android:key="send_link_previews"
|
||||
android:summary="@string/pref_send_link_previews_summary"
|
||||
android:title="@string/pref_send_link_previews" />
|
||||
<SwitchPreference
|
||||
android:defaultValue="@bool/notifications_from_strangers"
|
||||
android:key="notifications_from_strangers"
|
||||
|
@ -502,25 +507,25 @@
|
|||
android:key="use_internal_updater"
|
||||
android:summary="@string/pref_use_internal_updater_summary"
|
||||
android:title="@string/pref_use_internal_updater" />
|
||||
<!--
|
||||
<SwitchPreference
|
||||
android:defaultValue="@bool/show_links_inside"
|
||||
android:key="show_links_inside"
|
||||
android:summary="@string/pref_show_links_inside_summary"
|
||||
android:title="@string/pref_show_links_inside" />
|
||||
-->
|
||||
<SwitchPreference
|
||||
android:defaultValue="@bool/show_maps_inside"
|
||||
android:disableDependentsState="false"
|
||||
android:key="show_maps_inside"
|
||||
android:summary="@string/pref_show_mappreview_inside_summary"
|
||||
android:title="@string/pref_show_mappreview_inside" />
|
||||
<!--
|
||||
<EditTextPreference
|
||||
android:defaultValue="@string/mappreview_url"
|
||||
android:dependency="show_maps_inside"
|
||||
android:key="mappreview_host"
|
||||
android:summary="@string/pref_mappreview_host_summary"
|
||||
android:title="@string/pref_mappreview_host" />
|
||||
-->
|
||||
<SwitchPreference
|
||||
android:defaultValue="@bool/warn_unencrypted_chat"
|
||||
android:key="warn_unencrypted_chat"
|
||||
|
|
Loading…
Reference in a new issue