Show original message inline

pull/147/head
M66B 6 years ago
parent 4f0d9a15c1
commit c49be8bd69

@ -126,7 +126,6 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
static final String ACTION_VIEW_MESSAGES = BuildConfig.APPLICATION_ID + ".VIEW_MESSAGES";
static final String ACTION_VIEW_THREAD = BuildConfig.APPLICATION_ID + ".VIEW_THREAD";
static final String ACTION_VIEW_FULL = BuildConfig.APPLICATION_ID + ".VIEW_FULL";
static final String ACTION_STORE_RAW = BuildConfig.APPLICATION_ID + ".STORE_RAW";
static final String ACTION_EDIT_FOLDER = BuildConfig.APPLICATION_ID + ".EDIT_FOLDER";
static final String ACTION_EDIT_ANSWERS = BuildConfig.APPLICATION_ID + ".EDIT_ANSWERS";
@ -481,7 +480,6 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
IntentFilter iff = new IntentFilter();
iff.addAction(ACTION_VIEW_MESSAGES);
iff.addAction(ACTION_VIEW_THREAD);
iff.addAction(ACTION_VIEW_FULL);
iff.addAction(ACTION_STORE_RAW);
iff.addAction(ACTION_EDIT_FOLDER);
iff.addAction(ACTION_EDIT_ANSWERS);
@ -1031,8 +1029,6 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
onViewMessages(intent);
else if (ACTION_VIEW_THREAD.equals(action))
onViewThread(intent);
else if (ACTION_VIEW_FULL.equals(action))
onViewFull(intent);
else if (ACTION_STORE_RAW.equals(action))
onStoreRaw(intent);
else if (ACTION_EDIT_FOLDER.equals(action))
@ -1099,15 +1095,6 @@ public class ActivityView extends ActivityBilling implements FragmentManager.OnB
fragmentTransaction.commit();
}
private void onViewFull(Intent intent) {
FragmentWebView fragment = new FragmentWebView();
fragment.setArguments(intent.getExtras());
FragmentTransaction fragmentTransaction = getSupportFragmentManager().beginTransaction();
fragmentTransaction.replace(R.id.content_frame, fragment).addToBackStack("webview");
fragmentTransaction.commit();
}
private void onStoreRaw(Intent intent) {
message = intent.getLongExtra("id", -1);
Intent create = new Intent(Intent.ACTION_CREATE_DOCUMENT);

@ -52,6 +52,7 @@ import android.text.style.ImageSpan;
import android.text.style.QuoteSpan;
import android.text.style.StyleSpan;
import android.text.style.URLSpan;
import android.util.Base64;
import android.util.LongSparseArray;
import android.util.TypedValue;
import android.view.LayoutInflater;
@ -61,6 +62,10 @@ import android.view.MotionEvent;
import android.view.TouchDelegate;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.DownloadListener;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.Button;
import android.widget.CheckBox;
import android.widget.CompoundButton;
@ -73,10 +78,16 @@ import android.widget.Toast;
import com.google.android.material.bottomnavigation.BottomNavigationView;
import com.google.android.material.snackbar.Snackbar;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import org.xml.sax.XMLReader;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import java.text.Collator;
import java.text.DateFormat;
import java.text.SimpleDateFormat;
@ -95,6 +106,7 @@ import javax.mail.internet.InternetAddress;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.appcompat.widget.PopupMenu;
import androidx.constraintlayout.widget.ConstraintLayout;
import androidx.constraintlayout.widget.Group;
import androidx.core.content.ContextCompat;
import androidx.lifecycle.LifecycleOwner;
@ -204,6 +216,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
private ImageButton ibQuotes;
private ImageButton ibImages;
private TextView tvBody;
private View vwBody;
private ContentLoadingProgressBar pbBody;
private TextView tvNoInternetBody;
@ -283,6 +296,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
ibQuotes = itemView.findViewById(R.id.ibQuotes);
ibImages = itemView.findViewById(R.id.ibImages);
tvBody = itemView.findViewById(R.id.tvBody);
vwBody = itemView.findViewById(R.id.vwBody);
pbBody = itemView.findViewById(R.id.pbBody);
tvNoInternetBody = itemView.findViewById(R.id.tvNoInternetBody);
@ -406,6 +420,8 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
btnHtml.setVisibility(View.GONE);
ibQuotes.setVisibility(View.GONE);
ibImages.setVisibility(View.GONE);
tvBody.setVisibility(View.GONE);
vwBody.setVisibility(View.GONE);
pbBody.setVisibility(View.GONE);
tvNoInternetBody.setVisibility(View.GONE);
@ -426,6 +442,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
final boolean show_expanded = properties.getValue("expanded", message.id);
boolean show_addresses = !properties.getValue("addresses", message.id);
boolean show_headers = properties.getValue("headers", message.id);
final boolean show_html = properties.getValue("html", message.id);
pbLoading.setVisibility(View.GONE);
@ -637,9 +654,11 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
grpHeaders.setVisibility(show_headers && show_expanded ? View.VISIBLE : View.GONE);
grpAttachments.setVisibility(message.attachments > 0 && show_expanded ? View.VISIBLE : View.GONE);
btnHtml.setVisibility(viewType == ViewType.THREAD && show_expanded ? View.INVISIBLE : View.GONE);
ibQuotes.setVisibility(viewType == ViewType.THREAD && show_expanded ? View.INVISIBLE : View.GONE);
ibImages.setVisibility(viewType == ViewType.THREAD && show_expanded ? View.INVISIBLE : View.GONE);
btnHtml.setVisibility(viewType == ViewType.THREAD && show_expanded && !show_html ? View.INVISIBLE : View.GONE);
ibQuotes.setVisibility(viewType == ViewType.THREAD && show_expanded && !show_html ? View.INVISIBLE : View.GONE);
ibImages.setVisibility(viewType == ViewType.THREAD && show_expanded && !show_html ? View.INVISIBLE : View.GONE);
tvBody.setVisibility(viewType == ViewType.THREAD && show_expanded && !show_html ? View.INVISIBLE : View.GONE);
vwBody.setVisibility(viewType == ViewType.THREAD && show_expanded && show_html ? View.INVISIBLE : View.GONE);
pbBody.setVisibility(View.GONE);
tvNoInternetBody.setVisibility(View.GONE);
@ -697,11 +716,14 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
else
tvNoInternetBody.setVisibility(View.VISIBLE);
if (body == null && message.content) {
Bundle args = new Bundle();
args.putSerializable("message", message);
bodyTask.execute(context, owner, args, "message:body");
}
if (body == null && message.content)
if (show_html)
onShowHtmlConfirmed(message);
else {
Bundle args = new Bundle();
args.putSerializable("message", message);
bodyTask.execute(context, owner, args, "message:body");
}
List<EntityAttachment> attachments = idAttachments.get(message.id);
if (attachments != null)
@ -758,9 +780,9 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
if (attachment.type.startsWith("image/") && !attachment.isInline())
images.add(attachment);
adapterImage.set(images);
rvImage.setVisibility(images.size() > 0 ? View.VISIBLE : View.INVISIBLE);
rvImage.setVisibility(images.size() > 0 ? View.VISIBLE : View.GONE);
if (message.content) {
if (message.content && !show_html) {
Bundle args = new Bundle();
args.putSerializable("message", message);
bodyTask.execute(context, owner, args, "message:body");
@ -1176,12 +1198,164 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
.show();
}
@SuppressLint({"SetJavaScriptEnabled", "ClickableViewAccessibility"})
private void onShowHtmlConfirmed(final TupleMessageEx message) {
LocalBroadcastManager lbm = LocalBroadcastManager.getInstance(context);
lbm.sendBroadcast(
new Intent(ActivityView.ACTION_VIEW_FULL)
.putExtra("id", message.id)
.putExtra("from", MessageHelper.formatAddresses(message.from)));
properties.setValue("html", message.id, true);
btnHtml.setVisibility(View.GONE);
ibQuotes.setVisibility(View.GONE);
ibImages.setVisibility(View.GONE);
tvBody.setVisibility(View.GONE);
pbBody.setVisibility(View.VISIBLE);
if (!(vwBody instanceof WebView)) {
// For performance reasons the WebView is created when needed only
WebView webView = new WebView(context) {
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec);
int height = getMeasuredHeight();
if (height < tvBody.getMinHeight())
setMeasuredDimension(getMeasuredWidth(), tvBody.getMinHeight());
}
};
webView.setId(vwBody.getId());
ConstraintLayout cl = (ConstraintLayout) itemView;
cl.removeView(vwBody);
cl.addView(webView, vwBody.getLayoutParams());
vwBody = webView;
}
final WebView webView = (WebView) vwBody;
webView.setVisibility(View.INVISIBLE);
WebSettings settings = webView.getSettings();
settings.setJavaScriptEnabled(true);
settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(true);
settings.setBuiltInZoomControls(true);
settings.setDisplayZoomControls(false);
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
webView.setWebViewClient(new WebViewClient() {
@Override
public void onPageCommitVisible(WebView view, String url) {
pbBody.setVisibility(View.GONE);
webView.setVisibility(View.VISIBLE);
}
public boolean shouldOverrideUrlLoading(WebView view, String url) {
Helper.view(context, owner, Uri.parse(url), true);
return true;
}
});
webView.setDownloadListener(new DownloadListener() {
public void onDownloadStart(
String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
Log.i("Download url=" + url + " mime type=" + mimetype);
Uri uri = Uri.parse(url);
Helper.view(context, owner, uri, true);
}
});
webView.setOnLongClickListener(new View.OnLongClickListener() {
@Override
public boolean onLongClick(View view) {
WebView.HitTestResult result = ((WebView) view).getHitTestResult();
if (result.getType() == WebView.HitTestResult.IMAGE_TYPE ||
result.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
Log.i("Long press url=" + result.getExtra());
Uri uri = Uri.parse(result.getExtra());
Helper.view(context, owner, uri, true);
return true;
}
return false;
}
});
// Fix zooming
webView.setOnTouchListener(new View.OnTouchListener() {
@Override
public boolean onTouch(View v, MotionEvent me) {
if (me.getPointerCount() == 2) {
switch (me.getAction()) {
case MotionEvent.ACTION_DOWN:
v.getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_MOVE:
v.getParent().requestDisallowInterceptTouchEvent(true);
break;
case MotionEvent.ACTION_UP:
v.getParent().requestDisallowInterceptTouchEvent(false);
break;
}
}
return false;
}
});
Bundle args = new Bundle();
args.putLong("id", message.id);
new SimpleTask<String>() {
@Override
protected String onExecute(Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
String html = Helper.readText(EntityMessage.getFile(context, id));
Document doc = Jsoup.parse(html);
for (Element img : doc.select("img"))
try {
String src = img.attr("src");
if (src.startsWith("cid:")) {
String cid = src.substring(4);
EntityAttachment attachment = DB.getInstance(context).attachment().getAttachment(id, cid);
if (attachment != null && attachment.available) {
InputStream is = null;
try {
File file = EntityAttachment.getFile(context, attachment.id);
is = new BufferedInputStream(new FileInputStream(file));
byte[] bytes = new byte[(int) file.length()];
if (is.read(bytes) != bytes.length)
throw new IOException("length");
StringBuilder sb = new StringBuilder();
sb.append("data:");
sb.append(attachment.type);
sb.append(";base64,");
sb.append(Base64.encodeToString(bytes, Base64.DEFAULT));
img.attr("src", sb.toString());
} finally {
if (is != null)
is.close();
}
}
}
} catch (Throwable ex) {
Log.e(ex);
}
return doc.html();
}
@Override
protected void onExecuted(Bundle args, String html) {
webView.loadDataWithBaseURL("email://", html, "text/html", "UTF-8", null);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(context, owner, ex);
}
}.execute(context, owner, args, "message:webview");
}
private void onShowQuotes(final TupleMessageEx message) {
@ -1284,6 +1458,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
ibImages.setVisibility(has_images && show_expanded && !show_images ? View.VISIBLE : View.GONE);
tvBody.setText(body);
tvBody.setMovementMethod(new UrlHandler());
tvBody.setVisibility(show_expanded ? View.VISIBLE : View.GONE);
pbBody.setVisibility(View.GONE);
}

@ -1,247 +0,0 @@
package eu.faircode.email;
/*
This file is part of FairEmail.
FairEmail is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
FairEmail is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with FairEmail. If not, see <http://www.gnu.org/licenses/>.
Copyright 2018-2019 by Marcel Bokhorst (M66B)
*/
import android.content.Context;
import android.content.Intent;
import android.content.pm.PackageManager;
import android.net.Uri;
import android.os.Bundle;
import android.util.Base64;
import android.view.ContextMenu;
import android.view.LayoutInflater;
import android.view.Menu;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.DownloadListener;
import android.webkit.WebChromeClient;
import android.webkit.WebSettings;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;
import android.widget.Toast;
import org.jsoup.Jsoup;
import org.jsoup.nodes.Document;
import org.jsoup.nodes.Element;
import java.io.BufferedInputStream;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.InputStream;
import androidx.annotation.NonNull;
import androidx.annotation.Nullable;
import androidx.lifecycle.Lifecycle;
// https://developer.android.com/reference/android/webkit/WebView
public class FragmentWebView extends FragmentBase {
private ProgressBar progressBar;
private WebView webview;
@Override
@Nullable
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
View view = inflater.inflate(R.layout.fragment_webview, container, false);
progressBar = view.findViewById(R.id.progressbar);
webview = view.findViewById(R.id.webview);
progressBar.setProgress(0);
progressBar.setVisibility(View.VISIBLE);
WebSettings settings = webview.getSettings();
settings.setJavaScriptEnabled(true);
settings.setUseWideViewPort(true);
settings.setLoadWithOverviewMode(true);
settings.setBuiltInZoomControls(true);
settings.setDisplayZoomControls(false);
settings.setMixedContentMode(WebSettings.MIXED_CONTENT_ALWAYS_ALLOW);
webview.setWebViewClient(new WebViewClient() {
public boolean shouldOverrideUrlLoading(WebView view, String url) {
if (getViewLifecycleOwner().getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED)) {
Helper.view(getContext(), getViewLifecycleOwner(), Uri.parse(url), true);
return true;
}
return false;
}
});
webview.setWebChromeClient(new WebChromeClient() {
public void onProgressChanged(WebView view, int progress) {
progressBar.setProgress(progress);
if (progress == 100)
progressBar.setVisibility(View.GONE);
}
});
webview.setDownloadListener(new DownloadListener() {
public void onDownloadStart(
String url, String userAgent, String contentDisposition, String mimetype, long contentLength) {
Log.i("Download url=" + url + " mime type=" + mimetype);
Uri uri = Uri.parse(url);
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(uri);
PackageManager pm = getContext().getPackageManager();
if (intent.resolveActivity(pm) == null)
Toast.makeText(getContext(), getString(R.string.title_no_viewer, uri.toString()), Toast.LENGTH_LONG).show();
else
startActivity(intent);
}
});
registerForContextMenu(webview);
onLoad();
((ActivityBase) getActivity()).addBackPressedListener(onBackPressedListener);
return view;
}
private void onLoad() {
Bundle args = getArguments();
if (args.containsKey("url")) {
String url = args.getString("url");
webview.loadUrl(url);
setSubtitle(url);
} else if (args.containsKey("id")) {
new SimpleTask<String>() {
@Override
protected String onExecute(Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
String html = Helper.readText(EntityMessage.getFile(context, id));
Document doc = Jsoup.parse(html);
for (Element img : doc.select("img"))
try {
String src = img.attr("src");
if (src.startsWith("cid")) {
String[] cids = src.split(":");
if (cids.length > 1) {
String cid = "<" + cids[1] + ">";
EntityAttachment attachment = DB.getInstance(context).attachment().getAttachment(id, cid);
if (attachment != null && attachment.available) {
InputStream is = null;
try {
File file = EntityAttachment.getFile(context, attachment.id);
is = new BufferedInputStream(new FileInputStream(file));
byte[] bytes = new byte[(int) file.length()];
if (is.read(bytes) != bytes.length)
throw new IOException("length");
StringBuilder sb = new StringBuilder();
sb.append("data:");
sb.append(attachment.type);
sb.append(";base64,");
sb.append(Base64.encodeToString(bytes, Base64.DEFAULT));
img.attr("src", sb.toString());
} finally {
if (is != null)
is.close();
}
}
}
}
} catch (Throwable ex) {
Log.e(ex);
}
return doc.html();
}
@Override
protected void onExecuted(Bundle args, String html) {
String from = args.getString("from");
webview.loadDataWithBaseURL("email://", html, "text/html", "UTF-8", null);
setSubtitle(from);
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex);
}
}.execute(this, args, "webview:format");
}
}
@Override
public void onDestroyView() {
((ActivityBase) getActivity()).removeBackPressedListener(onBackPressedListener);
super.onDestroyView();
}
@Override
public void onCreateContextMenu(ContextMenu menu, View view, ContextMenu.ContextMenuInfo menuInfo) {
super.onCreateContextMenu(menu, view, menuInfo);
if (view instanceof WebView) {
final WebView.HitTestResult result = ((WebView) view).getHitTestResult();
if (result.getType() == WebView.HitTestResult.IMAGE_TYPE ||
result.getType() == WebView.HitTestResult.SRC_IMAGE_ANCHOR_TYPE) {
Log.i("Context menu url=" + result.getExtra());
menu.add(Menu.NONE, 1, 0, R.string.title_view)
.setOnMenuItemClickListener(new MenuItem.OnMenuItemClickListener() {
@Override
public boolean onMenuItemClick(MenuItem item) {
Uri uri = Uri.parse(result.getExtra());
Intent intent = new Intent(Intent.ACTION_VIEW);
intent.setData(uri);
PackageManager pm = getContext().getPackageManager();
if (intent.resolveActivity(pm) == null)
Toast.makeText(getContext(), getString(R.string.title_no_viewer, uri.toString()), Toast.LENGTH_LONG).show();
else
startActivity(intent);
return true;
}
});
}
}
}
ActivityBase.IBackPressedListener onBackPressedListener = new ActivityBase.IBackPressedListener() {
@Override
public boolean onBackPressed() {
boolean can = webview.canGoBack();
if (can)
webview.goBack();
Bundle args = getArguments();
if (args.containsKey("from") && !webview.canGoBack())
setSubtitle(args.getString("from"));
return can;
}
};
}

@ -739,13 +739,21 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier_body" />
<View
android:id="@+id/vwBody"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvBody" />
<eu.faircode.email.ContentLoadingProgressBar
android:id="@+id/pbBody"
style="@style/Base.Widget.AppCompat.ProgressBar"
android:layout_width="24dp"
android:layout_height="24dp"
android:indeterminate="true"
app:layout_constraintBottom_toBottomOf="@id/tvBody"
app:layout_constraintBottom_toBottomOf="@id/vwBody"
app:layout_constraintEnd_toEndOf="@id/tvBody"
app:layout_constraintStart_toStartOf="@id/tvBody"
app:layout_constraintTop_toTopOf="@id/tvBody" />
@ -756,7 +764,7 @@
android:layout_height="wrap_content"
android:text="@string/title_no_internet"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintBottom_toBottomOf="@id/tvBody"
app:layout_constraintBottom_toBottomOf="@id/vwBody"
app:layout_constraintEnd_toEndOf="@id/tvBody"
app:layout_constraintStart_toStartOf="@id/tvBody"
app:layout_constraintTop_toTopOf="@id/tvBody" />
@ -768,7 +776,7 @@
android:layout_marginTop="3dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvBody" />
app:layout_constraintTop_toBottomOf="@id/vwBody" />
<View
android:id="@+id/vSeparator"
@ -806,6 +814,6 @@
android:id="@+id/grpExpanded"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="vSeparatorAddress,ivExpanderAddress,bnvActions,tvBody" />
app:constraint_referenced_ids="vSeparatorAddress,ivExpanderAddress,bnvActions" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

@ -721,7 +721,6 @@
android:layout_marginTop="3dp"
android:layout_marginEnd="6dp"
android:fontFamily="monospace"
android:minHeight="60dp"
android:text="Body"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:textIsSelectable="true"
@ -729,13 +728,22 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/barrier_body" />
<View
android:id="@+id/vwBody"
android:layout_width="0dp"
android:layout_height="wrap_content"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintHeight_min="60dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/tvBody" />
<eu.faircode.email.ContentLoadingProgressBar
android:id="@+id/pbBody"
style="@style/Base.Widget.AppCompat.ProgressBar"
android:layout_width="24dp"
android:layout_height="24dp"
android:indeterminate="true"
app:layout_constraintBottom_toBottomOf="@id/tvBody"
app:layout_constraintBottom_toBottomOf="@id/vwBody"
app:layout_constraintEnd_toEndOf="@id/tvBody"
app:layout_constraintStart_toStartOf="@id/tvBody"
app:layout_constraintTop_toTopOf="@id/tvBody" />
@ -746,7 +754,7 @@
android:layout_height="wrap_content"
android:text="@string/title_no_internet"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintBottom_toBottomOf="@id/tvBody"
app:layout_constraintBottom_toBottomOf="@id/vwBody"
app:layout_constraintEnd_toEndOf="@id/tvBody"
app:layout_constraintStart_toStartOf="@id/tvBody"
app:layout_constraintTop_toTopOf="@id/tvBody" />
@ -758,7 +766,7 @@
android:layout_marginTop="3dp"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvBody" />
app:layout_constraintTop_toBottomOf="@id/vwBody" />
<View
android:id="@+id/vSeparator"
@ -796,6 +804,6 @@
android:id="@+id/grpExpanded"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="vSeparatorAddress,ivExpanderAddress,bnvActions,tvBody" />
app:constraint_referenced_ids="vSeparatorAddress,ivExpanderAddress,bnvActions" />
</androidx.constraintlayout.widget.ConstraintLayout>
</FrameLayout>

@ -354,7 +354,6 @@
<string name="title_discard">Discard</string>
<string name="title_save">Save</string>
<string name="title_send">Send</string>
<string name="title_view">View</string>
<string name="title_send_at">Send at &#8230;</string>
<string name="title_no_selection">Nothing selected</string>

Loading…
Cancel
Save