Implement showing changelog item.

pull/131/head
Tom Hacohen 4 years ago
parent 71e37fb9a6
commit cea7f8fdc6

@ -0,0 +1,436 @@
package com.etesync.syncadapter.ui.etebase
import android.content.Context
import android.os.Bundle
import android.text.format.DateFormat
import android.text.format.DateUtils
import android.view.*
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.fragment.app.FragmentManager
import androidx.fragment.app.FragmentPagerAdapter
import androidx.fragment.app.activityViewModels
import androidx.viewpager.widget.ViewPager
import at.bitfire.ical4android.Event
import at.bitfire.ical4android.InvalidCalendarException
import at.bitfire.ical4android.Task
import at.bitfire.vcard4android.Contact
import com.etesync.syncadapter.CachedCollection
import com.etesync.syncadapter.CachedItem
import com.etesync.syncadapter.Constants
import com.etesync.syncadapter.R
import com.etesync.syncadapter.ui.BaseActivity
import com.google.android.material.tabs.TabLayout
import ezvcard.util.PartialDate
import org.jetbrains.anko.doAsync
import org.jetbrains.anko.uiThread
import java.io.IOException
import java.io.StringReader
import java.text.SimpleDateFormat
import java.util.*
import java.util.concurrent.Future
class CollectionItemFragment(private val cachedItem: CachedItem) : Fragment() {
private val collectionModel: CollectionViewModel by activityViewModels()
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val ret = inflater.inflate(R.layout.journal_item_activity, container, false)
setHasOptionsMenu(true)
if (savedInstanceState == null) {
collectionModel.observe(this) {
(activity as? BaseActivity?)?.supportActionBar?.title = it.meta.name
if (container != null) {
initUi(inflater, ret, it)
}
}
}
return ret
}
private fun initUi(inflater: LayoutInflater, v: View, cachedCollection: CachedCollection) {
val viewPager = v.findViewById<ViewPager>(R.id.viewpager)
viewPager.adapter = TabsAdapter(childFragmentManager, requireContext(), cachedCollection, cachedItem)
val tabLayout = v.findViewById<TabLayout>(R.id.tabs)
tabLayout.setupWithViewPager(viewPager)
ListEntriesFragment.setItemView(v.findViewById(R.id.journal_list_item), cachedCollection.meta.collectionType, cachedItem)
}
override fun onCreateOptionsMenu(menu: Menu, inflater: MenuInflater) {
inflater.inflate(R.menu.collection_item_fragment, menu)
// menu.setGroupVisible(R.id.journal_item_menu_event_invite, emailInvitationEvent != null)
}
}
private class TabsAdapter(fm: FragmentManager, private val context: Context, private val cachedCollection: CachedCollection, private val cachedItem: CachedItem) : FragmentPagerAdapter(fm) {
override fun getCount(): Int {
// FIXME: Make it depend on info enumType (only have non-raw for known types)
return 2
}
override fun getPageTitle(position: Int): CharSequence? {
return if (position == 0) {
context.getString(R.string.journal_item_tab_main)
} else {
context.getString(R.string.journal_item_tab_raw)
}
}
override fun getItem(position: Int): Fragment {
return if (position == 0) {
PrettyFragment(cachedCollection, cachedItem.content)
} else {
TextFragment(cachedItem.content)
}
}
}
class TextFragment(private val content: String) : Fragment() {
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
val v = inflater.inflate(R.layout.text_fragment, container, false)
val tv = v.findViewById<View>(R.id.content) as TextView
tv.text = content
return v
}
}
class PrettyFragment(private val cachedCollection: CachedCollection, private val content: String) : Fragment() {
private var asyncTask: Future<Unit>? = null
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
var v: View? = null
when (cachedCollection.meta.collectionType) {
Constants.ETEBASE_TYPE_ADDRESS_BOOK -> {
v = inflater.inflate(R.layout.contact_info, container, false)
asyncTask = loadContactTask(v)
}
Constants.ETEBASE_TYPE_CALENDAR -> {
v = inflater.inflate(R.layout.event_info, container, false)
asyncTask = loadEventTask(v)
}
Constants.ETEBASE_TYPE_TASKS -> {
v = inflater.inflate(R.layout.task_info, container, false)
asyncTask = loadTaskTask(v)
}
}
return v
}
override fun onDestroyView() {
super.onDestroyView()
if (asyncTask != null)
asyncTask!!.cancel(true)
}
private fun loadEventTask(view: View): Future<Unit> {
return doAsync {
var event: Event? = null
val inputReader = StringReader(content)
try {
event = Event.eventsFromReader(inputReader, null)[0]
} catch (e: InvalidCalendarException) {
e.printStackTrace()
} catch (e: IOException) {
e.printStackTrace()
}
if (event != null) {
uiThread {
val loader = view.findViewById<View>(R.id.event_info_loading_msg)
loader.visibility = View.GONE
val contentContainer = view.findViewById<View>(R.id.event_info_scroll_view)
contentContainer.visibility = View.VISIBLE
setTextViewText(view, R.id.title, event.summary)
setTextViewText(view, R.id.when_datetime, getDisplayedDatetime(event.dtStart?.date?.time!!, event.dtEnd?.date!!.time, event.isAllDay(), context))
setTextViewText(view, R.id.where, event.location)
val organizer = event.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, event.description)
var first = true
var sb = StringBuilder()
for (attendee in event.attendees) {
if (first) {
first = false
sb.append(getString(R.string.journal_item_attendees)).append(": ")
} else {
sb.append(", ")
}
sb.append(attendee.calAddress.toString().replaceFirst("mailto:".toRegex(), ""))
}
setTextViewText(view, R.id.attendees, sb.toString())
first = true
sb = StringBuilder()
for (alarm in event.alarms) {
if (first) {
first = false
sb.append(getString(R.string.journal_item_reminders)).append(": ")
} else {
sb.append(", ")
}
sb.append(alarm.trigger.value)
}
setTextViewText(view, R.id.reminders, sb.toString())
/* FIXME:
if (event.attendees.isNotEmpty() && activity != null) {
(activity as JournalItemActivity).allowSendEmail(event, syncEntry.content)
}
*/
}
}
}
}
private fun loadTaskTask(view: View): Future<Unit> {
return doAsync {
var task: Task? = null
val inputReader = StringReader(content)
try {
task = Task.tasksFromReader(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
val reader = StringReader(content)
try {
contact = Contact.fromReader(reader, null)[0]
} catch (e: IOException) {
e.printStackTrace()
}
if (contact != null) {
uiThread {
val loader = view.findViewById<View>(R.id.loading_msg)
loader.visibility = View.GONE
val contentContainer = view.findViewById<View>(R.id.content_container)
contentContainer.visibility = View.VISIBLE
val tv = view.findViewById<View>(R.id.display_name) as TextView
tv.text = contact.displayName
if (contact.group) {
showGroup(contact)
} else {
showContact(contact)
}
}
}
}
}
private fun showGroup(contact: Contact) {
val view = requireView()
val mainCard = view.findViewById<View>(R.id.main_card) as ViewGroup
addInfoItem(view.context, mainCard, getString(R.string.journal_item_member_count), null, contact.members.size.toString())
for (member in contact.members) {
addInfoItem(view.context, mainCard, getString(R.string.journal_item_member), null, member)
}
}
private fun showContact(contact: Contact) {
val view = requireView()
val mainCard = view.findViewById<View>(R.id.main_card) as ViewGroup
val aboutCard = view.findViewById<View>(R.id.about_card) as ViewGroup
aboutCard.findViewById<View>(R.id.title_container).visibility = View.VISIBLE
// TEL
for (labeledPhone in contact.phoneNumbers) {
val types = labeledPhone.property.types
val type = if (types.size > 0) types[0].value else null
addInfoItem(view.context, mainCard, getString(R.string.journal_item_phone), type, labeledPhone.property.text)
}
// EMAIL
for (labeledEmail in contact.emails) {
val types = labeledEmail.property.types
val type = if (types.size > 0) types[0].value else null
addInfoItem(view.context, mainCard, getString(R.string.journal_item_email), type, labeledEmail.property.value)
}
// ORG, TITLE, ROLE
if (contact.organization != null) {
addInfoItem(view.context, aboutCard, getString(R.string.journal_item_organization), contact.jobTitle, contact.organization?.values!![0])
}
if (contact.jobDescription != null) {
addInfoItem(view.context, aboutCard, getString(R.string.journal_item_job_description), null, contact.jobTitle)
}
// IMPP
for (labeledImpp in contact.impps) {
addInfoItem(view.context, mainCard, getString(R.string.journal_item_impp), labeledImpp.property.protocol, labeledImpp.property.handle)
}
// NICKNAME
if (contact.nickName != null && !contact.nickName?.values?.isEmpty()!!) {
addInfoItem(view.context, aboutCard, getString(R.string.journal_item_nickname), null, contact.nickName?.values!![0])
}
// ADR
for (labeledAddress in contact.addresses) {
val types = labeledAddress.property.types
val type = if (types.size > 0) types[0].value else null
addInfoItem(view.context, mainCard, getString(R.string.journal_item_address), type, labeledAddress.property.label)
}
// NOTE
if (contact.note != null) {
addInfoItem(view.context, aboutCard, getString(R.string.journal_item_note), null, contact.note)
}
// URL
for (labeledUrl in contact.urls) {
addInfoItem(view.context, aboutCard, getString(R.string.journal_item_website), null, labeledUrl.property.value)
}
// ANNIVERSARY
if (contact.anniversary != null) {
addInfoItem(view.context, aboutCard, getString(R.string.journal_item_anniversary), null, getDisplayedDate(contact.anniversary?.date, contact.anniversary?.partialDate))
}
// BDAY
if (contact.birthDay != null) {
addInfoItem(view.context, aboutCard, getString(R.string.journal_item_birthday), null, getDisplayedDate(contact.birthDay?.date, contact.birthDay?.partialDate))
}
// RELATED
for (related in contact.relations) {
val types = related.types
val type = if (types.size > 0) types[0].value else null
addInfoItem(view.context, aboutCard, getString(R.string.journal_item_relation), type, related.text)
}
// PHOTO
// if (contact.photo != null)
}
private fun getDisplayedDate(date: Date?, partialDate: PartialDate?): String? {
if (date != null) {
val epochDate = date.time
return getDisplayedDatetime(epochDate, epochDate, true, context)
} else if (partialDate != null){
val formatter = SimpleDateFormat("d MMMM", Locale.getDefault())
val calendar = GregorianCalendar()
calendar.set(Calendar.DAY_OF_MONTH, partialDate.date!!)
calendar.set(Calendar.MONTH, partialDate.month!! - 1)
return formatter.format(calendar.time)
}
return null
}
companion object {
private fun addInfoItem(context: Context, parent: ViewGroup, type: String, label: String?, value: String?): View {
val layout = parent.findViewById<View>(R.id.container) as ViewGroup
val 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.visibility = View.VISIBLE
return infoItem
}
private fun setTextViewText(parent: View, id: Int, text: String?) {
val tv = parent.findViewById<View>(id) as TextView
if (text == null) {
tv.visibility = View.GONE
} else {
tv.text = text
}
}
fun getDisplayedDatetime(startMillis: Long, endMillis: Long, allDay: Boolean, context: Context?): String? {
// Configure date/time formatting.
val flagsDate = DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_WEEKDAY
var flagsTime = DateUtils.FORMAT_SHOW_TIME
if (DateFormat.is24HourFormat(context)) {
flagsTime = flagsTime or DateUtils.FORMAT_24HOUR
}
val datetimeString: String
if (allDay) {
// For multi-day allday events or single-day all-day events that are not
// today or tomorrow, use framework formatter.
// We need to remove 24hrs because full day events are from the start of a day until the start of the next
var adjustedEnd = endMillis - 24 * 60 * 60 * 1000;
if (adjustedEnd < startMillis) {
adjustedEnd = startMillis;
}
val f = Formatter(StringBuilder(50), Locale.getDefault())
datetimeString = DateUtils.formatDateRange(context, f, startMillis,
adjustedEnd, flagsDate).toString()
} else {
// For multiday events, shorten day/month names.
// Example format: "Fri Apr 6, 5:00pm - Sun, Apr 8, 6:00pm"
val flagsDatetime = flagsDate or flagsTime or DateUtils.FORMAT_ABBREV_MONTH or
DateUtils.FORMAT_ABBREV_WEEKDAY
datetimeString = DateUtils.formatDateRange(context, startMillis, endMillis,
flagsDatetime)
}
return datetimeString
}
}
}

@ -8,6 +8,7 @@ import android.view.ViewGroup
import android.widget.*
import androidx.fragment.app.ListFragment
import androidx.fragment.app.activityViewModels
import androidx.fragment.app.commit
import com.etesync.syncadapter.CachedCollection
import com.etesync.syncadapter.CachedItem
import com.etesync.syncadapter.Constants
@ -60,8 +61,10 @@ class ListEntriesFragment : ListFragment(), AdapterView.OnItemClickListener {
override fun onItemClick(parent: AdapterView<*>, view: View, position: Int, id: Long) {
val item = listAdapter?.getItem(position) as CachedItem
Toast.makeText(context, "Clicked ${item.item.uid}", Toast.LENGTH_LONG).show()
// startActivity(JournalItemActivity.newIntent(requireContext(), account, info, entry.content))
activity?.supportFragmentManager?.commit {
replace(R.id.fragment_container, CollectionItemFragment(item))
addToBackStack(EditCollectionFragment::class.java.name)
}
}
internal inner class EntriesListAdapter(context: Context, val cachedCollection: CachedCollection) : ArrayAdapter<CachedItem>(context, R.layout.journal_viewer_list_item) {

@ -0,0 +1,26 @@
<?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"
xmlns:app="http://schemas.android.com/apk/res-auto">
<group android:id="@+id/journal_item_menu_event_invite">
<item android:title="@string/calendar_attendees_send_email_action"
android:id="@+id/on_send_event_invite"
android:icon="@drawable/ic_email_black"
app:showAsAction="always" />
</group>
<group android:id="@+id/journal_item_menu_restore">
<item android:title="@string/journal_item_restore_action"
android:icon="@drawable/ic_restore_black"
android:id="@+id/on_restore_item"
app:showAsAction="never" />
</group>
</menu>
Loading…
Cancel
Save