diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index d0d5b632..985eced2 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -220,6 +220,7 @@
+
diff --git a/app/src/main/java/com/etesync/syncadapter/model/SyncEntry.java b/app/src/main/java/com/etesync/syncadapter/model/SyncEntry.java
index 449d7a04..5087ba1a 100644
--- a/app/src/main/java/com/etesync/syncadapter/model/SyncEntry.java
+++ b/app/src/main/java/com/etesync/syncadapter/model/SyncEntry.java
@@ -4,9 +4,11 @@ import com.etesync.syncadapter.GsonHelper;
import com.etesync.syncadapter.journalmanager.Crypto;
import com.etesync.syncadapter.journalmanager.JournalEntryManager;
+import java.io.Serializable;
+
import lombok.Getter;
-public class SyncEntry {
+public class SyncEntry implements Serializable {
@Getter
private String content;
@Getter
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/JournalItemActivity.java b/app/src/main/java/com/etesync/syncadapter/ui/JournalItemActivity.java
new file mode 100644
index 00000000..a66e0ad5
--- /dev/null
+++ b/app/src/main/java/com/etesync/syncadapter/ui/JournalItemActivity.java
@@ -0,0 +1,431 @@
+package com.etesync.syncadapter.ui;
+
+import android.content.Context;
+import android.content.Intent;
+import android.os.AsyncTask;
+import android.os.Bundle;
+import android.support.design.widget.TabLayout;
+import android.support.v4.app.Fragment;
+import android.support.v4.app.FragmentManager;
+import android.support.v4.app.FragmentPagerAdapter;
+import android.support.v4.view.ViewPager;
+import android.text.format.DateFormat;
+import android.text.format.DateUtils;
+import android.text.format.Time;
+import android.view.LayoutInflater;
+import android.view.View;
+import android.view.ViewGroup;
+import android.widget.TextView;
+
+import com.etesync.syncadapter.App;
+import com.etesync.syncadapter.Constants;
+import com.etesync.syncadapter.R;
+import com.etesync.syncadapter.model.CollectionInfo;
+import com.etesync.syncadapter.model.JournalEntity;
+import com.etesync.syncadapter.model.SyncEntry;
+
+import net.fortuna.ical4j.model.component.VAlarm;
+import net.fortuna.ical4j.model.property.Attendee;
+
+import org.apache.commons.codec.Charsets;
+
+import java.io.ByteArrayInputStream;
+import java.io.IOException;
+import java.io.InputStream;
+import java.util.Formatter;
+import java.util.List;
+import java.util.Locale;
+
+import at.bitfire.ical4android.Event;
+import at.bitfire.ical4android.InvalidCalendarException;
+import at.bitfire.vcard4android.Contact;
+import at.bitfire.vcard4android.LabeledProperty;
+import ezvcard.parameter.AddressType;
+import ezvcard.parameter.EmailType;
+import ezvcard.parameter.RelatedType;
+import ezvcard.parameter.TelephoneType;
+import ezvcard.property.Address;
+import ezvcard.property.Email;
+import ezvcard.property.Impp;
+import ezvcard.property.Related;
+import ezvcard.property.Telephone;
+import ezvcard.property.Url;
+import io.requery.Persistable;
+import io.requery.sql.EntityDataStore;
+
+import static com.etesync.syncadapter.ui.journalviewer.ListEntriesFragment.setJournalEntryView;
+
+public class JournalItemActivity extends BaseActivity implements Refreshable {
+ private static final String KEY_SYNC_ENTRY = "syncEntry";
+ private JournalEntity journalEntity;
+ protected CollectionInfo info;
+ private SyncEntry syncEntry;
+
+ public static Intent newIntent(Context context, CollectionInfo info, SyncEntry syncEntry) {
+ Intent intent = new Intent(context, JournalItemActivity.class);
+ intent.putExtra(Constants.KEY_COLLECTION_INFO, info);
+ intent.putExtra(KEY_SYNC_ENTRY, syncEntry);
+ return intent;
+ }
+
+ @Override
+ public void refresh() {
+ EntityDataStore data = ((App) getApplicationContext()).getData();
+
+ journalEntity = JournalEntity.fetch(data, info.getServiceEntity(data), info.uid);
+ if ((journalEntity == null) || journalEntity.isDeleted()) {
+ finish();
+ return;
+ }
+
+ info = journalEntity.getInfo();
+
+ setTitle(info.displayName);
+
+ setJournalEntryView(findViewById(R.id.journal_list_item), info, syncEntry);
+ }
+
+ @Override
+ protected void onCreate(Bundle savedInstanceState) {
+ super.onCreate(savedInstanceState);
+
+ setContentView(R.layout.journal_item_activity);
+
+ getSupportActionBar().setDisplayHomeAsUpEnabled(true);
+
+ info = (CollectionInfo) getIntent().getExtras().getSerializable(Constants.KEY_COLLECTION_INFO);
+ syncEntry = (SyncEntry) getIntent().getExtras().getSerializable(KEY_SYNC_ENTRY);
+
+ refresh();
+
+ ViewPager viewPager = (ViewPager) findViewById(R.id.viewpager);
+ viewPager.setAdapter(new TabsAdapter(getSupportFragmentManager(), info, syncEntry));
+
+ TabLayout tabLayout = (TabLayout) findViewById(R.id.tabs);
+ tabLayout.setupWithViewPager(viewPager);
+ }
+
+ private static class TabsAdapter extends FragmentPagerAdapter {
+ private CollectionInfo info;
+ private SyncEntry syncEntry;
+ public TabsAdapter(FragmentManager fm, CollectionInfo info, SyncEntry syncEntry) {
+ super(fm);
+ this.info = info;
+ this.syncEntry = syncEntry;
+ }
+
+ @Override
+ public int getCount() {
+ // FIXME: Make it depend on info type (only have non-raw for known types)
+ return 2;
+ }
+
+ @Override
+ public CharSequence getPageTitle(int position) {
+ // FIXME: use string resources
+ if (position == 0) {
+ return "Main";
+ } else {
+ return "Raw";
+ }
+ }
+
+ @Override
+ public Fragment getItem(int position) {
+ if (position == 0) {
+ return PrettyFragment.newInstance(info, syncEntry);
+ } else {
+ return TextFragment.newInstance(syncEntry);
+ }
+ }
+ }
+
+ public static class TextFragment extends Fragment {
+ public static TextFragment newInstance(SyncEntry syncEntry) {
+ TextFragment frag = new TextFragment();
+ Bundle args = new Bundle(1);
+ args.putSerializable(KEY_SYNC_ENTRY, syncEntry);
+ frag.setArguments(args);
+ return frag;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View v = inflater.inflate(R.layout.text_fragment, container, false);
+
+ TextView tv = (TextView) v.findViewById(R.id.content);
+
+ SyncEntry syncEntry = (SyncEntry) getArguments().getSerializable(KEY_SYNC_ENTRY);
+ tv.setText(syncEntry.getContent());
+
+ return v;
+ }
+ }
+
+ public static class PrettyFragment extends Fragment {
+ CollectionInfo info;
+ SyncEntry syncEntry;
+
+ public static PrettyFragment newInstance(CollectionInfo info, SyncEntry syncEntry) {
+ PrettyFragment frag = new PrettyFragment();
+ Bundle args = new Bundle(1);
+ args.putSerializable(Constants.KEY_COLLECTION_INFO, info);
+ args.putSerializable(KEY_SYNC_ENTRY, syncEntry);
+ frag.setArguments(args);
+ return frag;
+ }
+
+ @Override
+ public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
+ View v = null;
+
+ info = (CollectionInfo) getArguments().getSerializable(Constants.KEY_COLLECTION_INFO);
+ syncEntry = (SyncEntry) getArguments().getSerializable(KEY_SYNC_ENTRY);
+
+ switch (info.type) {
+ case ADDRESS_BOOK:
+ v = inflater.inflate(R.layout.contact_info, container, false);
+ new LoadContactTask(v).execute();
+ break;
+ case CALENDAR:
+ v = inflater.inflate(R.layout.event_info, container, false);
+ new LoadEventTask(v).execute();
+ break;
+ }
+
+ return v;
+ }
+
+ private class LoadEventTask extends AsyncTask {
+ View view;
+ LoadEventTask(View v) {
+ super();
+ view = v;
+ }
+ @Override
+ protected Event doInBackground(Void... aVoids) {
+ InputStream is = new ByteArrayInputStream(syncEntry.getContent().getBytes(Charsets.UTF_8));
+
+ try {
+ return Event.fromStream(is, Charsets.UTF_8, null)[0];
+ } catch (InvalidCalendarException e) {
+ e.printStackTrace();
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Event event) {
+ final View loader = view.findViewById(R.id.event_info_loading_msg);
+ loader.setVisibility(View.GONE);
+ final View contentContainer = view.findViewById(R.id.event_info_scroll_view);
+ contentContainer.setVisibility(View.VISIBLE);
+
+ setTextViewText(view, R.id.title, event.summary);
+
+ setTextViewText(view, R.id.when_datetime, getDisplayedDatetime(event.dtStart.getDate().getTime(), event.dtEnd.getDate().getTime(), event.isAllDay(), getContext()));
+
+ setTextViewText(view, R.id.where, event.location);
+
+ if (event.organizer != null) {
+ TextView tv = (TextView) view.findViewById(R.id.organizer);
+ tv.setText(event.organizer.getCalAddress().toString().replaceFirst("mailto:", ""));
+ } else {
+ View organizer = view.findViewById(R.id.organizer_container);
+ organizer.setVisibility(View.GONE);
+ }
+
+ setTextViewText(view, R.id.description, event.description);
+
+ boolean first = true;
+ StringBuilder sb = new StringBuilder();
+ for (Attendee attendee : event.attendees) {
+ if (first) {
+ first = false;
+ sb.append("Attendees: ");
+ } else {
+ sb.append(", ");
+ }
+ sb.append(attendee.getCalAddress().toString().replaceFirst("mailto:", ""));
+ }
+ setTextViewText(view, R.id.attendees, sb.toString());
+
+ first = true;
+ sb = new StringBuilder();
+ for (VAlarm alarm : event.alarms) {
+ if (first) {
+ first = false;
+ sb.append("Reminders: ");
+ } else {
+ sb.append(", ");
+ }
+ sb.append(alarm.getTrigger().getValue());
+ }
+ setTextViewText(view, R.id.reminders, sb.toString());
+ }
+ }
+
+ private class LoadContactTask extends AsyncTask {
+ View view;
+
+ LoadContactTask(View v) {
+ super();
+ view = v;
+ }
+
+ @Override
+ protected Contact doInBackground(Void... aVoids) {
+ InputStream is = new ByteArrayInputStream(syncEntry.getContent().getBytes(Charsets.UTF_8));
+
+ try {
+ return Contact.fromStream(is, Charsets.UTF_8, null)[0];
+ } catch (IOException e) {
+ e.printStackTrace();
+ }
+
+ return null;
+ }
+
+ @Override
+ protected void onPostExecute(Contact contact) {
+ final View loader = view.findViewById(R.id.loading_msg);
+ loader.setVisibility(View.GONE);
+ final View contentContainer = view.findViewById(R.id.content_container);
+ contentContainer.setVisibility(View.VISIBLE);
+
+ TextView tv = (TextView) view.findViewById(R.id.display_name);
+ tv.setText(contact.displayName);
+
+ final ViewGroup mainCard = (ViewGroup) view.findViewById(R.id.main_card);
+ final ViewGroup aboutCard = (ViewGroup) view.findViewById(R.id.about_card);
+ aboutCard.findViewById(R.id.title_container).setVisibility(View.VISIBLE);
+
+ // TEL
+ for (LabeledProperty labeledPhone : contact.phoneNumbers) {
+ List types = labeledPhone.property.getTypes();
+ String type = (types.size() > 0) ? types.get(0).getValue() : null;
+ addInfoItem(view.getContext(), mainCard, "Phone", type, labeledPhone.property.getText());
+ }
+
+ // EMAIL
+ for (LabeledProperty labeledEmail : contact.emails) {
+ List types = labeledEmail.property.getTypes();
+ String type = (types.size() > 0) ? types.get(0).getValue() : null;
+ addInfoItem(view.getContext(), mainCard, "Email", type, labeledEmail.property.getValue());
+ }
+
+ // ORG, TITLE, ROLE
+ if (contact.organization != null) {
+ addInfoItem(view.getContext(), aboutCard, "Organization", contact.jobTitle, contact.organization.getValues().get(0));
+ }
+ if (contact.jobDescription != null) {
+ addInfoItem(view.getContext(), aboutCard, "Job Description", null, contact.jobTitle);
+ }
+
+ // IMPP
+ for (LabeledProperty labeledImpp : contact.impps) {
+ addInfoItem(view.getContext(), mainCard, "Instant Messaging", labeledImpp.property.getProtocol(), labeledImpp.property.getHandle());
+ }
+
+ // NICKNAME
+ if (contact.nickName != null) {
+ addInfoItem(view.getContext(), aboutCard, "Nickname", null, contact.nickName.getValues().get(0));
+ }
+
+ // ADR
+ for (LabeledProperty labeledAddress : contact.addresses) {
+ List types = labeledAddress.property.getTypes();
+ String type = (types.size() > 0) ? types.get(0).getValue() : null;
+ addInfoItem(view.getContext(), mainCard, "Address", type, labeledAddress.property.getLabel());
+ }
+
+ // NOTE
+ if (contact.note != null) {
+ addInfoItem(view.getContext(), aboutCard, "Note", null, contact.note);
+ }
+
+ // URL
+ for (LabeledProperty labeledUrl : contact.urls) {
+ addInfoItem(view.getContext(), aboutCard, "Website", null, labeledUrl.property.getValue());
+ }
+
+ // ANNIVERSARY
+ if (contact.anniversary != null) {
+ String date = getDisplayedDatetime(contact.anniversary.getDate().getTime(), contact.anniversary.getDate().getTime(), true, getContext());
+ addInfoItem(view.getContext(), aboutCard, "Anniversary", null, date);
+ }
+ // BDAY
+ if (contact.birthDay != null) {
+ String date = getDisplayedDatetime(contact.birthDay.getDate().getTime(), contact.birthDay.getDate().getTime(), true, getContext());
+ addInfoItem(view.getContext(), aboutCard, "Birthday", null, date);
+ }
+
+ // RELATED
+ for (Related related : contact.relations) {
+ List types = related.getTypes();
+ String type = (types.size() > 0) ? types.get(0).getValue() : null;
+ addInfoItem(view.getContext(), aboutCard, "Relation", type, related.getText());
+ }
+
+ // PHOTO
+ // if (contact.photo != null)
+ }
+ }
+
+ private static View addInfoItem(Context context, ViewGroup parent, String type, String label, String value) {
+ ViewGroup layout = (ViewGroup) parent.findViewById(R.id.container);
+ View infoItem = LayoutInflater.from(context).inflate(R.layout.contact_info_item, layout, false);
+ layout.addView(infoItem);
+ setTextViewText(infoItem, R.id.type, type);
+ setTextViewText(infoItem, R.id.title, label);
+ setTextViewText(infoItem, R.id.content, value);
+ parent.setVisibility(View.VISIBLE);
+
+ return infoItem;
+ }
+
+ private static void setTextViewText(View parent, int id, String text) {
+ TextView tv = (TextView) parent.findViewById(id);
+ if (text == null) {
+ tv.setVisibility(View.GONE);
+ } else {
+ tv.setText(text);
+ }
+ }
+
+ public static String getDisplayedDatetime(long startMillis, long endMillis, boolean allDay, Context context) {
+ // Configure date/time formatting.
+ int flagsDate = DateUtils.FORMAT_SHOW_DATE | DateUtils.FORMAT_SHOW_WEEKDAY;
+ int flagsTime = DateUtils.FORMAT_SHOW_TIME;
+ if (DateFormat.is24HourFormat(context)) {
+ flagsTime |= DateUtils.FORMAT_24HOUR;
+ }
+
+ String datetimeString = null;
+ if (allDay) {
+ // For multi-day allday events or single-day all-day events that are not
+ // today or tomorrow, use framework formatter.
+ Formatter f = new Formatter(new StringBuilder(50), Locale.getDefault());
+ datetimeString = DateUtils.formatDateRange(context, f, startMillis,
+ endMillis, flagsDate, Time.TIMEZONE_UTC).toString();
+ } else {
+ // For multiday events, shorten day/month names.
+ // Example format: "Fri Apr 6, 5:00pm - Sun, Apr 8, 6:00pm"
+ int flagsDatetime = flagsDate | flagsTime | DateUtils.FORMAT_ABBREV_MONTH |
+ DateUtils.FORMAT_ABBREV_WEEKDAY;
+ datetimeString = DateUtils.formatDateRange(context, startMillis, endMillis,
+ flagsDatetime);
+ }
+ return datetimeString;
+ }
+ }
+
+ @Override
+ protected void onResume() {
+ super.onResume();
+ refresh();
+ }
+}
diff --git a/app/src/main/java/com/etesync/syncadapter/ui/journalviewer/ListEntriesFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/journalviewer/ListEntriesFragment.java
index 515840fa..cf58a41b 100644
--- a/app/src/main/java/com/etesync/syncadapter/ui/journalviewer/ListEntriesFragment.java
+++ b/app/src/main/java/com/etesync/syncadapter/ui/journalviewer/ListEntriesFragment.java
@@ -13,12 +13,12 @@ import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.v4.app.ListFragment;
-import android.support.v7.app.AlertDialog;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
+import android.widget.ImageView;
import android.widget.TextView;
import com.etesync.syncadapter.App;
@@ -27,9 +27,10 @@ import com.etesync.syncadapter.model.CollectionInfo;
import com.etesync.syncadapter.model.EntryEntity;
import com.etesync.syncadapter.model.JournalEntity;
import com.etesync.syncadapter.model.JournalModel;
+import com.etesync.syncadapter.model.SyncEntry;
+import com.etesync.syncadapter.ui.JournalItemActivity;
import java.util.List;
-import java.util.Locale;
import io.requery.Persistable;
import io.requery.sql.EntityDataStore;
@@ -81,9 +82,7 @@ public class ListEntriesFragment extends ListFragment implements AdapterView.OnI
@Override
public void onItemClick(AdapterView> parent, View view, int position, long id) {
EntryEntity entry = (EntryEntity) getListAdapter().getItem(position);
- new AlertDialog.Builder(getActivity())
- .setTitle("Raw dump")
- .setMessage("Action: " + entry.getContent().getAction().toString() + "\nIntegrity: " + entry.getUid() + "\n" + entry.getContent().getContent()).show();
+ startActivity(JournalItemActivity.newIntent(getContext(), info, entry.getContent()));
}
class EntriesListAdapter extends ArrayAdapter {
@@ -91,20 +90,6 @@ public class ListEntriesFragment extends ListFragment implements AdapterView.OnI
super(context, R.layout.journal_viewer_list_item);
}
- private String getLine(String content, String prefix) {
- if (content == null) {
- return null;
- }
-
- int start = content.indexOf(prefix);
- if (start >= 0) {
- int end = content.indexOf("\n", start);
- content = content.substring(start + prefix.length(), end);
- } else {
- content = null;
- }
- return content;
- }
@Override
@NonNull
public View getView(int position, View v, @NonNull ViewGroup parent) {
@@ -117,26 +102,62 @@ public class ListEntriesFragment extends ListFragment implements AdapterView.OnI
// FIXME: hacky way to make it show sensible info
CollectionInfo info = journalEntity.getInfo();
- String fullContent = entryEntity.getContent().getContent();
- String prefix;
- if (info.type == CollectionInfo.Type.CALENDAR) {
- prefix = "SUMMARY:";
- } else {
- prefix = "FN:";
- }
- String content = getLine(fullContent, prefix);
- content = (content != null) ? content : entryEntity.getUid().substring(0, 20);
- tv.setText(String.format(Locale.getDefault(), "%s: %s", entryEntity.getContent().getAction().toString(), content));
-
- tv = (TextView) v.findViewById(R.id.description);
- content = getLine(fullContent, "UID:");
- content = "UID: " + ((content != null) ? content : "Not found");
- tv.setText(content);
+ setJournalEntryView(v, info, entryEntity.getContent());
return v;
}
}
+ private static String getLine(String content, String prefix) {
+ if (content == null) {
+ return null;
+ }
+
+ int start = content.indexOf(prefix);
+ if (start >= 0) {
+ int end = content.indexOf("\n", start);
+ content = content.substring(start + prefix.length(), end);
+ } else {
+ content = null;
+ }
+ return content;
+ }
+
+ public static void setJournalEntryView(View v, CollectionInfo info, SyncEntry syncEntry) {
+
+ TextView tv = (TextView) v.findViewById(R.id.title);
+
+ // FIXME: hacky way to make it show sensible info
+ String fullContent = syncEntry.getContent();
+ String prefix;
+ if (info.type == CollectionInfo.Type.CALENDAR) {
+ prefix = "SUMMARY:";
+ } else {
+ prefix = "FN:";
+ }
+ String content = getLine(fullContent, prefix);
+ content = (content != null) ? content : "Not found";
+ tv.setText(content);
+
+ tv = (TextView) v.findViewById(R.id.description);
+ content = getLine(fullContent, "UID:");
+ content = "UID: " + ((content != null) ? content : "Not found");
+ tv.setText(content);
+
+ ImageView action = (ImageView) v.findViewById(R.id.action);
+ switch (syncEntry.getAction()) {
+ case ADD:
+ action.setImageResource(R.drawable.action_add);
+ break;
+ case CHANGE:
+ action.setImageResource(R.drawable.action_change);
+ break;
+ case DELETE:
+ action.setImageResource(R.drawable.action_delete);
+ break;
+ }
+ }
+
private class JournalFetch extends AsyncTask> {
@Override
diff --git a/app/src/main/res/drawable/action_add.xml b/app/src/main/res/drawable/action_add.xml
new file mode 100644
index 00000000..89d2df2e
--- /dev/null
+++ b/app/src/main/res/drawable/action_add.xml
@@ -0,0 +1,17 @@
+
+
+
+
+
diff --git a/app/src/main/res/drawable/action_change.xml b/app/src/main/res/drawable/action_change.xml
new file mode 100644
index 00000000..1b65f3df
--- /dev/null
+++ b/app/src/main/res/drawable/action_change.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/drawable/action_delete.xml b/app/src/main/res/drawable/action_delete.xml
new file mode 100644
index 00000000..a789c3fb
--- /dev/null
+++ b/app/src/main/res/drawable/action_delete.xml
@@ -0,0 +1,5 @@
+
+
+
diff --git a/app/src/main/res/layout/contact_info.xml b/app/src/main/res/layout/contact_info.xml
new file mode 100644
index 00000000..1f81bdb1
--- /dev/null
+++ b/app/src/main/res/layout/contact_info.xml
@@ -0,0 +1,78 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/contact_info_item.xml b/app/src/main/res/layout/contact_info_item.xml
new file mode 100644
index 00000000..2b26f497
--- /dev/null
+++ b/app/src/main/res/layout/contact_info_item.xml
@@ -0,0 +1,38 @@
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/contact_info_item_group.xml b/app/src/main/res/layout/contact_info_item_group.xml
new file mode 100644
index 00000000..0c5831cf
--- /dev/null
+++ b/app/src/main/res/layout/contact_info_item_group.xml
@@ -0,0 +1,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/event_info.xml b/app/src/main/res/layout/event_info.xml
new file mode 100644
index 00000000..2ce03584
--- /dev/null
+++ b/app/src/main/res/layout/event_info.xml
@@ -0,0 +1,116 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/event_info_headline.xml b/app/src/main/res/layout/event_info_headline.xml
new file mode 100644
index 00000000..30cbc37a
--- /dev/null
+++ b/app/src/main/res/layout/event_info_headline.xml
@@ -0,0 +1,82 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/journal_item_activity.xml b/app/src/main/res/layout/journal_item_activity.xml
new file mode 100644
index 00000000..97e113ab
--- /dev/null
+++ b/app/src/main/res/layout/journal_item_activity.xml
@@ -0,0 +1,27 @@
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/journal_viewer_list_item.xml b/app/src/main/res/layout/journal_viewer_list_item.xml
index ffc456d7..d8dbffc5 100644
--- a/app/src/main/res/layout/journal_viewer_list_item.xml
+++ b/app/src/main/res/layout/journal_viewer_list_item.xml
@@ -7,6 +7,11 @@
android:padding="8dp"
android:gravity="center_vertical">
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/layout/view_collection_members.xml b/app/src/main/res/layout/view_collection_members.xml
index ebebe0b3..c296e6f5 100644
--- a/app/src/main/res/layout/view_collection_members.xml
+++ b/app/src/main/res/layout/view_collection_members.xml
@@ -25,7 +25,6 @@
16dp
16dp
-
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index bea03001..b563adc1 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -7,7 +7,7 @@
~ http://www.gnu.org/licenses/gpl.html
-->
-
+
EteSync
@@ -128,6 +128,9 @@
Remove member
Would you like to revoke %s\'s access?\nPlease be advised that a malicious user would potentially be able to retain access to encryption keys. Please refer to the FAQ for more information.
+
+ About
+
EteSync permissions
Calendar permissions
@@ -247,7 +250,6 @@
Are you sure?
This collection (%s) and all its data will be removed from the server.
Deleting collection
- Stats
No entries found for this journal
@@ -298,4 +300,6 @@
From Account
Select Account
+
+ Organizer: