diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 3c2fd429..3a3ddfdb 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -181,7 +181,7 @@ android:name=".ui.AccountActivity" android:parentActivityName=".ui.AccountsActivity"> - + diff --git a/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.java b/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.java index f487d68f..362453c8 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.java @@ -180,13 +180,13 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu final ArrayAdapter adapter = (ArrayAdapter)list.getAdapter(); final CollectionInfo info = adapter.getItem(position); - startActivity(EditCollectionActivity.newIntent(AccountActivity.this, account, info, (adapter.getCount() > 1))); + startActivity(ViewCollectionActivity.newIntent(AccountActivity.this, account, info)); } }; public void onChangeJournalClick(View view) { - Intent intent = new Intent(this, JournalViewerActivity.class); - intent.putExtra(JournalViewerActivity.EXTRA_ACCOUNT, account); + Intent intent = new Intent(this, ViewCollectionActivity.class); + intent.putExtra(ViewCollectionActivity.EXTRA_ACCOUNT, account); startActivity(intent); } diff --git a/app/src/main/java/com/etesync/syncadapter/ui/EditCollectionActivity.java b/app/src/main/java/com/etesync/syncadapter/ui/EditCollectionActivity.java index a0465812..ded3d778 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/EditCollectionActivity.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/EditCollectionActivity.java @@ -18,20 +18,20 @@ import android.view.MenuItem; import android.view.View; import android.widget.EditText; +import com.etesync.syncadapter.App; import com.etesync.syncadapter.R; import com.etesync.syncadapter.model.CollectionInfo; +import com.etesync.syncadapter.model.JournalEntity; import com.etesync.syncadapter.resource.LocalCalendar; +import io.requery.Persistable; +import io.requery.sql.EntityDataStore; + public class EditCollectionActivity extends CreateCollectionActivity { - private final static String EXTRA_ALLOW_DELETE = "allowDelete"; - - protected boolean allowDelete; - - public static Intent newIntent(Context context, Account account, CollectionInfo info, boolean allowDelete) { + public static Intent newIntent(Context context, Account account, CollectionInfo info) { Intent intent = new Intent(context, EditCollectionActivity.class); intent.putExtra(CreateCollectionActivity.EXTRA_ACCOUNT, account); intent.putExtra(CreateCollectionActivity.EXTRA_COLLECTION_INFO, info); - intent.putExtra(EXTRA_ALLOW_DELETE, allowDelete); return intent; } @@ -39,8 +39,6 @@ public class EditCollectionActivity extends CreateCollectionActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); - allowDelete = getIntent().getExtras().getBoolean(EXTRA_ALLOW_DELETE, false); - setTitle(R.string.edit_collection); if (info.type == CollectionInfo.Type.CALENDAR) { @@ -70,7 +68,10 @@ public class EditCollectionActivity extends CreateCollectionActivity { } public void onDeleteCollection(MenuItem item) { - if (!allowDelete) { + EntityDataStore data = ((App) getApplication()).getData(); + int journalCount = data.count(JournalEntity.class).where(JournalEntity.SERVICE.eq(info.serviceID)).get().value(); + + if (journalCount < 2) { new AlertDialog.Builder(this) .setIcon(R.drawable.ic_error_dark) .setTitle(R.string.account_delete_collection_last_title) diff --git a/app/src/main/java/com/etesync/syncadapter/ui/JournalViewerActivity.java b/app/src/main/java/com/etesync/syncadapter/ui/JournalViewerActivity.java deleted file mode 100644 index 74215855..00000000 --- a/app/src/main/java/com/etesync/syncadapter/ui/JournalViewerActivity.java +++ /dev/null @@ -1,69 +0,0 @@ -/* - * Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering). - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the GNU Public License v3.0 - * which accompanies this distribution, and is available at - * http://www.gnu.org/licenses/gpl.html - */ - -package com.etesync.syncadapter.ui; - -import android.accounts.Account; -import android.content.Intent; -import android.os.Bundle; -import android.support.v4.app.NavUtils; -import android.support.v7.app.AppCompatActivity; -import android.view.MenuItem; - -import com.etesync.syncadapter.App; -import com.etesync.syncadapter.ui.journalviewer.ListJournalsFragment; - -public class JournalViewerActivity extends AppCompatActivity { - public final static String EXTRA_ACCOUNT = "account"; - - private Account account; - - @Override - protected void onCreate(Bundle savedInstanceState) { - super.onCreate(savedInstanceState); - - getSupportActionBar().setDisplayHomeAsUpEnabled(true); - - if (savedInstanceState == null) { - account = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT); - // first call, add fragment - getSupportFragmentManager().beginTransaction() - .replace(android.R.id.content, ListJournalsFragment.newInstance(account)) - .commit(); - } - } - - @Override - public boolean onOptionsItemSelected(MenuItem item) { - if (item.getItemId() == android.R.id.home) { - if (!getSupportFragmentManager().popBackStackImmediate()) { - finish(); - } - return true; - } - return false; - } - - @Override - protected void onResume() { - super.onResume(); - - App app = (App)getApplicationContext(); - if (app.getCertManager() != null) - app.getCertManager().appInForeground = true; - } - - @Override - protected void onPause() { - super.onPause(); - - App app = (App)getApplicationContext(); - if (app.getCertManager() != null) - app.getCertManager().appInForeground = false; - } -} diff --git a/app/src/main/java/com/etesync/syncadapter/ui/ViewCollectionActivity.java b/app/src/main/java/com/etesync/syncadapter/ui/ViewCollectionActivity.java new file mode 100644 index 00000000..cdaec8ba --- /dev/null +++ b/app/src/main/java/com/etesync/syncadapter/ui/ViewCollectionActivity.java @@ -0,0 +1,160 @@ +/* + * Copyright © 2013 – 2016 Ricki Hirner (bitfire web engineering). + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the GNU Public License v3.0 + * which accompanies this distribution, and is available at + * http://www.gnu.org/licenses/gpl.html + */ + +package com.etesync.syncadapter.ui; + +import android.accounts.Account; +import android.content.Context; +import android.content.Intent; +import android.os.Bundle; +import android.provider.CalendarContract; +import android.provider.ContactsContract; +import android.support.v7.app.AppCompatActivity; +import android.view.Menu; +import android.view.MenuItem; +import android.view.View; +import android.widget.TextView; + +import com.etesync.syncadapter.App; +import com.etesync.syncadapter.R; +import com.etesync.syncadapter.model.CollectionInfo; +import com.etesync.syncadapter.model.EntryEntity; +import com.etesync.syncadapter.model.JournalEntity; +import com.etesync.syncadapter.resource.LocalAddressBook; +import com.etesync.syncadapter.resource.LocalCalendar; +import com.etesync.syncadapter.ui.journalviewer.ListEntriesFragment; + +import java.util.Locale; + +import at.bitfire.ical4android.CalendarStorageException; +import at.bitfire.vcard4android.ContactsStorageException; +import io.requery.Persistable; +import io.requery.sql.EntityDataStore; + +public class ViewCollectionActivity extends AppCompatActivity { + public final static String EXTRA_ACCOUNT = "account", + EXTRA_COLLECTION_INFO = "collectionInfo"; + + private Account account; + protected CollectionInfo info; + + public static Intent newIntent(Context context, Account account, CollectionInfo info) { + Intent intent = new Intent(context, ViewCollectionActivity.class); + intent.putExtra(ViewCollectionActivity.EXTRA_ACCOUNT, account); + intent.putExtra(ViewCollectionActivity.EXTRA_COLLECTION_INFO, info); + return intent; + } + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + + setContentView(R.layout.view_collection_activity); + + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + + account = getIntent().getExtras().getParcelable(EXTRA_ACCOUNT); + info = (CollectionInfo) getIntent().getExtras().getSerializable(EXTRA_COLLECTION_INFO); + + getSupportFragmentManager().beginTransaction() + .add(R.id.list_entries_container, ListEntriesFragment.newInstance(info)) + .commit(); + + + final TextView stats = (TextView) findViewById(R.id.stats); + + final View colorSquare = findViewById(R.id.color); + if (info.type == CollectionInfo.Type.CALENDAR) { + if (info.color != null) { + colorSquare.setBackgroundColor(info.color); + } else { + colorSquare.setBackgroundColor(LocalCalendar.defaultColor); + } + + try { + LocalCalendar resource = (LocalCalendar) LocalCalendar.find(account, this.getContentResolver().acquireContentProviderClient(CalendarContract.CONTENT_URI), + LocalCalendar.Factory.INSTANCE, CalendarContract.Calendars.NAME + "=?", new String[]{info.url})[0]; + long count = resource.count(); + EntityDataStore data = ((App) getApplication()).getData(); + int entryCount = -1; + final JournalEntity journalEntity = data.select(JournalEntity.class).where(JournalEntity.UID.eq(info.url)).limit(1).get().firstOrNull(); + if (journalEntity != null) { + entryCount = data.count(EntryEntity.class).where(EntryEntity.JOURNAL.eq(journalEntity)).get().value(); + } + stats.setText(String.format(Locale.getDefault(), "Events: %d, Journal entries: %d", count, entryCount)); + } catch (CalendarStorageException e) { + e.printStackTrace(); + stats.setText("Stats loading error."); + } + } else { + colorSquare.setVisibility(View.GONE); + + try { + LocalAddressBook resource = new LocalAddressBook(account, this.getContentResolver().acquireContentProviderClient(ContactsContract.Contacts.CONTENT_URI)); + long count = resource.count(); + EntityDataStore data = ((App) getApplication()).getData(); + int entryCount = -1; + final JournalEntity journalEntity = data.select(JournalEntity.class).where(JournalEntity.UID.eq(info.url)).limit(1).get().firstOrNull(); + if (journalEntity != null) { + entryCount = data.count(EntryEntity.class).where(EntryEntity.JOURNAL.eq(journalEntity)).get().value(); + }; + stats.setText(String.format(Locale.getDefault(), "Contacts: %d, Journal Entries: %d", count, entryCount)); + } catch (ContactsStorageException e) { + e.printStackTrace(); + stats.setText("Stats loading error."); + } + } + + final TextView title = (TextView) findViewById(R.id.display_name); + title.setText(info.displayName); + + final TextView desc = (TextView) findViewById(R.id.description); + desc.setText(info.description); + } + + @Override + public boolean onCreateOptionsMenu(Menu menu) { + getMenuInflater().inflate(R.menu.activity_view_collection, menu); + return true; + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + if (item.getItemId() == android.R.id.home) { + if (!getSupportFragmentManager().popBackStackImmediate()) { + finish(); + } + return true; + } + return false; + } + + @Override + protected void onResume() { + super.onResume(); + + App app = (App) getApplicationContext(); + if (app.getCertManager() != null) + app.getCertManager().appInForeground = true; + } + + @Override + protected void onPause() { + super.onPause(); + + App app = (App) getApplicationContext(); + if (app.getCertManager() != null) + app.getCertManager().appInForeground = false; + } + + public void onEditCollection(MenuItem item) { + startActivity(EditCollectionActivity.newIntent(this, account, info)); + // FIXME: Handle it more gracefully + finish(); + } +} 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 3bcef1e1..21b53bbe 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 @@ -10,6 +10,7 @@ package com.etesync.syncadapter.ui.journalviewer; import android.content.Context; 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; @@ -21,6 +22,7 @@ import android.widget.TextView; import com.etesync.syncadapter.App; import com.etesync.syncadapter.R; +import com.etesync.syncadapter.model.CollectionInfo; import com.etesync.syncadapter.model.EntryEntity; import com.etesync.syncadapter.model.JournalEntity; import com.etesync.syncadapter.model.JournalModel; @@ -31,15 +33,15 @@ import io.requery.Persistable; import io.requery.sql.EntityDataStore; public class ListEntriesFragment extends ListFragment implements AdapterView.OnItemClickListener { - protected static final String EXTRA_JOURNAL = "journal"; + protected static final String EXTRA_COLLECTION_INFO = "collectionInfo"; private EntityDataStore data; private JournalEntity journalEntity; - public static ListEntriesFragment newInstance(String journal) { + public static ListEntriesFragment newInstance(CollectionInfo info) { ListEntriesFragment frag = new ListEntriesFragment(); Bundle args = new Bundle(1); - args.putSerializable(EXTRA_JOURNAL, journal); + args.putSerializable(EXTRA_COLLECTION_INFO, info); frag.setArguments(args); return frag; } @@ -48,14 +50,14 @@ public class ListEntriesFragment extends ListFragment implements AdapterView.OnI public void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); data = ((App) getContext().getApplicationContext()).getData(); - String name = getArguments().getString(EXTRA_JOURNAL); - journalEntity = JournalModel.Journal.fetch(data, name); + CollectionInfo info = (CollectionInfo) getArguments().getSerializable(EXTRA_COLLECTION_INFO); + journalEntity = JournalModel.Journal.fetch(data, info.url); } @Override public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - getActivity().setTitle(String.format(Locale.getDefault(), "%s (%d)", journalEntity.getInfo().displayName, data.count(EntryEntity.class).where(EntryEntity.JOURNAL.eq(journalEntity)).get().value())); - return inflater.inflate(R.layout.journal_viewer_list_entries, container, false); + getActivity().setTitle(journalEntity.getInfo().displayName); + return inflater.inflate(R.layout.journal_viewer_list, container, false); } @Override @@ -74,24 +76,52 @@ public class ListEntriesFragment extends ListFragment implements AdapterView.OnI public void onItemClick(AdapterView parent, View view, int position, long id) { EntryEntity entry = (EntryEntity) getListAdapter().getItem(position); new AlertDialog.Builder(getActivity()) - .setTitle("Raw dump: " + entry.getUid()) - .setMessage("Action: " + entry.getContent().getAction().toString() + "\nUid: " + entry.getUid() + "\n" + entry.getContent().getContent()).show(); + .setTitle("Raw dump") + .setMessage("Action: " + entry.getContent().getAction().toString() + "\nIntegrity: " + entry.getUid() + "\n" + entry.getContent().getContent()).show(); } - static class EntriesListAdapter extends ArrayAdapter { - public EntriesListAdapter(Context context) { - super(context, R.layout.journal_viewer_list_journals_item); + class EntriesListAdapter extends ArrayAdapter { + EntriesListAdapter(Context context) { + super(context, R.layout.journal_viewer_list_item); } + private String getLine(String content, String prefix) { + 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 - public View getView(int position, View v, ViewGroup parent) { + @NonNull + public View getView(int position, View v, @NonNull ViewGroup parent) { if (v == null) - v = LayoutInflater.from(getContext()).inflate(R.layout.journal_viewer_list_journals_item, parent, false); + v = LayoutInflater.from(getContext()).inflate(R.layout.journal_viewer_list_item, parent, false); EntryEntity entryEntity = getItem(position); TextView tv = (TextView) v.findViewById(R.id.title); - tv.setText(String.format(Locale.getDefault(), "%s: %s", entryEntity.getContent().getAction().toString(), entryEntity.getUid())); + + // 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); return v; } diff --git a/app/src/main/java/com/etesync/syncadapter/ui/journalviewer/ListJournalsFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/journalviewer/ListJournalsFragment.java index dd189915..601abfe5 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/journalviewer/ListJournalsFragment.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/journalviewer/ListJournalsFragment.java @@ -87,7 +87,7 @@ public class ListJournalsFragment extends ListFragment implements AdapterView.On if (item.getViewType() == 1) { CollectionInfo info = ((ListItem) item).info; getFragmentManager().beginTransaction() - .replace(android.R.id.content, ListEntriesFragment.newInstance(info.url)) + .replace(android.R.id.content, ListEntriesFragment.newInstance(info)) .addToBackStack(null) .commitAllowingStateLoss(); } diff --git a/app/src/main/res/layout/journal_viewer_list_entries.xml b/app/src/main/res/layout/journal_viewer_list.xml similarity index 90% rename from app/src/main/res/layout/journal_viewer_list_entries.xml rename to app/src/main/res/layout/journal_viewer_list.xml index 1d8e24b2..e21e5e44 100644 --- a/app/src/main/res/layout/journal_viewer_list_entries.xml +++ b/app/src/main/res/layout/journal_viewer_list.xml @@ -7,9 +7,7 @@ - + android:layout_height="match_parent" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/view_collection_activity.xml b/app/src/main/res/layout/view_collection_activity.xml new file mode 100644 index 00000000..34106bfe --- /dev/null +++ b/app/src/main/res/layout/view_collection_activity.xml @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/activity_view_collection.xml b/app/src/main/res/menu/activity_view_collection.xml new file mode 100644 index 00000000..530e8426 --- /dev/null +++ b/app/src/main/res/menu/activity_view_collection.xml @@ -0,0 +1,17 @@ + + + + + + + + \ 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 fde1276b..26e26a86 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -187,6 +187,7 @@ Display name (title) of this collection: Title is required Description (optional): + Edit Save Delete Are you sure?