Show when no internet connection while required

This commit is contained in:
M66B 2018-12-25 12:10:20 +00:00
parent 4706b58232
commit 0a7ac9f9c4
7 changed files with 312 additions and 64 deletions

View File

@ -113,6 +113,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
private ViewType viewType;
private boolean outgoing;
private int zoom;
private boolean internet;
private IProperties properties;
private boolean threading;
@ -175,8 +176,10 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
private TextView tvHeaders;
private ProgressBar pbHeaders;
private TextView tvNoInternetHeaders;
private RecyclerView rvAttachment;
private TextView tvNoInternetAttachments;
private BottomNavigationView bnvActions;
@ -185,6 +188,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
private ImageButton ibImages;
private TextView tvBody;
private ProgressBar pbBody;
private TextView tvNoInternetBody;
private Group grpAddress;
private Group grpHeaders;
@ -230,6 +234,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
tvHeaders = itemView.findViewById(R.id.tvHeaders);
pbHeaders = itemView.findViewById(R.id.pbHeaders);
tvNoInternetHeaders = itemView.findViewById(R.id.tvNoInternetHeaders);
rvAttachment = itemView.findViewById(R.id.rvAttachment);
rvAttachment.setHasFixedSize(false);
@ -240,6 +245,8 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
adapter = new AdapterAttachment(context, owner, true);
rvAttachment.setAdapter(adapter);
tvNoInternetAttachments = itemView.findViewById(R.id.tvNoInternetAttachments);
bnvActions = itemView.findViewById(R.id.bnvActions);
btnHtml = itemView.findViewById(R.id.btnHtml);
@ -247,6 +254,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
ibImages = itemView.findViewById(R.id.ibImages);
tvBody = itemView.findViewById(R.id.tvBody);
pbBody = itemView.findViewById(R.id.pbBody);
tvNoInternetBody = itemView.findViewById(R.id.tvNoInternetBody);
grpAddress = itemView.findViewById(R.id.grpAddress);
grpHeaders = itemView.findViewById(R.id.grpHeaders);
@ -299,11 +307,16 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
tvKeywords.setVisibility(View.GONE);
pbHeaders.setVisibility(View.GONE);
tvNoInternetHeaders.setVisibility(View.GONE);
tvNoInternetAttachments.setVisibility(View.GONE);
bnvActions.setVisibility(View.GONE);
btnHtml.setVisibility(View.GONE);
ibQuotes.setVisibility(View.GONE);
ibImages.setVisibility(View.GONE);
pbBody.setVisibility(View.GONE);
tvNoInternetBody.setVisibility(View.GONE);
grpAddress.setVisibility(View.GONE);
ivAddContact.setVisibility(View.GONE);
@ -412,6 +425,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
ivAnswered.setVisibility(message.ui_answered ? View.VISIBLE : View.GONE);
ivAttachments.setVisibility(message.attachments > 0 ? View.VISIBLE : View.GONE);
tvNoInternetAttachments.setVisibility(View.GONE);
tvSubject.setText(message.subject);
if (viewType == ViewType.THREAD || viewType == ViewType.SEARCH)
@ -466,7 +480,15 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
grpAddress.setVisibility(viewType == ViewType.THREAD && show_expanded && show_addresses ? View.VISIBLE : View.GONE);
tvKeywords.setVisibility(View.GONE);
ivAddContact.setVisibility(viewType == ViewType.THREAD && show_expanded && show_addresses && contacts && message.from != null ? View.VISIBLE : View.GONE);
pbHeaders.setVisibility(View.GONE);
if (show_headers && show_expanded && message.headers == null) {
pbHeaders.setVisibility(internet ? View.VISIBLE : View.GONE);
tvNoInternetHeaders.setVisibility(internet ? View.GONE : View.VISIBLE);
} else {
pbHeaders.setVisibility(View.GONE);
tvNoInternetHeaders.setVisibility(View.GONE);
}
grpHeaders.setVisibility(show_headers && show_expanded ? View.VISIBLE : View.GONE);
grpAttachments.setVisibility(message.attachments > 0 && show_expanded ? View.VISIBLE : View.GONE);
bnvActions.setVisibility(viewType == ViewType.THREAD && show_expanded ? View.INVISIBLE : View.GONE);
@ -474,6 +496,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
ibQuotes.setVisibility(viewType == ViewType.THREAD && show_expanded ? View.INVISIBLE : View.GONE);
ibImages.setVisibility(viewType == ViewType.THREAD && show_expanded ? View.INVISIBLE : View.GONE);
pbBody.setVisibility(View.GONE);
tvNoInternetBody.setVisibility(View.GONE);
bnvActions.setTag(null);
@ -521,7 +544,10 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
Spanned body = properties.getBody(message.id);
tvBody.setText(body);
tvBody.setMovementMethod(new UrlHandler());
pbBody.setVisibility(View.VISIBLE);
if (internet || message.content)
pbBody.setVisibility(View.VISIBLE);
else
tvNoInternetBody.setVisibility(View.VISIBLE);
if (body == null && message.content) {
Bundle args = new Bundle();
@ -538,6 +564,15 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
adapter.set(attachments);
boolean downloading = false;
for (EntityAttachment attachment : attachments)
if (attachment.progress != null) {
downloading = true;
break;
}
tvNoInternetAttachments.setVisibility(downloading && !internet ? View.VISIBLE : View.GONE);
if (message.content) {
Bundle args = new Bundle();
args.putSerializable("message", message);
@ -1228,7 +1263,10 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
properties.setValue("headers", data.message.id, show_headers);
if (show_headers && data.message.headers == null) {
grpHeaders.setVisibility(View.VISIBLE);
pbHeaders.setVisibility(View.VISIBLE);
if (internet)
pbHeaders.setVisibility(View.VISIBLE);
else
tvNoInternetHeaders.setVisibility(View.VISIBLE);
Bundle args = new Bundle();
args.putLong("id", data.message.id);
@ -1644,6 +1682,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
this.viewType = viewType;
this.outgoing = outgoing;
this.zoom = zoom;
this.internet = (Helper.isMetered(context, false) != null);
this.properties = properties;
SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(context);
@ -1682,6 +1721,14 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
notifyDataSetChanged();
}
void checkInternet() {
boolean internet = (Helper.isMetered(context, false) != null);
if (this.internet != internet) {
this.internet = internet;
notifyDataSetChanged();
}
}
@Override
public int getItemCount() {
return differ.getItemCount();

View File

@ -30,6 +30,10 @@ import android.content.pm.PackageManager;
import android.database.Cursor;
import android.graphics.Typeface;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
@ -129,7 +133,9 @@ public class FragmentCompose extends FragmentEx {
private ImageView ivBccAdd;
private EditText etSubject;
private RecyclerView rvAttachment;
private TextView tvNoInternetAttachments;
private EditText etBody;
private TextView tvNoInternet;
private TextView tvSignature;
private TextView tvReference;
private BottomNavigationView edit_bar;
@ -178,7 +184,9 @@ public class FragmentCompose extends FragmentEx {
ivBccAdd = view.findViewById(R.id.ivBccAdd);
etSubject = view.findViewById(R.id.etSubject);
rvAttachment = view.findViewById(R.id.rvAttachment);
tvNoInternetAttachments = view.findViewById(R.id.tvNoInternetAttachments);
etBody = view.findViewById(R.id.etBody);
tvNoInternet = view.findViewById(R.id.tvNoInternet);
tvSignature = view.findViewById(R.id.tvSignature);
tvReference = view.findViewById(R.id.tvReference);
edit_bar = view.findViewById(R.id.edit_bar);
@ -302,12 +310,13 @@ public class FragmentCompose extends FragmentEx {
setSubtitle(R.string.title_compose);
tvExtraPrefix.setText(null);
tvExtraSuffix.setText(null);
etBody.setText(null);
grpHeader.setVisibility(View.GONE);
grpExtra.setVisibility(View.GONE);
grpAddresses.setVisibility(View.GONE);
grpAttachments.setVisibility(View.GONE);
etBody.setVisibility(View.GONE);
tvNoInternet.setVisibility(View.GONE);
grpSignature.setVisibility(View.GONE);
grpReference.setVisibility(View.GONE);
edit_bar.setVisibility(View.GONE);
@ -386,6 +395,8 @@ public class FragmentCompose extends FragmentEx {
adapter = new AdapterAttachment(getContext(), getViewLifecycleOwner(), false);
rvAttachment.setAdapter(adapter);
tvNoInternetAttachments.setVisibility(View.GONE);
pgpService = new OpenPgpServiceConnection(getContext(), "org.sufficientlysecure.keychain");
pgpService.bindToService();
@ -455,6 +466,11 @@ public class FragmentCompose extends FragmentEx {
public void onResume() {
super.onResume();
ConnectivityManager cm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkRequest.Builder builder = new NetworkRequest.Builder();
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
cm.registerNetworkCallback(builder.build(), networkCallback);
if (!pgpService.isBound())
pgpService.bindToService();
}
@ -463,7 +479,48 @@ public class FragmentCompose extends FragmentEx {
public void onPause() {
if (autosave)
onAction(R.id.action_save);
super.onPause();
ConnectivityManager cm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
cm.unregisterNetworkCallback(networkCallback);
}
ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
check();
}
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
check();
}
@Override
public void onLost(Network network) {
check();
}
private void check() {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED))
checkInternet();
}
});
}
};
private void checkInternet() {
boolean internet = (Helper.isMetered(getContext(), false) != null);
Boolean content = (Boolean) tvNoInternet.getTag();
tvNoInternet.setVisibility(!internet && content != null && !content ? View.VISIBLE : View.GONE);
Boolean downloading = (Boolean) rvAttachment.getTag();
tvNoInternetAttachments.setVisibility(!internet && downloading != null && downloading ? View.VISIBLE : View.GONE);
}
@Override
@ -1448,6 +1505,16 @@ public class FragmentCompose extends FragmentEx {
adapter.set(attachments);
grpAttachments.setVisibility(attachments.size() > 0 ? View.VISIBLE : View.GONE);
boolean downloading = false;
for (EntityAttachment attachment : attachments)
if (attachment.progress != null) {
downloading = true;
break;
}
rvAttachment.setTag(downloading);
checkInternet();
}
});
@ -1457,73 +1524,77 @@ public class FragmentCompose extends FragmentEx {
// Draft was deleted
if (draft == null || draft.ui_hide)
finish();
else if (draft.content && state == State.NONE) {
state = State.LOADING;
else {
tvNoInternet.setTag(draft.content);
checkInternet();
Bundle args = new Bundle();
args.putLong("id", result.draft.id);
if (result.draft.replying != null)
args.putLong("reference", result.draft.replying);
else if (result.draft.forwarding != null)
args.putLong("reference", result.draft.forwarding);
if (draft.content && state == State.NONE) {
state = State.LOADING;
new SimpleTask<Spanned[]>() {
@Override
protected Spanned[] onLoad(final Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
final long reference = args.getLong("reference", -1);
Bundle args = new Bundle();
args.putLong("id", result.draft.id);
if (result.draft.replying != null)
args.putLong("reference", result.draft.replying);
else if (result.draft.forwarding != null)
args.putLong("reference", result.draft.forwarding);
String body = EntityMessage.read(context, id);
String quote = (reference < 0 ? null : HtmlHelper.getQuote(context, reference, true));
new SimpleTask<Spanned[]>() {
@Override
protected Spanned[] onLoad(final Context context, Bundle args) throws Throwable {
long id = args.getLong("id");
final long reference = args.getLong("reference", -1);
return new Spanned[]{
Html.fromHtml(body, cidGetter, null),
quote == null ? null : Html.fromHtml(quote,
new Html.ImageGetter() {
@Override
public Drawable getDrawable(String source) {
return HtmlHelper.decodeImage(source, context, reference, false);
}
},
null)};
}
String body = EntityMessage.read(context, id);
String quote = (reference < 0 ? null : HtmlHelper.getQuote(context, reference, true));
@Override
protected void onLoaded(Bundle args, Spanned[] texts) {
etBody.setText(texts[0]);
etBody.setSelection(0);
tvReference.setText(texts[1]);
return new Spanned[]{
Html.fromHtml(body, cidGetter, null),
quote == null ? null : Html.fromHtml(quote,
new Html.ImageGetter() {
@Override
public Drawable getDrawable(String source) {
return HtmlHelper.decodeImage(source, context, reference, false);
}
},
null)};
}
state = State.LOADED;
autosave = true;
@Override
protected void onLoaded(Bundle args, Spanned[] texts) {
etBody.setText(texts[0]);
etBody.setSelection(0);
tvReference.setText(texts[1]);
pbWait.setVisibility(View.GONE);
etBody.setVisibility(View.VISIBLE);
grpReference.setVisibility(texts[1] == null ? View.GONE : View.VISIBLE);
edit_bar.setVisibility(View.VISIBLE);
bottom_navigation.setVisibility(View.VISIBLE);
Helper.setViewsEnabled(view, true);
state = State.LOADED;
autosave = true;
getActivity().invalidateOptionsMenu();
pbWait.setVisibility(View.GONE);
grpReference.setVisibility(texts[1] == null ? View.GONE : View.VISIBLE);
edit_bar.setVisibility(View.VISIBLE);
bottom_navigation.setVisibility(View.VISIBLE);
Helper.setViewsEnabled(view, true);
new Handler().post(new Runnable() {
@Override
public void run() {
if (TextUtils.isEmpty(etTo.getText()))
etTo.requestFocus();
else if (TextUtils.isEmpty(etSubject.getText()))
etSubject.requestFocus();
else
etBody.requestFocus();
}
});
}
getActivity().invalidateOptionsMenu();
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex);
}
}.load(FragmentCompose.this, args);
new Handler().post(new Runnable() {
@Override
public void run() {
if (TextUtils.isEmpty(etTo.getText()))
etTo.requestFocus();
else if (TextUtils.isEmpty(etSubject.getText()))
etSubject.requestFocus();
else
etBody.requestFocus();
}
});
}
@Override
protected void onException(Bundle args, Throwable ex) {
Helper.unexpectedError(getContext(), getViewLifecycleOwner(), ex);
}
}.load(FragmentCompose.this, args);
}
}
}
});

View File

@ -26,6 +26,10 @@ import android.content.SharedPreferences;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.drawable.Drawable;
import android.net.ConnectivityManager;
import android.net.Network;
import android.net.NetworkCapabilities;
import android.net.NetworkRequest;
import android.os.Bundle;
import android.os.Handler;
import android.preference.PreferenceManager;
@ -1422,8 +1426,48 @@ public class FragmentMessages extends FragmentEx {
public void onResume() {
super.onResume();
grpSupport.setVisibility(Helper.isPro(getContext()) ? View.GONE : View.VISIBLE);
ConnectivityManager cm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkRequest.Builder builder = new NetworkRequest.Builder();
builder.addCapability(NetworkCapabilities.NET_CAPABILITY_INTERNET);
cm.registerNetworkCallback(builder.build(), networkCallback);
}
@Override
public void onPause() {
super.onPause();
ConnectivityManager cm = (ConnectivityManager) getContext().getSystemService(Context.CONNECTIVITY_SERVICE);
cm.unregisterNetworkCallback(networkCallback);
}
ConnectivityManager.NetworkCallback networkCallback = new ConnectivityManager.NetworkCallback() {
@Override
public void onAvailable(Network network) {
check();
}
@Override
public void onCapabilitiesChanged(Network network, NetworkCapabilities networkCapabilities) {
check();
}
@Override
public void onLost(Network network) {
check();
}
private void check() {
getActivity().runOnUiThread(new Runnable() {
@Override
public void run() {
if (getLifecycle().getCurrentState().isAtLeast(Lifecycle.State.RESUMED))
adapter.checkInternet();
}
});
}
};
@Override
public void onCreateOptionsMenu(Menu menu, MenuInflater inflater) {
inflater.inflate(R.menu.menu_messages, menu);

View File

@ -185,6 +185,16 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/vSeparatorAttachments" />
<TextView
android:id="@+id/tvNoInternetAttachments"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_no_internet"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toEndOf="@id/rvAttachment"
app:layout_constraintStart_toStartOf="@id/rvAttachment"
app:layout_constraintTop_toBottomOf="@id/rvAttachment" />
<View
android:id="@+id/vSeparator"
android:layout_width="match_parent"
@ -192,7 +202,7 @@
android:layout_marginTop="6dp"
android:background="?attr/colorSeparator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/rvAttachment" />
app:layout_constraintTop_toBottomOf="@+id/tvNoInternetAttachments" />
<EditText
android:id="@+id/etBody"
@ -210,6 +220,17 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/vSeparator" />
<TextView
android:id="@+id/tvNoInternet"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_no_internet"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintBottom_toBottomOf="@id/etBody"
app:layout_constraintEnd_toEndOf="@id/etBody"
app:layout_constraintStart_toStartOf="@id/etBody"
app:layout_constraintTop_toTopOf="@id/etBody" />
<View
android:id="@+id/vSeparatorSignature"
android:layout_width="match_parent"

View File

@ -513,6 +513,17 @@
app:layout_constraintStart_toStartOf="@id/tvHeaders"
app:layout_constraintTop_toTopOf="@id/tvHeaders" />
<TextView
android:id="@+id/tvNoInternetHeaders"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_no_internet"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintBottom_toBottomOf="@id/tvHeaders"
app:layout_constraintEnd_toEndOf="@id/tvHeaders"
app:layout_constraintStart_toStartOf="@id/tvHeaders"
app:layout_constraintTop_toTopOf="@id/tvHeaders" />
<View
android:id="@+id/vSeparatorAttachments"
android:layout_width="0dp"
@ -536,6 +547,16 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/vSeparatorAttachments" />
<TextView
android:id="@+id/tvNoInternetAttachments"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_no_internet"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toEndOf="@id/rvAttachment"
app:layout_constraintStart_toStartOf="@id/rvAttachment"
app:layout_constraintTop_toBottomOf="@id/rvAttachment" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bnvActions"
android:layout_width="0dp"
@ -547,7 +568,7 @@
app:labelVisibilityMode="labeled"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/rvAttachment"
app:layout_constraintTop_toBottomOf="@id/tvNoInternetAttachments"
app:menu="@menu/action_message" />
<Button
@ -617,6 +638,17 @@
app:layout_constraintStart_toStartOf="@id/tvBody"
app:layout_constraintTop_toTopOf="@id/tvBody" />
<TextView
android:id="@+id/tvNoInternetBody"
android:layout_width="wrap_content"
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_constraintEnd_toEndOf="@id/tvBody"
app:layout_constraintStart_toStartOf="@id/tvBody"
app:layout_constraintTop_toTopOf="@id/tvBody" />
<View
android:id="@+id/vSeparator"
android:layout_width="match_parent"

View File

@ -510,6 +510,17 @@
app:layout_constraintStart_toStartOf="@id/tvHeaders"
app:layout_constraintTop_toTopOf="@id/tvHeaders" />
<TextView
android:id="@+id/tvNoInternetHeaders"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_no_internet"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintBottom_toBottomOf="@id/tvHeaders"
app:layout_constraintEnd_toEndOf="@id/tvHeaders"
app:layout_constraintStart_toStartOf="@id/tvHeaders"
app:layout_constraintTop_toTopOf="@id/tvHeaders" />
<View
android:id="@+id/vSeparatorAttachments"
android:layout_width="0dp"
@ -533,6 +544,16 @@
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/vSeparatorAttachments" />
<TextView
android:id="@+id/tvNoInternetAttachments"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/title_no_internet"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintEnd_toEndOf="@id/rvAttachment"
app:layout_constraintStart_toStartOf="@id/rvAttachment"
app:layout_constraintTop_toBottomOf="@id/rvAttachment" />
<com.google.android.material.bottomnavigation.BottomNavigationView
android:id="@+id/bnvActions"
android:layout_width="0dp"
@ -544,7 +565,7 @@
app:labelVisibilityMode="labeled"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/rvAttachment"
app:layout_constraintTop_toBottomOf="@id/tvNoInternetAttachments"
app:menu="@menu/action_message" />
<Button
@ -614,6 +635,17 @@
app:layout_constraintStart_toStartOf="@id/tvBody"
app:layout_constraintTop_toTopOf="@id/tvBody" />
<TextView
android:id="@+id/tvNoInternetBody"
android:layout_width="wrap_content"
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_constraintEnd_toEndOf="@id/tvBody"
app:layout_constraintStart_toStartOf="@id/tvBody"
app:layout_constraintTop_toTopOf="@id/tvBody" />
<View
android:id="@+id/vSeparator"
android:layout_width="match_parent"

View File

@ -273,6 +273,7 @@
<string name="title_no_saf">Storage access framework not available</string>
<string name="title_no_stream">An outdated app sent a file path instead of a file stream</string>
<string name="title_no_contacts">Contact picker not available</string>
<string name="title_no_internet">No internet connection</string>
<string name="title_attachment_saved">Attachment saved</string>
<string name="title_attachment_unavailable">Some attachments are not downloaded and will not be added, continue?</string>
<string name="title_image_unavailable">Some images are not downloaded and will not be added, continue?</string>