diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index c47e5f92..6c3a4fc1 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -181,6 +181,7 @@ android:name=".ui.AccountActivity" android:parentActivityName=".ui.AccountsActivity"> + diff --git a/app/src/main/java/com/etesync/syncadapter/model/ServiceDB.java b/app/src/main/java/com/etesync/syncadapter/model/ServiceDB.java index b9dceea8..6059b745 100644 --- a/app/src/main/java/com/etesync/syncadapter/model/ServiceDB.java +++ b/app/src/main/java/com/etesync/syncadapter/model/ServiceDB.java @@ -182,6 +182,12 @@ public class ServiceDB { else return null; } + + @Nullable + public Long getService(@NonNull Account account, String service) { + @Cleanup SQLiteDatabase db = getReadableDatabase(); + return getService(db, account, service); + } } 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 f87930e0..40df4b37 100644 --- a/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.java +++ b/app/src/main/java/com/etesync/syncadapter/ui/AccountActivity.java @@ -50,12 +50,6 @@ import android.widget.PopupMenu; import android.widget.ProgressBar; import android.widget.TextView; -import java.io.IOException; -import java.util.LinkedList; -import java.util.List; -import java.util.logging.Level; - -import at.bitfire.cert4android.CustomCertManager; import com.etesync.syncadapter.AccountUpdateService; import com.etesync.syncadapter.App; import com.etesync.syncadapter.Constants; @@ -65,6 +59,13 @@ import com.etesync.syncadapter.model.JournalEntity; import com.etesync.syncadapter.model.ServiceDB.OpenHelper; import com.etesync.syncadapter.model.ServiceDB.Services; import com.etesync.syncadapter.resource.LocalCalendar; + +import java.io.IOException; +import java.util.LinkedList; +import java.util.List; +import java.util.logging.Level; + +import at.bitfire.cert4android.CustomCertManager; import at.bitfire.ical4android.TaskProvider; import io.requery.Persistable; import io.requery.sql.EntityDataStore; @@ -205,6 +206,11 @@ public class AccountActivity extends AppCompatActivity implements Toolbar.OnMenu } }; + public void onChangeJournalClick(View view) { + Intent intent = new Intent(this, JournalViewerActivity.class); + intent.putExtra(JournalViewerActivity.EXTRA_ACCOUNT, account); + startActivity(intent); + } /* LOADERS AND LOADED DATA */ diff --git a/app/src/main/java/com/etesync/syncadapter/ui/JournalViewerActivity.java b/app/src/main/java/com/etesync/syncadapter/ui/JournalViewerActivity.java new file mode 100644 index 00000000..74215855 --- /dev/null +++ b/app/src/main/java/com/etesync/syncadapter/ui/JournalViewerActivity.java @@ -0,0 +1,69 @@ +/* + * 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/journalviewer/ListEntriesFragment.java b/app/src/main/java/com/etesync/syncadapter/ui/journalviewer/ListEntriesFragment.java new file mode 100644 index 00000000..3bcef1e1 --- /dev/null +++ b/app/src/main/java/com/etesync/syncadapter/ui/journalviewer/ListEntriesFragment.java @@ -0,0 +1,99 @@ +/* + * 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.journalviewer; + +import android.content.Context; +import android.os.Bundle; +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.TextView; + +import com.etesync.syncadapter.App; +import com.etesync.syncadapter.R; +import com.etesync.syncadapter.model.EntryEntity; +import com.etesync.syncadapter.model.JournalEntity; +import com.etesync.syncadapter.model.JournalModel; + +import java.util.Locale; + +import io.requery.Persistable; +import io.requery.sql.EntityDataStore; + +public class ListEntriesFragment extends ListFragment implements AdapterView.OnItemClickListener { + protected static final String EXTRA_JOURNAL = "journal"; + + private EntityDataStore data; + private JournalEntity journalEntity; + + public static ListEntriesFragment newInstance(String journal) { + ListEntriesFragment frag = new ListEntriesFragment(); + Bundle args = new Bundle(1); + args.putSerializable(EXTRA_JOURNAL, journal); + frag.setArguments(args); + return frag; + } + + @Override + 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); + } + + @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); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + EntriesListAdapter listAdapter = new EntriesListAdapter(getContext()); + setListAdapter(listAdapter); + + listAdapter.addAll(data.select(EntryEntity.class).where(EntryEntity.JOURNAL.eq(journalEntity)).orderBy(EntryEntity.ID.desc()).get().toList()); + + getListView().setOnItemClickListener(this); + } + + @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: " + entry.getUid()) + .setMessage("Action: " + entry.getContent().getAction().toString() + "\nUid: " + 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); + } + + @Override + public View getView(int position, View v, ViewGroup parent) { + if (v == null) + v = LayoutInflater.from(getContext()).inflate(R.layout.journal_viewer_list_journals_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())); + + 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 new file mode 100644 index 00000000..dd189915 --- /dev/null +++ b/app/src/main/java/com/etesync/syncadapter/ui/journalviewer/ListJournalsFragment.java @@ -0,0 +1,196 @@ +/* + * 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.journalviewer; + +import android.accounts.Account; +import android.content.Context; +import android.graphics.Typeface; +import android.os.Bundle; +import android.support.annotation.NonNull; +import android.support.v4.app.ListFragment; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; +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.JournalEntity; +import com.etesync.syncadapter.model.ServiceDB; + +import io.requery.Persistable; +import io.requery.sql.EntityDataStore; +import lombok.Cleanup; + +public class ListJournalsFragment extends ListFragment implements AdapterView.OnItemClickListener { + private final static String ARG_ACCOUNT = "account"; + + private Account account; + + public static ListJournalsFragment newInstance(Account account) { + ListJournalsFragment frag = new ListJournalsFragment(); + Bundle args = new Bundle(1); + args.putParcelable(ARG_ACCOUNT, account); + frag.setArguments(args); + return frag; + } + + @Override + public void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + account = getArguments().getParcelable(ARG_ACCOUNT); + } + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + getActivity().setTitle(R.string.change_journal_title); + return inflater.inflate(R.layout.journal_viewer_list_journals, container, false); + } + + @Override + public void onViewCreated(View view, Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + + CollectionsListAdapter listAdapter = new CollectionsListAdapter(getContext()); + setListAdapter(listAdapter); + + final EntityDataStore data = ((App) getContext().getApplicationContext()).getData(); + @Cleanup ServiceDB.OpenHelper dbHelper = new ServiceDB.OpenHelper(getContext()); + + Long service = dbHelper.getService(account, ServiceDB.Services.SERVICE_CARDDAV); + listAdapter.add(new HeaderItem(getString(R.string.settings_carddav))); + for (CollectionInfo info : JournalEntity.getCollections(data, service)) { + listAdapter.add(new ListItem(info)); + } + + service = dbHelper.getService(account, ServiceDB.Services.SERVICE_CALDAV); + listAdapter.add(new HeaderItem(getString(R.string.settings_caldav))); + for (CollectionInfo info : JournalEntity.getCollections(data, service)) { + listAdapter.add(new ListItem(info)); + } + + getListView().setOnItemClickListener(this); + } + + @Override + public void onItemClick(AdapterView parent, View view, int position, long id) { + Item item = (Item) getListAdapter().getItem(position); + if (item.getViewType() == 1) { + CollectionInfo info = ((ListItem) item).info; + getFragmentManager().beginTransaction() + .replace(android.R.id.content, ListEntriesFragment.newInstance(info.url)) + .addToBackStack(null) + .commitAllowingStateLoss(); + } + } + + private static class CollectionsListAdapter extends ArrayAdapter { + CollectionsListAdapter(Context context) { + super(context, 0); + } + + @NonNull + @Override + public View getView(int position, View v, @NonNull ViewGroup parent) { + Item item = getItem(position); + LayoutInflater inflater = LayoutInflater.from(getContext()); + + if (v == null) { + v = item.getView(inflater, v, parent); + } + + return v; + } + + @Override + public int getViewTypeCount() { + return Item.Type.values().length; + + } + + @Override + public int getItemViewType(int position) { + return getItem(position).getViewType(); + } + } + + interface Item { + enum Type { + Header(0), + Item(1); + + private final int value; + + Type(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + } + + int getViewType(); + + View getView(LayoutInflater inflater, View convertView, ViewGroup parent); + } + + private static class HeaderItem implements Item { + private String header; + + HeaderItem(String header) { + this.header = header; + } + + @Override + public int getViewType() { + return Type.Header.getValue(); + } + + @Override + public View getView(LayoutInflater inflater, View v, ViewGroup parent) { + if (v == null) + v = inflater.inflate(R.layout.journal_viewer_list_journals_header, parent, false); + + TextView tv = (TextView) v.findViewById(R.id.title); + tv.setTypeface(null, Typeface.BOLD); + tv.setText(header); + + return v; + } + } + + + private static class ListItem implements Item { + private CollectionInfo info; + + ListItem(CollectionInfo info) { + this.info = info; + } + + @Override + public int getViewType() { + return Type.Item.getValue(); + } + + @Override + public View getView(LayoutInflater inflater, View v, ViewGroup parent) { + if (v == null) + v = inflater.inflate(R.layout.journal_viewer_list_journals_item, parent, false); + + TextView tv = (TextView) v.findViewById(R.id.title); + tv.setText(info.displayName); + + return v; + } + } +} diff --git a/app/src/main/res/layout/activity_account.xml b/app/src/main/res/layout/activity_account.xml index a7f77a96..8afaaf10 100644 --- a/app/src/main/res/layout/activity_account.xml +++ b/app/src/main/res/layout/activity_account.xml @@ -118,7 +118,7 @@ android:theme="@style/toolbar_theme" style="@style/toolbar_style" app:navigationIcon="@drawable/ic_journal" - app:title="Change Journal" + app:title="@string/change_journal_title" android:elevation="2dp" tools:ignore="UnusedAttribute"/> @@ -126,20 +126,22 @@ xmlns:tools="http://schemas.android.com/tools" android:layout_width="match_parent" android:layout_height="wrap_content" - android:padding="8dp" android:gravity="center_vertical"> - + android:onClick="onChangeJournalClick" + android:gravity="left|center_vertical" + android:padding="16dp" + style="@style/Widget.AppCompat.Button.Borderless" + android:text="View (ugly preview-release)"/> diff --git a/app/src/main/res/layout/journal_viewer_list_entries.xml b/app/src/main/res/layout/journal_viewer_list_entries.xml new file mode 100644 index 00000000..1d8e24b2 --- /dev/null +++ b/app/src/main/res/layout/journal_viewer_list_entries.xml @@ -0,0 +1,22 @@ + + + + + + + + diff --git a/app/src/main/res/layout/journal_viewer_list_journals.xml b/app/src/main/res/layout/journal_viewer_list_journals.xml new file mode 100644 index 00000000..b48b3a91 --- /dev/null +++ b/app/src/main/res/layout/journal_viewer_list_journals.xml @@ -0,0 +1,12 @@ + + + + + diff --git a/app/src/main/res/layout/journal_viewer_list_journals_header.xml b/app/src/main/res/layout/journal_viewer_list_journals_header.xml new file mode 100644 index 00000000..6a9f39d1 --- /dev/null +++ b/app/src/main/res/layout/journal_viewer_list_journals_header.xml @@ -0,0 +1,25 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/journal_viewer_list_journals_item.xml b/app/src/main/res/layout/journal_viewer_list_journals_item.xml new file mode 100644 index 00000000..ab8fd805 --- /dev/null +++ b/app/src/main/res/layout/journal_viewer_list_journals_item.xml @@ -0,0 +1,24 @@ + + + + + + + + + + \ 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 9c18d796..7d77b705 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -90,6 +90,8 @@ Can\'t delete last collection Deleting the last collection is not allowed, please create a new one if you\'d like to delete this one. + Change Journal + EteSync permissions Calendar permissions @@ -191,6 +193,9 @@ Deleting collection Stats + + No entries found for this journal + An error has occurred. An HTTP error has occurred.