mirror of https://github.com/M66B/FairEmail.git
Added calendar replies
This commit is contained in:
parent
29dc3a9f8d
commit
189a24bc8b
|
@ -50,6 +50,7 @@ This app starts a foreground service with a low priority status bar notification
|
|||
* Send messages after selected time
|
||||
* Synchronization scheduling ([instructions](https://github.com/M66B/open-source-email/blob/master/FAQ.md#user-content-faq78))
|
||||
* Reply templates
|
||||
* Accept/decline calendar invitations
|
||||
* Filter rules ([instructions](https://github.com/M66B/open-source-email/blob/master/FAQ.md#user-content-faq71))
|
||||
* Search on device or server ([instructions](https://github.com/M66B/open-source-email/blob/master/FAQ.md#user-content-faq13))
|
||||
* Keyword management
|
||||
|
|
|
@ -124,7 +124,10 @@ import javax.mail.internet.InternetAddress;
|
|||
import biweekly.Biweekly;
|
||||
import biweekly.ICalendar;
|
||||
import biweekly.component.VEvent;
|
||||
import biweekly.parameter.ParticipationStatus;
|
||||
import biweekly.property.Attendee;
|
||||
import biweekly.property.Method;
|
||||
import biweekly.property.Organizer;
|
||||
import biweekly.property.Summary;
|
||||
import biweekly.util.ICalDate;
|
||||
|
||||
|
@ -264,6 +267,9 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
private TextView tvCalendarStart;
|
||||
private TextView tvCalendarEnd;
|
||||
private TextView tvAttendees;
|
||||
private Button btnCalendarAccept;
|
||||
private Button btnCalendarDecline;
|
||||
private Button btnCalendarMaybe;
|
||||
private ContentLoadingProgressBar pbCalendarWait;
|
||||
|
||||
private RecyclerView rvImage;
|
||||
|
@ -271,6 +277,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
private Group grpAddresses;
|
||||
private Group grpHeaders;
|
||||
private Group grpCalendar;
|
||||
private Group grpCalendarResponse;
|
||||
private Group grpAttachments;
|
||||
private Group grpImages;
|
||||
|
||||
|
@ -351,7 +358,9 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
tvCalendarStart = view.findViewById(R.id.tvCalendarStart);
|
||||
tvCalendarEnd = view.findViewById(R.id.tvCalendarEnd);
|
||||
tvAttendees = view.findViewById(R.id.tvAttendees);
|
||||
|
||||
btnCalendarAccept = view.findViewById(R.id.btnCalendarAccept);
|
||||
btnCalendarDecline = view.findViewById(R.id.btnCalendarDecline);
|
||||
btnCalendarMaybe = view.findViewById(R.id.btnCalendarMaybe);
|
||||
pbCalendarWait = view.findViewById(R.id.pbCalendarWait);
|
||||
|
||||
rvAttachment = attachments.findViewById(R.id.rvAttachment);
|
||||
|
@ -389,6 +398,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
grpAddresses = itemView.findViewById(R.id.grpAddresses);
|
||||
grpHeaders = itemView.findViewById(R.id.grpHeaders);
|
||||
grpCalendar = itemView.findViewById(R.id.grpCalendar);
|
||||
grpCalendarResponse = itemView.findViewById(R.id.grpCalendarResponse);
|
||||
grpAttachments = attachments.findViewById(R.id.grpAttachments);
|
||||
grpImages = itemView.findViewById(R.id.grpImages);
|
||||
}
|
||||
|
@ -431,6 +441,10 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
ibImages.setOnClickListener(this);
|
||||
ibFull.setOnClickListener(this);
|
||||
|
||||
btnCalendarAccept.setOnClickListener(this);
|
||||
btnCalendarDecline.setOnClickListener(this);
|
||||
btnCalendarMaybe.setOnClickListener(this);
|
||||
|
||||
bnvActions.setOnNavigationItemSelectedListener(this);
|
||||
}
|
||||
|
||||
|
@ -440,18 +454,26 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
ivExpander.setOnClickListener(null);
|
||||
} else
|
||||
view.setOnClickListener(null);
|
||||
|
||||
ivSnoozed.setOnClickListener(null);
|
||||
ivFlagged.setOnClickListener(null);
|
||||
|
||||
ivExpanderAddress.setOnClickListener(null);
|
||||
ibSearchContact.setOnClickListener(null);
|
||||
ibNotifyContact.setOnClickListener(null);
|
||||
ibAddContact.setOnClickListener(null);
|
||||
|
||||
btnDownloadAttachments.setOnClickListener(null);
|
||||
btnSaveAttachments.setOnClickListener(null);
|
||||
|
||||
tbHtml.setOnCheckedChangeListener(null);
|
||||
ibImages.setOnClickListener(null);
|
||||
ibFull.setOnClickListener(null);
|
||||
|
||||
btnCalendarAccept.setOnClickListener(null);
|
||||
btnCalendarDecline.setOnClickListener(null);
|
||||
btnCalendarMaybe.setOnClickListener(null);
|
||||
|
||||
bnvActions.setOnNavigationItemSelectedListener(null);
|
||||
}
|
||||
|
||||
|
@ -716,6 +738,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
grpAddresses.setVisibility(View.GONE);
|
||||
grpHeaders.setVisibility(View.GONE);
|
||||
grpCalendar.setVisibility(View.GONE);
|
||||
grpCalendarResponse.setVisibility(View.GONE);
|
||||
grpAttachments.setVisibility(View.GONE);
|
||||
grpImages.setVisibility(View.GONE);
|
||||
|
||||
|
@ -1042,11 +1065,12 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
if (show_inline || !inline)
|
||||
a.add(attachment);
|
||||
|
||||
if (attachment.available && "text/calendar".endsWith(attachment.type)) {
|
||||
if (attachment.available && "text/calendar".equals(attachment.type)) {
|
||||
// https://tools.ietf.org/html/rfc5546
|
||||
calendar = true;
|
||||
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", message.id);
|
||||
args.putSerializable("file", attachment.getFile(context));
|
||||
|
||||
new SimpleTask<ICalendar>() {
|
||||
|
@ -1069,11 +1093,17 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
|
||||
@Override
|
||||
protected void onExecuted(Bundle args, ICalendar icalendar) {
|
||||
long id = args.getLong("id");
|
||||
TupleMessageEx amessage = getMessage();
|
||||
if (amessage == null || !amessage.id.equals(id))
|
||||
return;
|
||||
|
||||
if (icalendar == null || icalendar.getEvents().size() == 0)
|
||||
return;
|
||||
|
||||
DateFormat df = SimpleDateFormat.getDateTimeInstance();
|
||||
|
||||
Method method = icalendar.getMethod();
|
||||
VEvent event = icalendar.getEvents().get(0);
|
||||
|
||||
Summary summary = event.getSummary();
|
||||
|
@ -1096,6 +1126,8 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
}
|
||||
}
|
||||
|
||||
Organizer organizer = event.getOrganizer();
|
||||
|
||||
tvCalendarSummary.setText(summary == null ? null : summary.getValue());
|
||||
tvCalendarSummary.setVisibility(summary == null ? View.GONE : View.VISIBLE);
|
||||
|
||||
|
@ -1107,6 +1139,12 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
|
||||
tvAttendees.setText(TextUtils.join(", ", attendee));
|
||||
tvAttendees.setVisibility(attendee.size() == 0 ? View.GONE : View.VISIBLE);
|
||||
|
||||
boolean canRespond =
|
||||
(method != null && method.isRequest() &&
|
||||
organizer != null && organizer.getEmail() != null &&
|
||||
message.to != null && message.to.length > 0);
|
||||
grpCalendarResponse.setVisibility(canRespond ? View.VISIBLE : View.GONE);
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -1125,6 +1163,7 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
tvAttendees.setVisibility(View.GONE);
|
||||
pbCalendarWait.setVisibility(View.GONE);
|
||||
grpCalendar.setVisibility(View.GONE);
|
||||
grpCalendarResponse.setVisibility(View.GONE);
|
||||
}
|
||||
|
||||
cbInline.setOnCheckedChangeListener(null);
|
||||
|
@ -1162,6 +1201,86 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
showText(message);
|
||||
}
|
||||
|
||||
private void onActionCalendar(TupleMessageEx message, int action) {
|
||||
Bundle args = new Bundle();
|
||||
args.putLong("id", message.id);
|
||||
args.putInt("action", action);
|
||||
|
||||
new SimpleTask<File>() {
|
||||
@Override
|
||||
protected File onExecute(Context context, Bundle args) throws Throwable {
|
||||
long id = args.getLong("id");
|
||||
int action = args.getInt("action");
|
||||
|
||||
DB db = DB.getInstance(context);
|
||||
|
||||
EntityMessage message = db.message().getMessage(id);
|
||||
if (message == null)
|
||||
return null;
|
||||
|
||||
List<EntityAttachment> attachments = db.attachment().getAttachments(id);
|
||||
for (EntityAttachment attachment : attachments)
|
||||
if (attachment.available && "text/calendar".equals(attachment.type)) {
|
||||
File file = attachment.getFile(context);
|
||||
ICalendar icalendar = Biweekly.parse(file).first();
|
||||
VEvent event = icalendar.getEvents().get(0);
|
||||
|
||||
// https://tools.ietf.org/html/rfc5546#section-4.2.2
|
||||
VEvent ev = new VEvent();
|
||||
ev.setOrganizer(event.getOrganizer());
|
||||
ev.setUid(event.getUid());
|
||||
if (event.getSequence() != null)
|
||||
ev.setSequence(event.getSequence());
|
||||
|
||||
InternetAddress to = (InternetAddress) message.to[0];
|
||||
Attendee attendee = new Attendee(to.getPersonal(), to.getAddress());
|
||||
|
||||
switch (action) {
|
||||
case R.id.btnCalendarAccept:
|
||||
attendee.setParticipationStatus(ParticipationStatus.ACCEPTED);
|
||||
break;
|
||||
case R.id.btnCalendarDecline:
|
||||
attendee.setParticipationStatus(ParticipationStatus.DECLINED);
|
||||
break;
|
||||
case R.id.btnCalendarMaybe:
|
||||
attendee.setParticipationStatus(ParticipationStatus.TENTATIVE);
|
||||
break;
|
||||
}
|
||||
|
||||
ev.addAttendee(attendee);
|
||||
|
||||
ICalendar response = new ICalendar();
|
||||
response.setMethod(Method.REPLY);
|
||||
response.addEvent(ev);
|
||||
|
||||
File dir = new File(context.getFilesDir(), "temporary");
|
||||
if (!dir.exists())
|
||||
dir.mkdir();
|
||||
File ics = new File(dir, "meeting.ics");
|
||||
response.write(ics);
|
||||
|
||||
return ics;
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onExecuted(Bundle args, File ics) {
|
||||
Intent reply = new Intent(context, ActivityCompose.class)
|
||||
.putExtra("action", "participation")
|
||||
.putExtra("reference", args.getLong("id"))
|
||||
.putExtra("ics", ics);
|
||||
context.startActivity(reply);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onException(Bundle args, Throwable ex) {
|
||||
Helper.unexpectedError(context, owner, ex);
|
||||
}
|
||||
}.execute(context, owner, args, "message:participation");
|
||||
}
|
||||
|
||||
private TupleMessageEx getMessage() {
|
||||
int pos = getAdapterPosition();
|
||||
if (pos == RecyclerView.NO_POSITION)
|
||||
|
@ -1187,18 +1306,30 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
else if (view.getId() == R.id.ibAddContact)
|
||||
onAddContact(message);
|
||||
else if (viewType == ViewType.THREAD) {
|
||||
if (view.getId() == R.id.ivExpanderAddress)
|
||||
onToggleAddresses(message);
|
||||
else if (view.getId() == R.id.btnDownloadAttachments)
|
||||
onDownloadAttachments(message);
|
||||
else if (view.getId() == R.id.btnSaveAttachments)
|
||||
onSaveAttachments(message);
|
||||
else if (view.getId() == R.id.ibImages)
|
||||
onShowImages(message);
|
||||
else if (view.getId() == R.id.ibFull)
|
||||
onShowFull(message);
|
||||
else
|
||||
onToggleMessage(message);
|
||||
switch (view.getId()) {
|
||||
case R.id.ivExpanderAddress:
|
||||
onToggleAddresses(message);
|
||||
break;
|
||||
case R.id.btnDownloadAttachments:
|
||||
onDownloadAttachments(message);
|
||||
break;
|
||||
case R.id.btnSaveAttachments:
|
||||
onSaveAttachments(message);
|
||||
break;
|
||||
case R.id.ibImages:
|
||||
onShowImages(message);
|
||||
break;
|
||||
case R.id.ibFull:
|
||||
onShowFull(message);
|
||||
break;
|
||||
case R.id.btnCalendarAccept:
|
||||
case R.id.btnCalendarDecline:
|
||||
case R.id.btnCalendarMaybe:
|
||||
onActionCalendar(message, view.getId());
|
||||
break;
|
||||
default:
|
||||
onToggleMessage(message);
|
||||
}
|
||||
} else {
|
||||
vwRipple.setPressed(true);
|
||||
vwRipple.setPressed(false);
|
||||
|
@ -1697,11 +1828,6 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
showText(message);
|
||||
}
|
||||
|
||||
private class OriginalMessage {
|
||||
String html;
|
||||
boolean has_images;
|
||||
}
|
||||
|
||||
private String themeHtml(String html) {
|
||||
if (dark) {
|
||||
String color = String.format("#%06X", (textColorSecondary & 0xFFFFFF));
|
||||
|
@ -2199,12 +2325,6 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
});
|
||||
}
|
||||
|
||||
private class ActionData {
|
||||
boolean hasJunk;
|
||||
boolean delete;
|
||||
TupleMessageEx message;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
|
||||
ActionData data = (ActionData) bnvActions.getTag();
|
||||
|
@ -3177,6 +3297,17 @@ public class AdapterMessage extends RecyclerView.Adapter<AdapterMessage.ViewHold
|
|||
Long getKey() {
|
||||
return getKeyAtPosition(getAdapterPosition());
|
||||
}
|
||||
|
||||
private class OriginalMessage {
|
||||
String html;
|
||||
boolean has_images;
|
||||
}
|
||||
|
||||
private class ActionData {
|
||||
boolean hasJunk;
|
||||
boolean delete;
|
||||
TupleMessageEx message;
|
||||
}
|
||||
}
|
||||
|
||||
AdapterMessage(Context context, LifecycleOwner owner,
|
||||
|
|
|
@ -659,6 +659,7 @@ public class FragmentCompose extends FragmentBase {
|
|||
args.putLong("id", getArguments().getLong("id", -1));
|
||||
args.putLong("account", getArguments().getLong("account", -1));
|
||||
args.putLong("reference", getArguments().getLong("reference", -1));
|
||||
args.putSerializable("ics", getArguments().getSerializable("ics"));
|
||||
args.putBoolean("raw", getArguments().getBoolean("raw", false));
|
||||
args.putLong("answer", getArguments().getLong("answer", -1));
|
||||
args.putString("to", getArguments().getString("to"));
|
||||
|
@ -1937,6 +1938,7 @@ public class FragmentCompose extends FragmentBase {
|
|||
String action = args.getString("action");
|
||||
long id = args.getLong("id", -1);
|
||||
long reference = args.getLong("reference", -1);
|
||||
File ics = (File) args.getSerializable("ics");
|
||||
long answer = args.getLong("answer", -1);
|
||||
|
||||
Log.i("Load draft action=" + action + " id=" + id + " reference=" + reference);
|
||||
|
@ -2013,7 +2015,8 @@ public class FragmentCompose extends FragmentBase {
|
|||
body = EntityAnswer.getAnswerText(db, answer, null) + body;
|
||||
} else {
|
||||
if ("reply".equals(action) || "reply_all".equals(action) ||
|
||||
"list".equals(action) || "receipt".equals(action)) {
|
||||
"list".equals(action) || "receipt".equals(action) ||
|
||||
"participation".equals(action)) {
|
||||
if (ref.to != null && ref.to.length > 0) {
|
||||
String to = ((InternetAddress) ref.to[0]).getAddress();
|
||||
int at = to.indexOf('@');
|
||||
|
@ -2072,14 +2075,14 @@ public class FragmentCompose extends FragmentBase {
|
|||
} else if ("receipt".equals(action)) {
|
||||
draft.receipt_request = true;
|
||||
}
|
||||
|
||||
} else if ("forward".equals(action)) {
|
||||
draft.thread = draft.msgid; // new thread
|
||||
draft.from = ref.to;
|
||||
}
|
||||
|
||||
String subject = (ref.subject == null ? "" : ref.subject);
|
||||
if ("reply".equals(action) || "reply_all".equals(action)) {
|
||||
if ("reply".equals(action) || "reply_all".equals(action) ||
|
||||
"participation".equals(action)) {
|
||||
String re = context.getString(R.string.title_subject_reply, "");
|
||||
if (!prefix_once || !subject.startsWith(re))
|
||||
draft.subject = context.getString(R.string.title_subject_reply, subject);
|
||||
|
@ -2172,6 +2175,19 @@ public class FragmentCompose extends FragmentBase {
|
|||
HtmlHelper.getPreview(body),
|
||||
null);
|
||||
|
||||
if ("participation".equals(action)) {
|
||||
EntityAttachment attachment = new EntityAttachment();
|
||||
attachment.message = draft.id;
|
||||
attachment.sequence = 1;
|
||||
attachment.name = ics.getName();
|
||||
attachment.type = "text/calendar";
|
||||
attachment.size = ics.length();
|
||||
attachment.progress = null;
|
||||
attachment.available = true;
|
||||
attachment.id = db.attachment().insertAttachment(attachment);
|
||||
ics.renameTo(attachment.getFile(context));
|
||||
}
|
||||
|
||||
Core.updateMessageSize(context, draft.id);
|
||||
|
||||
// Write reference text
|
||||
|
|
|
@ -68,6 +68,9 @@ import javax.mail.internet.MimeMultipart;
|
|||
import javax.mail.internet.MimeUtility;
|
||||
import javax.mail.internet.ParseException;
|
||||
|
||||
import biweekly.Biweekly;
|
||||
import biweekly.ICalendar;
|
||||
|
||||
public class MessageHelper {
|
||||
private MimeMessage imessage;
|
||||
|
||||
|
@ -373,22 +376,38 @@ public class MessageHelper {
|
|||
for (final EntityAttachment attachment : attachments)
|
||||
if (attachment.available) {
|
||||
BodyPart bpAttachment = new MimeBodyPart();
|
||||
bpAttachment.setFileName(attachment.name);
|
||||
|
||||
File file = attachment.getFile(context);
|
||||
|
||||
FileDataSource dataSource = new FileDataSource(file);
|
||||
dataSource.setFileTypeMap(new FileTypeMap() {
|
||||
@Override
|
||||
public String getContentType(File file) {
|
||||
// https://tools.ietf.org/html/rfc6047
|
||||
if ("text/calendar".equals(attachment.type))
|
||||
try {
|
||||
ICalendar icalendar = Biweekly.parse(file).first();
|
||||
if (icalendar != null &&
|
||||
icalendar.getMethod() != null &&
|
||||
icalendar.getMethod().isReply())
|
||||
return "text/calendar" +
|
||||
"; method=REPLY" +
|
||||
"; charset=" + Charset.defaultCharset().name();
|
||||
} catch (IOException ex) {
|
||||
Log.e(ex);
|
||||
}
|
||||
|
||||
return attachment.type;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getContentType(String filename) {
|
||||
return attachment.type;
|
||||
return getContentType(new File(filename));
|
||||
}
|
||||
});
|
||||
bpAttachment.setDataHandler(new DataHandler(dataSource));
|
||||
|
||||
bpAttachment.setFileName(attachment.name);
|
||||
if (attachment.disposition != null)
|
||||
bpAttachment.setDisposition(attachment.disposition);
|
||||
if (attachment.cid != null)
|
||||
|
|
|
@ -71,12 +71,51 @@
|
|||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvCalendarEnd" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCalendarAccept"
|
||||
style="?android:attr/buttonStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="6dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:text="@string/title_icalendar_accept"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvAttendees" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCalendarDecline"
|
||||
style="?android:attr/buttonStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:text="@string/title_icalendar_decline"
|
||||
app:layout_constraintStart_toEndOf="@id/btnCalendarAccept"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvAttendees" />
|
||||
|
||||
<Button
|
||||
android:id="@+id/btnCalendarMaybe"
|
||||
style="?android:attr/buttonStyleSmall"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="12dp"
|
||||
android:layout_marginTop="12dp"
|
||||
android:minWidth="0dp"
|
||||
android:minHeight="0dp"
|
||||
android:text="@string/title_icalendar_maybe"
|
||||
app:layout_constraintStart_toEndOf="@id/btnCalendarDecline"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvAttendees" />
|
||||
|
||||
<View
|
||||
android:id="@+id/paddingBottom"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="6dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@id/tvAttendees" />
|
||||
app:layout_constraintTop_toBottomOf="@id/btnCalendarMaybe" />
|
||||
|
||||
<eu.faircode.email.ContentLoadingProgressBar
|
||||
android:id="@+id/pbCalendarWait"
|
||||
|
@ -95,4 +134,10 @@
|
|||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:constraint_referenced_ids="vSeparatorCalendar,paddingBottom" />
|
||||
|
||||
<androidx.constraintlayout.widget.Group
|
||||
android:id="@+id/grpCalendarResponse"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
app:constraint_referenced_ids="btnCalendarAccept,btnCalendarDecline,btnCalendarMaybe" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
Loading…
Reference in New Issue