1
0
mirror of https://github.com/etesync/android synced 2025-02-22 20:42:04 +00:00

Tasks: add ability to create, edit, deleted and view task journals

This commit is contained in:
Tom Hacohen 2019-01-08 11:13:24 +00:00
parent 3996f1824d
commit 8af6858422
11 changed files with 419 additions and 50 deletions

View File

@ -10,8 +10,6 @@ package com.etesync.syncadapter.ui
import android.accounts.Account
import android.accounts.AccountManager
import android.accounts.AuthenticatorException
import android.accounts.OperationCanceledException
import android.app.LoaderManager
import android.content.*
import android.content.ContentResolver.SYNC_OBSERVER_TYPE_ACTIVE
@ -41,7 +39,6 @@ import com.etesync.syncadapter.ui.setup.SetupUserInfoFragment
import com.etesync.syncadapter.utils.HintManager
import com.etesync.syncadapter.utils.ShowcaseBuilder
import tourguide.tourguide.ToolTip
import java.io.IOException
import java.util.logging.Level
class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMenu.OnMenuItemClickListener, LoaderManager.LoaderCallbacks<AccountActivity.AccountInfo>, Refreshable {
@ -51,6 +48,7 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
internal var listCalDAV: ListView? = null
internal var listCardDAV: ListView? = null
internal var listTaskDAV: ListView? = null
private val onItemClickListener = AdapterView.OnItemClickListener { parent, view, position, id ->
val list = parent as ListView
@ -98,6 +96,13 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
tbCalDAV.setOnMenuItemClickListener(this)
tbCalDAV.setTitle(R.string.settings_caldav)
// TaskDAV toolbar
val tbTaskDAV = findViewById<View>(R.id.taskdav_menu) as Toolbar
tbTaskDAV.overflowIcon = icMenu
tbTaskDAV.inflateMenu(R.menu.taskdav_actions)
tbTaskDAV.setOnMenuItemClickListener(this)
tbTaskDAV.setTitle(R.string.settings_taskdav)
// load CardDAV/CalDAV journals
loaderManager.initLoader(0, intent.extras, this)
@ -157,6 +162,11 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
info.type = CollectionInfo.Type.CALENDAR
startActivity(CreateCollectionActivity.newIntent(this@AccountActivity, account!!, info))
}
R.id.create_tasklist -> {
info = CollectionInfo()
info.type = CollectionInfo.Type.TASKS
startActivity(CreateCollectionActivity.newIntent(this@AccountActivity, account!!, info))
}
R.id.create_addressbook -> {
info = CollectionInfo()
info.type = CollectionInfo.Type.ADDRESS_BOOK
@ -171,6 +181,7 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
class AccountInfo {
internal var carddav: ServiceInfo? = null
internal var caldav: ServiceInfo? = null
internal var taskdav: ServiceInfo? = null
class ServiceInfo {
internal var id: Long = 0
@ -222,6 +233,22 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
listCalDAV!!.onItemClickListener = onItemClickListener
} else
card.visibility = View.GONE
card = findViewById<View>(R.id.caldav) as CardView
if (info.taskdav != null) {
val progress = findViewById<View>(R.id.taskdav_refreshing) as ProgressBar
progress.visibility = if (info.taskdav!!.refreshing) View.VISIBLE else View.GONE
listTaskDAV = findViewById<View>(R.id.tasklists) as ListView
listTaskDAV!!.isEnabled = !info.taskdav!!.refreshing
listTaskDAV!!.setAlpha(if (info.taskdav!!.refreshing) 0.5f else 1f)
val adapter = CollectionListAdapter(this, account!!)
adapter.addAll(info.taskdav!!.journals!!)
listTaskDAV!!.adapter = adapter
listTaskDAV!!.onItemClickListener = onItemClickListener
} else
card.visibility = View.GONE
}
override fun onLoaderReset(loader: Loader<AccountInfo>) {
@ -230,6 +257,9 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
if (listCalDAV != null)
listCalDAV!!.adapter = null
if (listTaskDAV != null)
listTaskDAV!!.adapter = null
}
@ -300,10 +330,16 @@ class AccountActivity : BaseActivity(), Toolbar.OnMenuItemClickListener, PopupMe
info.caldav = AccountInfo.ServiceInfo()
info.caldav!!.id = id
info.caldav!!.refreshing = davService != null && davService!!.isRefreshing(id) ||
ContentResolver.isSyncActive(account, CalendarContract.AUTHORITY) ||
ContentResolver.isSyncActive(account, TaskProvider.ProviderName.OpenTasks.authority)
ContentResolver.isSyncActive(account, CalendarContract.AUTHORITY)
info.caldav!!.journals = JournalEntity.getJournals(data, serviceEntity)
}
CollectionInfo.Type.TASKS -> {
info.taskdav = AccountInfo.ServiceInfo()
info.taskdav!!.id = id
info.taskdav!!.refreshing = davService != null && davService!!.isRefreshing(id) ||
ContentResolver.isSyncActive(account, TaskProvider.ProviderName.OpenTasks.authority)
info.taskdav!!.journals = JournalEntity.getJournals(data, serviceEntity)
}
}
}
return info

View File

@ -44,7 +44,7 @@ open class CreateCollectionActivity : BaseActivity() {
val displayName = findViewById<View>(R.id.display_name) as EditText
when (info.type) {
CollectionInfo.Type.CALENDAR, CollectionInfo.Type.TASKS -> {
CollectionInfo.Type.CALENDAR -> {
setTitle(R.string.create_calendar)
displayName.setHint(R.string.create_calendar_display_name_hint)
@ -59,6 +59,21 @@ open class CreateCollectionActivity : BaseActivity() {
}).show()
}
}
CollectionInfo.Type.TASKS -> {
setTitle(R.string.create_tasklist)
displayName.setHint(R.string.create_tasklist_display_name_hint)
val colorSquare = findViewById<View>(R.id.color)
colorSquare.setOnClickListener {
AmbilWarnaDialog(this@CreateCollectionActivity, (colorSquare.background as ColorDrawable).color, true, object : AmbilWarnaDialog.OnAmbilWarnaListener {
override fun onCancel(dialog: AmbilWarnaDialog) {}
override fun onOk(dialog: AmbilWarnaDialog, color: Int) {
colorSquare.setBackgroundColor(color)
}
}).show()
}
}
CollectionInfo.Type.ADDRESS_BOOK -> {
setTitle(R.string.create_addressbook)
displayName.setHint(R.string.create_addressbook_display_name_hint)

View File

@ -2,7 +2,6 @@ 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
@ -18,6 +17,7 @@ import android.view.ViewGroup
import android.widget.TextView
import at.bitfire.ical4android.Event
import at.bitfire.ical4android.InvalidCalendarException
import at.bitfire.ical4android.Task
import at.bitfire.vcard4android.Contact
import com.etesync.syncadapter.App
import com.etesync.syncadapter.Constants
@ -143,6 +143,10 @@ class JournalItemActivity : BaseActivity(), Refreshable {
v = inflater.inflate(R.layout.event_info, container, false)
asyncTask = loadEventTask(v)
}
CollectionInfo.Type.TASKS -> {
v = inflater.inflate(R.layout.task_info, container, false)
asyncTask = loadTaskTask(v)
}
}
return v
@ -221,6 +225,45 @@ class JournalItemActivity : BaseActivity(), Refreshable {
}
}
private fun loadTaskTask(view: View): Future<Unit> {
return doAsync {
var task: Task? = null
val inputReader = StringReader(syncEntry.content)
try {
task = Task.fromReader(inputReader)[0]
} catch (e: InvalidCalendarException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
}
if (task != null) {
uiThread {
val loader = view.findViewById<View>(R.id.task_info_loading_msg)
loader.visibility = View.GONE
val contentContainer = view.findViewById<View>(R.id.task_info_scroll_view)
contentContainer.visibility = View.VISIBLE
setTextViewText(view, R.id.title, task.summary)
setTextViewText(view, R.id.where, task.location)
val organizer = task.organizer
if (organizer != null) {
val tv = view.findViewById<View>(R.id.organizer) as TextView
tv.text = organizer.calAddress.toString().replaceFirst("mailto:".toRegex(), "")
} else {
val organizerView = view.findViewById<View>(R.id.organizer_container)
organizerView.visibility = View.GONE
}
setTextViewText(view, R.id.description, task.description)
}
}
}
}
private fun loadContactTask(view: View): Future<Unit> {
return doAsync {
var contact: Contact? = null

View File

@ -22,6 +22,7 @@ import android.view.MenuItem
import android.view.View
import android.widget.TextView
import at.bitfire.ical4android.CalendarStorageException
import at.bitfire.ical4android.TaskProvider
import at.bitfire.vcard4android.ContactsStorageException
import com.etesync.syncadapter.App
import com.etesync.syncadapter.R
@ -30,10 +31,12 @@ 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.resource.LocalTaskList
import com.etesync.syncadapter.ui.importlocal.ImportActivity
import com.etesync.syncadapter.ui.journalviewer.ListEntriesFragment
import com.etesync.syncadapter.utils.HintManager
import com.etesync.syncadapter.utils.ShowcaseBuilder
import org.dmfs.tasks.contract.TaskContract
import tourguide.tourguide.ToolTip
import java.io.FileNotFoundException
import java.util.*
@ -58,10 +61,16 @@ class ViewCollectionActivity : BaseActivity(), Refreshable {
isOwner = journalEntity!!.isOwner(account.name)
val colorSquare = findViewById<View>(R.id.color)
if (info.type == CollectionInfo.Type.CALENDAR) {
colorSquare.setBackgroundColor(info.color ?: LocalCalendar.defaultColor)
} else {
colorSquare.visibility = View.GONE
when (info.type) {
CollectionInfo.Type.CALENDAR -> {
colorSquare.setBackgroundColor(info.color ?: LocalCalendar.defaultColor)
}
CollectionInfo.Type.TASKS -> {
colorSquare.setBackgroundColor(info.color ?: LocalCalendar.defaultColor)
}
CollectionInfo.Type.ADDRESS_BOOK -> {
colorSquare.visibility = View.GONE
}
}
LoadCountTask().execute()
@ -134,6 +143,14 @@ class ViewCollectionActivity : BaseActivity(), Refreshable {
}
fun onImport(item: MenuItem) {
if (info.type == CollectionInfo.Type.TASKS) {
val dialog = AlertDialog.Builder(this)
.setIcon(R.drawable.ic_info_dark)
.setTitle("Not Implemented")
.setMessage("Importing tasks is not yet implemented")
dialog.show()
return
}
startActivity(ImportActivity.newIntent(this@ViewCollectionActivity, account, info))
}
@ -166,39 +183,40 @@ class ViewCollectionActivity : BaseActivity(), Refreshable {
val journalEntity = JournalEntity.fetch(data, info.getServiceEntity(data), info.uid)
entryCount = data.count(EntryEntity::class.java).where(EntryEntity.JOURNAL.eq(journalEntity)).get().value()
val count: Long
var count: Long = -1
if (info.type == CollectionInfo.Type.CALENDAR) {
try {
val providerClient = contentResolver.acquireContentProviderClient(CalendarContract.CONTENT_URI)
val resource = LocalCalendar.findByName(account, providerClient, LocalCalendar.Factory, info.uid!!)
providerClient!!.release()
if (resource == null) {
return null
when (info.type) {
CollectionInfo.Type.CALENDAR -> {
try {
val providerClient = contentResolver.acquireContentProviderClient(CalendarContract.CONTENT_URI)
val resource = LocalCalendar.findByName(account, providerClient, LocalCalendar.Factory, info.uid!!)
providerClient!!.release()
if (resource == null) {
return null
}
count = resource.count()
} catch (e: FileNotFoundException) {
e.printStackTrace()
} catch (e: CalendarStorageException) {
e.printStackTrace()
}
count = resource.count()
} catch (e: FileNotFoundException) {
e.printStackTrace()
return null
} catch (e: CalendarStorageException) {
e.printStackTrace()
return null
}
} else {
try {
val providerClient = contentResolver.acquireContentProviderClient(ContactsContract.Contacts.CONTENT_URI)
val resource = LocalAddressBook.findByUid(this@ViewCollectionActivity, providerClient!!, account, info.uid!!)
providerClient.release()
if (resource == null) {
return null
CollectionInfo.Type.TASKS -> {
count = -1
}
CollectionInfo.Type.ADDRESS_BOOK -> {
try {
val providerClient = contentResolver.acquireContentProviderClient(ContactsContract.Contacts.CONTENT_URI)
val resource = LocalAddressBook.findByUid(this@ViewCollectionActivity, providerClient!!, account, info.uid!!)
providerClient.release()
if (resource == null) {
return null
}
count = resource.count()
} catch (e: ContactsStorageException) {
e.printStackTrace()
}
count = resource.count()
} catch (e: ContactsStorageException) {
e.printStackTrace()
return null
}
}
return count
}
@ -210,12 +228,19 @@ class ViewCollectionActivity : BaseActivity(), Refreshable {
if (result == null) {
stats.text = "Stats loading error."
} else {
if (info.type == CollectionInfo.Type.CALENDAR) {
stats.text = String.format(Locale.getDefault(), "Events: %d, Journal entries: %d",
result, entryCount)
} else {
stats.text = String.format(Locale.getDefault(), "Contacts: %d, Journal Entries: %d",
result, entryCount)
when (info.type) {
CollectionInfo.Type.CALENDAR -> {
stats.text = String.format(Locale.getDefault(), "Events: %d, Journal entries: %d",
result, entryCount)
}
CollectionInfo.Type.TASKS -> {
stats.text = String.format(Locale.getDefault(), "Tasks: (TBD), Journal entries: %d",
entryCount)
}
CollectionInfo.Type.ADDRESS_BOOK -> {
stats.text = String.format(Locale.getDefault(), "Contacts: %d, Journal Entries: %d",
result, entryCount)
}
}
}
}

View File

@ -137,12 +137,16 @@ class ListEntriesFragment : ListFragment(), AdapterView.OnItemClickListener {
// FIXME: hacky way to make it show sensible info
val fullContent = syncEntry.content
val prefix: String
if (info.type == CollectionInfo.Type.CALENDAR) {
prefix = "SUMMARY:"
} else {
prefix = "FN:"
var prefix = ""
when (info.type) {
CollectionInfo.Type.CALENDAR, CollectionInfo.Type.TASKS -> {
prefix = "SUMMARY:"
}
CollectionInfo.Type.ADDRESS_BOOK -> {
prefix = "FN:"
}
}
var content = getLine(fullContent, prefix)
content = content ?: "Not found"
tv.text = content

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M4,10.5c-0.83,0 -1.5,0.67 -1.5,1.5s0.67,1.5 1.5,1.5 1.5,-0.67 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM4,4.5c-0.83,0 -1.5,0.67 -1.5,1.5S3.17,7.5 4,7.5 5.5,6.83 5.5,6 4.83,4.5 4,4.5zM4,16.5c-0.83,0 -1.5,0.68 -1.5,1.5s0.68,1.5 1.5,1.5 1.5,-0.68 1.5,-1.5 -0.67,-1.5 -1.5,-1.5zM7,19h14v-2L7,17v2zM7,13h14v-2L7,11v2zM7,5v2h14L21,5L7,5z"/>
</vector>

View File

@ -100,5 +100,46 @@
</LinearLayout>
</android.support.v7.widget.CardView>
<android.support.v7.widget.CardView
android:id="@+id/taskdav"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:cardUseCompatPadding="true"
app:cardElevation="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<android.support.v7.widget.Toolbar
android:id="@+id/taskdav_menu"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:theme="@style/toolbar_theme"
style="@style/toolbar_style"
app:navigationIcon="@drawable/ic_task_light"
app:title="@string/settings_taskdav"
android:elevation="2dp" tools:ignore="UnusedAttribute"/>
<ProgressBar
android:id="@+id/taskdav_refreshing"
style="@style/Widget.AppCompat.ProgressBar.Horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:visibility="gone"
android:indeterminate="true"/>
<com.etesync.syncadapter.ui.widget.MaximizedListView
android:id="@+id/tasklists"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:choiceMode="multipleChoice"
android:descendantFocusability="beforeDescendants"/>
</LinearLayout>
</android.support.v7.widget.CardView>
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,116 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2006 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<FrameLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:background="#fafafa"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="0dp">
<RelativeLayout
android:id="@+id/task_info_loading_msg"
android:layout_width="match_parent"
android:layout_height="match_parent">
<ProgressBar
android:id="@+id/task_info_progress_bar"
android:layout_width="100dip"
android:layout_height="100dip"
android:indeterminate="true"
android:layout_centerInParent="true" />
<TextView
android:layout_below="@id/task_info_progress_bar"
android:layout_centerHorizontal="true"
android:layout_marginTop="16dip"
android:text="@string/loading"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</RelativeLayout>
<ScrollView
xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/task_info_scroll_view"
android:orientation="vertical"
android:layout_width="match_parent"
android:fadingEdge="none"
android:animateLayoutChanges="true"
android:visibility="gone"
android:layout_height="match_parent" >
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<!-- Container for the task's headline
Name, Date, Time & Location
-->
<include layout="@layout/task_info_headline" />
<!-- Organizer -->
<LinearLayout
android:id="@+id/organizer_container"
android:visibility="gone"
android:paddingRight="16dip"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<TextView
android:id="@+id/organizer_label"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:singleLine="true"
android:text="@string/event_info_organizer"
style="?android:attr/textAppearanceSmall"
android:textSize="14sp"/>
<TextView
android:id="@+id/organizer"
android:layout_width="0px"
android:layout_height="wrap_content"
android:ellipsize="end"
android:layout_weight="1"
android:singleLine="true"
android:layout_marginLeft="2dip"
android:textIsSelectable="true"
style="?android:attr/textAppearanceSmall"
android:textSize="14sp"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="16dip"
android:paddingRight="16dip"
android:layout_marginTop="8dip"
android:orientation="vertical">
<!-- DESCRIPTION -->
<TextView
android:id="@+id/description"
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
<TextView
android:id="@+id/attendees"
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
<TextView
android:id="@+id/reminders"
android:layout_height="wrap_content"
android:layout_width="match_parent"/>
</LinearLayout>
</LinearLayout>
</ScrollView>
</FrameLayout>

View File

@ -0,0 +1,62 @@
<?xml version="1.0" encoding="utf-8"?><!--
Copyright (C) 2013 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/event_info_headline"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_weight="1"
android:background="@color/orange400"
android:orientation="vertical"
android:paddingBottom="16dip"
android:paddingLeft="16dip"
android:paddingRight="16dip"
android:paddingTop="8dip">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal">
<!-- WHAT -->
<TextView
android:id="@+id/title"
style="?android:attr/textAppearanceLarge"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight=".8"
android:autoLink="all"
android:textColor="@color/White"
android:textIsSelectable="true"
android:textSize="24sp"
android:textStyle="bold" />
</LinearLayout>
<!-- WHERE -->
<TextView
android:id="@+id/where"
style="?android:attr/textAppearanceLarge"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginTop="4dip"
android:ellipsize="end"
android:singleLine="false"
android:textColor="@color/White"
android:textIsSelectable="true"
android:textSize="14sp" />
</LinearLayout>

View File

@ -0,0 +1,15 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
~ 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
-->
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item android:id="@+id/create_tasklist"
android:title="@string/create_tasklist"/>
</menu>

View File

@ -267,12 +267,15 @@
<string name="settings_sync_wifi_only_ssid_message">Enter the name of a WiFi network (SSID) to restrict synchronization to this network, or leave blank for all WiFi connections.</string>
<string name="settings_carddav">Contacts</string>
<string name="settings_caldav">Calendar</string>
<string name="settings_taskdav">Tasks</string>
<!-- collection management -->
<string name="create_addressbook">Create address book</string>
<string name="create_addressbook_display_name_hint">My Address Book</string>
<string name="create_calendar">Create calendar</string>
<string name="create_calendar_display_name_hint">My Calendar</string>
<string name="create_tasklist">Create task list</string>
<string name="create_tasklist_display_name_hint">My Task List</string>
<string name="edit_collection">Edit collection</string>
<string name="create_collection_color">Set the calendar\'s color</string>
<string name="create_collection_creating">Creating collection</string>