Improved composing with signature, replying/forwarding

pull/146/head
M66B 6 years ago
parent e18db5c812
commit d3966263c1

File diff suppressed because it is too large Load Diff

@ -46,7 +46,7 @@ import io.requery.android.database.sqlite.RequerySQLiteOpenHelperFactory;
// https://developer.android.com/topic/libraries/architecture/room.html // https://developer.android.com/topic/libraries/architecture/room.html
@Database( @Database(
version = 3, version = 4,
entities = { entities = {
EntityIdentity.class, EntityIdentity.class,
EntityAccount.class, EntityAccount.class,
@ -138,6 +138,15 @@ public abstract class DB extends RoomDatabase {
" (SELECT account.signature FROM account WHERE account.id = identity.account)"); " (SELECT account.signature FROM account WHERE account.id = identity.account)");
} }
}) })
.addMigrations(new Migration(3, 4) {
@Override
public void migrate(SupportSQLiteDatabase db) {
Log.i(Helper.TAG, "DB migration from version " + startVersion + " to " + endVersion);
db.execSQL("ALTER TABLE `message` ADD COLUMN `forwarding` INTEGER" +
" REFERENCES `message`(`id`) ON UPDATE NO ACTION ON DELETE SET NULL");
db.execSQL("CREATE INDEX `index_message_forwarding` ON `message` (`forwarding`)");
}
})
.build(); .build();
} }

@ -20,6 +20,7 @@ package eu.faircode.email;
*/ */
import android.content.Context; import android.content.Context;
import android.text.Html;
import android.util.Log; import android.util.Log;
import java.io.BufferedReader; import java.io.BufferedReader;
@ -51,13 +52,15 @@ import static androidx.room.ForeignKey.SET_NULL;
@ForeignKey(childColumns = "account", entity = EntityAccount.class, parentColumns = "id", onDelete = CASCADE), @ForeignKey(childColumns = "account", entity = EntityAccount.class, parentColumns = "id", onDelete = CASCADE),
@ForeignKey(childColumns = "folder", entity = EntityFolder.class, parentColumns = "id", onDelete = CASCADE), @ForeignKey(childColumns = "folder", entity = EntityFolder.class, parentColumns = "id", onDelete = CASCADE),
@ForeignKey(childColumns = "identity", entity = EntityIdentity.class, parentColumns = "id", onDelete = SET_NULL), @ForeignKey(childColumns = "identity", entity = EntityIdentity.class, parentColumns = "id", onDelete = SET_NULL),
@ForeignKey(childColumns = "replying", entity = EntityMessage.class, parentColumns = "id", onDelete = SET_NULL) @ForeignKey(childColumns = "replying", entity = EntityMessage.class, parentColumns = "id", onDelete = SET_NULL),
@ForeignKey(childColumns = "forwarding", entity = EntityMessage.class, parentColumns = "id", onDelete = SET_NULL)
}, },
indices = { indices = {
@Index(value = {"account"}), @Index(value = {"account"}),
@Index(value = {"folder"}), @Index(value = {"folder"}),
@Index(value = {"identity"}), @Index(value = {"identity"}),
@Index(value = {"replying"}), @Index(value = {"replying"}),
@Index(value = {"forwarding"}),
@Index(value = {"folder", "uid", "ui_found"}, unique = true), @Index(value = {"folder", "uid", "ui_found"}, unique = true),
@Index(value = {"msgid", "folder", "ui_found"}, unique = true), @Index(value = {"msgid", "folder", "ui_found"}, unique = true),
@Index(value = {"thread"}), @Index(value = {"thread"}),
@ -80,6 +83,7 @@ public class EntityMessage implements Serializable {
public Long identity; public Long identity;
public String extra; // plus public String extra; // plus
public Long replying; public Long replying;
public Long forwarding;
public Long uid; // compose = null public Long uid; // compose = null
public String msgid; public String msgid;
public String references; public String references;
@ -178,6 +182,14 @@ public class EntityMessage implements Serializable {
} }
} }
static String getQuote(Context context, long id) throws IOException {
EntityMessage message = DB.getInstance(context).message().getMessage(id);
return String.format("<p>%s %s:</p><blockquote>%s</blockquote>",
Html.escapeHtml(new Date(message.sent == null ? message.received : message.sent).toString()),
Html.escapeHtml(MessageHelper.getFormattedAddresses(message.from, true)),
HtmlHelper.sanitize(EntityMessage.read(context, id)));
}
@Override @Override
public boolean equals(Object obj) { public boolean equals(Object obj) {
if (obj instanceof EntityMessage) { if (obj instanceof EntityMessage) {
@ -186,6 +198,7 @@ public class EntityMessage implements Serializable {
this.folder.equals(other.folder) && this.folder.equals(other.folder) &&
(this.identity == null ? other.identity == null : this.identity.equals(other.identity)) && (this.identity == null ? other.identity == null : this.identity.equals(other.identity)) &&
(this.replying == null ? other.replying == null : this.replying.equals(other.replying)) && (this.replying == null ? other.replying == null : this.replying.equals(other.replying)) &&
(this.forwarding == null ? other.forwarding == null : this.replying.equals(other.forwarding)) &&
(this.uid == null ? other.uid == null : this.uid.equals(other.uid)) && (this.uid == null ? other.uid == null : this.uid.equals(other.uid)) &&
(this.msgid == null ? other.msgid == null : this.msgid.equals(other.msgid)) && (this.msgid == null ? other.msgid == null : this.msgid.equals(other.msgid)) &&
(this.references == null ? other.references == null : this.references.equals(other.references)) && (this.references == null ? other.references == null : this.references.equals(other.references)) &&

@ -128,6 +128,8 @@ public class FragmentCompose extends FragmentEx {
private EditText etSubject; private EditText etSubject;
private RecyclerView rvAttachment; private RecyclerView rvAttachment;
private EditText etBody; private EditText etBody;
private TextView tvSignature;
private TextView tvReference;
private BottomNavigationView edit_bar; private BottomNavigationView edit_bar;
private BottomNavigationView bottom_navigation; private BottomNavigationView bottom_navigation;
private ProgressBar pbWait; private ProgressBar pbWait;
@ -135,14 +137,23 @@ public class FragmentCompose extends FragmentEx {
private Group grpExtra; private Group grpExtra;
private Group grpAddresses; private Group grpAddresses;
private Group grpAttachments; private Group grpAttachments;
private Group grpSignature;
private Group grpReference;
private AdapterAttachment adapter; private AdapterAttachment adapter;
private long working = -1; private long working = -1;
private boolean autosave = false; private boolean autosave = false;
private boolean pro = false;
private OpenPgpServiceConnection pgpService; private OpenPgpServiceConnection pgpService;
@Override
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
pro = Helper.isPro(getContext());
}
@Override @Override
@Nullable @Nullable
public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) { public View onCreateView(@NonNull LayoutInflater inflater, @Nullable ViewGroup container, @Nullable Bundle savedInstanceState) {
@ -164,6 +175,8 @@ public class FragmentCompose extends FragmentEx {
etSubject = view.findViewById(R.id.etSubject); etSubject = view.findViewById(R.id.etSubject);
rvAttachment = view.findViewById(R.id.rvAttachment); rvAttachment = view.findViewById(R.id.rvAttachment);
etBody = view.findViewById(R.id.etBody); etBody = view.findViewById(R.id.etBody);
tvSignature = view.findViewById(R.id.tvSignature);
tvReference = view.findViewById(R.id.tvReference);
edit_bar = view.findViewById(R.id.edit_bar); edit_bar = view.findViewById(R.id.edit_bar);
bottom_navigation = view.findViewById(R.id.bottom_navigation); bottom_navigation = view.findViewById(R.id.bottom_navigation);
pbWait = view.findViewById(R.id.pbWait); pbWait = view.findViewById(R.id.pbWait);
@ -171,6 +184,8 @@ public class FragmentCompose extends FragmentEx {
grpExtra = view.findViewById(R.id.grpExtra); grpExtra = view.findViewById(R.id.grpExtra);
grpAddresses = view.findViewById(R.id.grpAddresses); grpAddresses = view.findViewById(R.id.grpAddresses);
grpAttachments = view.findViewById(R.id.grpAttachments); grpAttachments = view.findViewById(R.id.grpAttachments);
grpSignature = view.findViewById(R.id.grpSignature);
grpReference = view.findViewById(R.id.grpReference);
// Wire controls // Wire controls
spIdentity.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { spIdentity.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() {
@ -180,17 +195,9 @@ public class FragmentCompose extends FragmentEx {
int at = (identity == null ? -1 : identity.email.indexOf('@')); int at = (identity == null ? -1 : identity.email.indexOf('@'));
tvExtraPrefix.setText(at < 0 ? null : identity.email.substring(0, at) + "+"); tvExtraPrefix.setText(at < 0 ? null : identity.email.substring(0, at) + "+");
tvExtraSuffix.setText(at < 0 ? null : identity.email.substring(at)); tvExtraSuffix.setText(at < 0 ? null : identity.email.substring(at));
if (pro) {
String signature = (identity == null ? null : identity.signature); tvSignature.setText(identity == null ? null : Html.fromHtml(identity.signature));
if (TextUtils.isEmpty(signature)) grpSignature.setVisibility(identity == null || TextUtils.isEmpty(identity.signature) ? View.GONE : View.VISIBLE);
signature = "&zwnj;";
String html = Html.toHtml(etBody.getText());
int cstart = html.indexOf("<tt>");
int cend = html.lastIndexOf("</tt>");
if (cstart >= 0 && cend > cstart) {
html = html.substring(0, cstart + 4) + signature + html.substring(cend);
etBody.setText(Html.fromHtml(html));
} }
} }
@ -198,6 +205,8 @@ public class FragmentCompose extends FragmentEx {
public void onNothingSelected(AdapterView<?> parent) { public void onNothingSelected(AdapterView<?> parent) {
tvExtraPrefix.setText(null); tvExtraPrefix.setText(null);
tvExtraSuffix.setText(null); tvExtraSuffix.setText(null);
tvSignature.setText(null);
grpSignature.setVisibility(View.GONE);
} }
}); });
@ -298,6 +307,8 @@ public class FragmentCompose extends FragmentEx {
grpAddresses.setVisibility(View.GONE); grpAddresses.setVisibility(View.GONE);
grpAttachments.setVisibility(View.GONE); grpAttachments.setVisibility(View.GONE);
etBody.setVisibility(View.GONE); etBody.setVisibility(View.GONE);
grpSignature.setVisibility(View.GONE);
grpReference.setVisibility(View.GONE);
edit_bar.setVisibility(View.GONE); edit_bar.setVisibility(View.GONE);
bottom_navigation.setVisibility(View.GONE); bottom_navigation.setVisibility(View.GONE);
pbWait.setVisibility(View.VISIBLE); pbWait.setVisibility(View.VISIBLE);
@ -640,7 +651,7 @@ public class FragmentCompose extends FragmentEx {
Properties props = MessageHelper.getSessionProperties(Helper.AUTH_TYPE_PASSWORD, false); Properties props = MessageHelper.getSessionProperties(Helper.AUTH_TYPE_PASSWORD, false);
Session isession = Session.getInstance(props, null); Session isession = Session.getInstance(props, null);
MimeMessage imessage = new MimeMessage(isession); MimeMessage imessage = new MimeMessage(isession);
MessageHelper.build(context, message, attachments, imessage); MessageHelper.build(context, message, imessage);
// Serialize message // Serialize message
ByteArrayOutputStream os = new ByteArrayOutputStream(); ByteArrayOutputStream os = new ByteArrayOutputStream();
@ -918,6 +929,7 @@ public class FragmentCompose extends FragmentEx {
UnderlineSpan[] uspans = spannable.getSpans(0, spannable.length(), UnderlineSpan.class); UnderlineSpan[] uspans = spannable.getSpans(0, spannable.length(), UnderlineSpan.class);
for (UnderlineSpan uspan : uspans) for (UnderlineSpan uspan : uspans)
spannable.removeSpan(uspan); spannable.removeSpan(uspan);
args.putString("body", Html.toHtml(spannable)); args.putString("body", Html.toHtml(spannable));
Log.i(Helper.TAG, "Run load id=" + working); Log.i(Helper.TAG, "Run load id=" + working);
@ -1026,7 +1038,6 @@ public class FragmentCompose extends FragmentEx {
long reference = args.getLong("reference", -1); long reference = args.getLong("reference", -1);
boolean raw = args.getBoolean("raw", false); boolean raw = args.getBoolean("raw", false);
long answer = args.getLong("answer", -1); long answer = args.getLong("answer", -1);
boolean pro = Helper.isPro(getContext());
Log.i(Helper.TAG, "Load draft action=" + action + " id=" + id + " reference=" + reference); Log.i(Helper.TAG, "Load draft action=" + action + " id=" + id + " reference=" + reference);
@ -1140,9 +1151,6 @@ public class FragmentCompose extends FragmentEx {
body = ""; body = "";
else else
body = body.replaceAll("\\r?\\n", "<br />"); body = body.replaceAll("\\r?\\n", "<br />");
if (pro)
body += "<p>&zwnj;</p><p><tt>&zwnj;</tt></p>";
} else { } else {
result.draft.thread = ref.thread; result.draft.thread = ref.thread;
@ -1170,28 +1178,14 @@ public class FragmentCompose extends FragmentEx {
} }
} else if ("forward".equals(action)) { } else if ("forward".equals(action)) {
//msg.replying = ref.id; result.draft.forwarding = ref.id;
result.draft.from = ref.to; result.draft.from = ref.to;
} }
long time = (ref.sent == null ? ref.received : ref.sent); if ("reply".equals(action) || "reply_all".equals(action))
if ("reply".equals(action) || "reply_all".equals(action)) {
result.draft.subject = context.getString(R.string.title_subject_reply, ref.subject); result.draft.subject = context.getString(R.string.title_subject_reply, ref.subject);
body = String.format("<p>%s %s:</p><blockquote>%s</blockquote>", else if ("forward".equals(action))
Html.escapeHtml(new Date(time).toString()),
Html.escapeHtml(MessageHelper.getFormattedAddresses(result.draft.to, true)),
HtmlHelper.sanitize(ref.read(context)));
} else if ("forward".equals(action)) {
result.draft.subject = context.getString(R.string.title_subject_forward, ref.subject); result.draft.subject = context.getString(R.string.title_subject_forward, ref.subject);
body = String.format("<p>%s %s:</p><blockquote>%s</blockquote>",
Html.escapeHtml(new Date(time).toString()),
Html.escapeHtml(MessageHelper.getFormattedAddresses(ref.from, true)),
HtmlHelper.sanitize(ref.read(context)));
}
if (pro)
body = "<p><tt>&zwnj;</tt></p>" + body;
if (answer > 0 && ("reply".equals(action) || "reply_all".equals(action))) { if (answer > 0 && ("reply".equals(action) || "reply_all".equals(action))) {
String text = db.answer().getAnswer(answer).text; String text = db.answer().getAnswer(answer).text;
@ -1206,8 +1200,7 @@ public class FragmentCompose extends FragmentEx {
text = text.replace("$email$", email == null ? "" : email); text = text.replace("$email$", email == null ? "" : email);
body = text + body; body = text + body;
} else }
body = "<p>&zwnj;</p>" + body;
} }
result.draft.content = true; result.draft.content = true;
@ -1302,22 +1295,36 @@ public class FragmentCompose extends FragmentEx {
etBody.setText(null); etBody.setText(null);
Bundle a = new Bundle(); final Bundle a = new Bundle();
a.putLong("id", result.draft.id); a.putLong("id", result.draft.id);
if (result.draft.replying != null)
a.putLong("reference", result.draft.replying);
else if (result.draft.forwarding != null)
a.putLong("reference", result.draft.forwarding);
new SimpleTask<Spanned>() { new SimpleTask<Spanned[]>() {
@Override @Override
protected Spanned onLoad(final Context context, Bundle args) throws Throwable { protected Spanned[] onLoad(final Context context, Bundle args) throws Throwable {
final long id = args.getLong("id"); long id = args.getLong("id");
long reference = args.getLong("reference", -1);
String body = EntityMessage.read(context, id); String body = EntityMessage.read(context, id);
return Html.fromHtml(body, cidGetter, null); String quote = (reference < 0 ? null : EntityMessage.getQuote(context, reference));
return new Spanned[]{
Html.fromHtml(body, cidGetter, null),
quote == null ? null : Html.fromHtml(quote)};
} }
@Override @Override
protected void onLoaded(Bundle args, Spanned body) { protected void onLoaded(Bundle args, Spanned[] texts) {
getActivity().invalidateOptionsMenu(); getActivity().invalidateOptionsMenu();
etBody.setText(body); etBody.setText(texts[0]);
etBody.setSelection(0); etBody.setSelection(0);
tvReference.setText(texts[1]);
grpReference.setVisibility(texts[1] == null ? View.GONE : View.VISIBLE);
new Handler().post(new Runnable() { new Handler().post(new Runnable() {
@Override @Override
public void run() { public void run() {

@ -173,14 +173,19 @@ public class MessageHelper {
return props; return props;
} }
static MimeMessageEx from(Context context, EntityMessage message, EntityMessage reply, List<EntityAttachment> attachments, Session isession) throws MessagingException, IOException { static MimeMessageEx from(Context context, EntityMessage message, Session isession) throws MessagingException, IOException {
DB db = DB.getInstance(context);
MimeMessageEx imessage = new MimeMessageEx(isession, message.msgid); MimeMessageEx imessage = new MimeMessageEx(isession, message.msgid);
if (reply == null) EntityMessage replying = null;
if (message.replying != null)
replying = db.message().getMessage(message.replying);
if (replying == null)
imessage.addHeader("References", message.msgid); imessage.addHeader("References", message.msgid);
else { else {
imessage.addHeader("In-Reply-To", reply.msgid); imessage.addHeader("In-Reply-To", replying.msgid);
imessage.addHeader("References", (reply.references == null ? "" : reply.references + " ") + reply.msgid); imessage.addHeader("References", (replying.references == null ? "" : replying.references + " ") + replying.msgid);
} }
imessage.setFlag(Flags.Flag.SEEN, message.seen); imessage.setFlag(Flags.Flag.SEEN, message.seen);
@ -210,6 +215,8 @@ public class MessageHelper {
imessage.setSentDate(new Date()); imessage.setSentDate(new Date());
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
if (message.from != null && message.from.length > 0) if (message.from != null && message.from.length > 0)
for (EntityAttachment attachment : attachments) for (EntityAttachment attachment : attachments)
if (attachment.available && "signature.asc".equals(attachment.name)) { if (attachment.available && "signature.asc".equals(attachment.name)) {
@ -265,14 +272,25 @@ public class MessageHelper {
return imessage; return imessage;
} }
build(context, message, attachments, imessage); build(context, message, imessage);
return imessage; return imessage;
} }
static void build(Context context, EntityMessage message, List<EntityAttachment> attachments, MimeMessage imessage) throws IOException, MessagingException { static void build(Context context, EntityMessage message, MimeMessage imessage) throws IOException, MessagingException {
DB db = DB.getInstance(context);
String body = message.read(context); String body = message.read(context);
if (Helper.isPro(context) && message.identity != null) {
EntityIdentity identity = db.identity().getIdentity(message.identity);
if (!TextUtils.isEmpty(identity.signature))
body += identity.signature;
}
if (message.replying != null || message.forwarding != null)
body += EntityMessage.getQuote(context, message.replying == null ? message.forwarding : message.replying);
BodyPart plain = new MimeBodyPart(); BodyPart plain = new MimeBodyPart();
plain.setContent(Jsoup.parse(body).text(), "text/plain; charset=" + Charset.defaultCharset().name()); plain.setContent(Jsoup.parse(body).text(), "text/plain; charset=" + Charset.defaultCharset().name());
@ -283,6 +301,7 @@ public class MessageHelper {
alternative.addBodyPart(plain); alternative.addBodyPart(plain);
alternative.addBodyPart(html); alternative.addBodyPart(html);
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
if (attachments.size() == 0) { if (attachments.size() == 0) {
imessage.setContent(alternative); imessage.setContent(alternative);
} else { } else {

@ -1398,8 +1398,7 @@ public class ServiceSynchronize extends LifecycleService {
private void doAdd(EntityFolder folder, Session isession, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) throws MessagingException, JSONException, IOException { private void doAdd(EntityFolder folder, Session isession, IMAPFolder ifolder, EntityMessage message, JSONArray jargs, DB db) throws MessagingException, JSONException, IOException {
// Append message // Append message
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id); MimeMessage imessage = MessageHelper.from(this, message, isession);
MimeMessage imessage = MessageHelper.from(this, message, null, attachments, isession);
AppendUID[] uid = ifolder.appendUIDMessages(new Message[]{imessage}); AppendUID[] uid = ifolder.appendUIDMessages(new Message[]{imessage});
db.message().setMessageUid(message.id, uid[0].uid); db.message().setMessageUid(message.id, uid[0].uid);
Log.i(Helper.TAG, "Appended uid=" + uid[0].uid); Log.i(Helper.TAG, "Appended uid=" + uid[0].uid);
@ -1432,14 +1431,12 @@ public class ServiceSynchronize extends LifecycleService {
} else { } else {
Log.w(Helper.TAG, "MOVE by DELETE/APPEND"); Log.w(Helper.TAG, "MOVE by DELETE/APPEND");
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
if (!EntityFolder.ARCHIVE.equals(folder.type)) { if (!EntityFolder.ARCHIVE.equals(folder.type)) {
imessage.setFlag(Flags.Flag.DELETED, true); imessage.setFlag(Flags.Flag.DELETED, true);
ifolder.expunge(); ifolder.expunge();
} }
MimeMessageEx icopy = MessageHelper.from(this, message, null, attachments, isession); MimeMessageEx icopy = MessageHelper.from(this, message, isession);
Folder itarget = istore.getFolder(target.name); Folder itarget = istore.getFolder(target.name);
itarget.appendMessages(new Message[]{icopy}); itarget.appendMessages(new Message[]{icopy});
} }
@ -1470,10 +1467,7 @@ public class ServiceSynchronize extends LifecycleService {
final Session isession = Session.getInstance(props, null); final Session isession = Session.getInstance(props, null);
// Create message // Create message
MimeMessage imessage; MimeMessage imessage = MessageHelper.from(this, message, isession);
EntityMessage reply = (message.replying == null ? null : db.message().getMessage(message.replying));
List<EntityAttachment> attachments = db.attachment().getAttachments(message.id);
imessage = MessageHelper.from(this, message, reply, attachments, isession);
if (ident.replyto != null) if (ident.replyto != null)
imessage.setReplyTo(new Address[]{new InternetAddress(ident.replyto)}); imessage.setReplyTo(new Address[]{new InternetAddress(ident.replyto)});

@ -205,11 +205,51 @@
android:gravity="top" android:gravity="top"
android:hint="@string/title_body_hint" android:hint="@string/title_body_hint"
android:inputType="textCapSentences|textMultiLine" android:inputType="textCapSentences|textMultiLine"
android:minHeight="480dp" android:minHeight="90dp"
android:textAppearance="@style/TextAppearance.AppCompat.Small" android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/vSeparator" /> app:layout_constraintTop_toBottomOf="@id/vSeparator" />
<View
android:id="@+id/vSeparatorSignature"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="6dp"
android:background="?attr/colorSeparator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/etBody" />
<TextView
android:id="@+id/tvSignature"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp"
android:fontFamily="monospace"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/vSeparatorSignature" />
<View
android:id="@+id/vSeparatorReference"
android:layout_width="match_parent"
android:layout_height="1dp"
android:layout_marginTop="6dp"
android:background="?attr/colorSeparator"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@+id/tvSignature" />
<TextView
android:id="@+id/tvReference"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginStart="6dp"
android:layout_marginEnd="6dp"
android:fontFamily="monospace"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/vSeparatorReference" />
<androidx.constraintlayout.widget.Group <androidx.constraintlayout.widget.Group
android:id="@+id/grpHeader" android:id="@+id/grpHeader"
android:layout_width="0dp" android:layout_width="0dp"
@ -233,6 +273,18 @@
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="0dp" android:layout_height="0dp"
app:constraint_referenced_ids="vSeparatorAttachments,rvAttachment" /> app:constraint_referenced_ids="vSeparatorAttachments,rvAttachment" />
<androidx.constraintlayout.widget.Group
android:id="@+id/grpSignature"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="vSeparatorSignature,tvSignature" />
<androidx.constraintlayout.widget.Group
android:id="@+id/grpReference"
android:layout_width="0dp"
android:layout_height="0dp"
app:constraint_referenced_ids="vSeparatorReference,tvReference" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</ScrollView> </ScrollView>

Loading…
Cancel
Save