Skip to content

Commit

Permalink
Merge pull request #13149 from nextcloud/feature/add-calendar-events-…
Browse files Browse the repository at this point in the history
…and-contacts

Feature Open Calendar Events and Contacts
  • Loading branch information
alperozturk96 authored Jul 30, 2024
2 parents fac7508 + 2775f1c commit 62df824
Show file tree
Hide file tree
Showing 11 changed files with 389 additions and 24 deletions.
30 changes: 30 additions & 0 deletions app/src/main/java/com/nextcloud/model/SearchResultEntryType.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Alper Ozturk <[email protected]>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.nextcloud.model

import com.owncloud.android.R

enum class SearchResultEntryType {
CalendarEvent,
Folder,
Note,
Contact,
Deck,
Unknown;

fun iconId(): Int {
return when (this) {
Folder -> R.drawable.folder
Note -> R.drawable.ic_edit
Contact -> R.drawable.file_vcard
CalendarEvent -> R.drawable.file_calendar
Deck -> R.drawable.ic_deck
else -> R.drawable.ic_find_in_page
}
}
}
78 changes: 78 additions & 0 deletions app/src/main/java/com/nextcloud/utils/CalendarEventManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,78 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Alper Ozturk <[email protected]>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.nextcloud.utils

import android.Manifest
import android.content.ContentUris
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.CalendarContract
import com.nextcloud.utils.extensions.showToast
import com.owncloud.android.R
import com.owncloud.android.lib.common.SearchResultEntry
import com.owncloud.android.ui.interfaces.UnifiedSearchListInterface
import com.owncloud.android.utils.PermissionUtil.checkSelfPermission

class CalendarEventManager(private val context: Context) {

fun openCalendarEvent(searchResult: SearchResultEntry, listInterface: UnifiedSearchListInterface) {
val havePermission = checkSelfPermission(context, Manifest.permission.READ_CALENDAR)
val createdAt = searchResult.createdAt()
val eventId: Long? = if (havePermission && createdAt != null) {
getCalendarEventId(searchResult.title, createdAt)
} else {
null
}

if (eventId == null) {
val messageId = if (havePermission) {
R.string.unified_search_fragment_calendar_event_not_found
} else {
R.string.unified_search_fragment_permission_needed
}
context.showToast(messageId)
listInterface.onSearchResultClicked(searchResult)
} else {
val uri: Uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, eventId)
val intent = Intent(Intent.ACTION_VIEW).setData(uri)
context.startActivity(intent)
}
}

private fun getCalendarEventId(eventTitle: String, eventStartDate: Long): Long? {
val projection = arrayOf(
CalendarContract.Events._ID,
CalendarContract.Events.TITLE,
CalendarContract.Events.DTSTART
)

val selection = "${CalendarContract.Events.TITLE} = ? AND ${CalendarContract.Events.DTSTART} = ?"
val selectionArgs = arrayOf(eventTitle, eventStartDate.toString())

val cursor = context.contentResolver.query(
CalendarContract.Events.CONTENT_URI,
projection,
selection,
selectionArgs,
"${CalendarContract.Events.DTSTART} ASC"
)

cursor?.use {
if (cursor.moveToFirst()) {
val idIndex = cursor.getColumnIndex(CalendarContract.Events._ID)
return cursor.getLong(idIndex)
}
}

return null
}
}

@Suppress("MagicNumber")
private fun SearchResultEntry.createdAt(): Long? = attributes["createdAt"]?.toLongOrNull()?.times(1000L)
144 changes: 144 additions & 0 deletions app/src/main/java/com/nextcloud/utils/ContactManager.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,144 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Alper Ozturk <[email protected]>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.nextcloud.utils

import android.Manifest
import android.content.Context
import android.content.Intent
import android.net.Uri
import android.provider.ContactsContract
import com.nextcloud.utils.extensions.showToast
import com.owncloud.android.R
import com.owncloud.android.lib.common.SearchResultEntry
import com.owncloud.android.ui.interfaces.UnifiedSearchListInterface
import com.owncloud.android.utils.PermissionUtil.checkSelfPermission

class ContactManager(private val context: Context) {

fun openContact(searchResult: SearchResultEntry, listInterface: UnifiedSearchListInterface) {
val havePermission = checkSelfPermission(context, Manifest.permission.READ_CONTACTS)
val displayName = searchResult.displayName()
val contactId: Long? = if (havePermission && displayName != null) {
getContactIds(displayName).let { contactIds ->
if (contactIds.size > 1) getContactId(searchResult, contactIds) else contactIds.firstOrNull()
}
} else {
null
}

if (contactId == null) {
val messageId = if (havePermission) {
R.string.unified_search_fragment_contact_not_found
} else {
R.string.unified_search_fragment_permission_needed
}
context.showToast(messageId)
listInterface.onSearchResultClicked(searchResult)
} else {
val uri = Uri.withAppendedPath(ContactsContract.Contacts.CONTENT_URI, contactId.toString())
val intent = Intent(Intent.ACTION_VIEW).apply {
setData(uri)
}
context.startActivity(intent)
}
}

private fun getContactId(searchResult: SearchResultEntry, contactIds: List<Long>): Long? {
val email = searchResult.email()
val phoneNumber = searchResult.phoneNumber()

contactIds.forEach {
val targetEmail = getEmailById(it) ?: ""
val targetPhoneNumber = getPhoneNumberById(it) ?: ""
if (targetEmail == email && targetPhoneNumber == phoneNumber) {
return it
}
}

return null
}

private fun getEmailById(contactId: Long): String? {
var result: String? = null
val projection = arrayOf(ContactsContract.CommonDataKinds.Email.ADDRESS)
val selection = "${ContactsContract.CommonDataKinds.Email.CONTACT_ID} = ?"
val selectionArgs = arrayOf(contactId.toString())

val cursor = context.contentResolver.query(
ContactsContract.CommonDataKinds.Email.CONTENT_URI,
projection,
selection,
selectionArgs,
null
)

cursor?.use {
val emailIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Email.ADDRESS)
while (cursor.moveToNext()) {
result = cursor.getString(emailIndex)
}
}

return result
}

private fun getPhoneNumberById(contactId: Long): String? {
var result: String? = null
val projection = arrayOf(ContactsContract.CommonDataKinds.Phone.NUMBER)
val selection = "${ContactsContract.CommonDataKinds.Phone.CONTACT_ID} = ?"
val selectionArgs = arrayOf(contactId.toString())

val cursor = context.contentResolver.query(
ContactsContract.CommonDataKinds.Phone.CONTENT_URI,
projection,
selection,
selectionArgs,
null
)

cursor?.use {
val phoneIndex = cursor.getColumnIndex(ContactsContract.CommonDataKinds.Phone.NUMBER)
while (cursor.moveToNext()) {
result = cursor.getString(phoneIndex)
}
}

return result
}

private fun getContactIds(displayName: String): List<Long> {
val result = arrayListOf<Long>()
val projection = arrayOf(ContactsContract.Contacts._ID)
val selection = "${ContactsContract.Contacts.DISPLAY_NAME} = ?"
val selectionArgs = arrayOf(displayName)

val cursor = context.contentResolver.query(
ContactsContract.Contacts.CONTENT_URI,
projection,
selection,
selectionArgs,
null
)

cursor?.use {
val idIndex = cursor.getColumnIndex(ContactsContract.Contacts._ID)
while (cursor.moveToNext()) {
val id = cursor.getLong(idIndex)
result.add(id)
}
}

return result
}
}

private fun SearchResultEntry.displayName(): String? = attributes["displayName"]

private fun SearchResultEntry.email(): String? = attributes["email"]

private fun SearchResultEntry.phoneNumber(): String? = attributes["phoneNumber"]
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
/*
* Nextcloud - Android Client
*
* SPDX-FileCopyrightText: 2024 Alper Ozturk <[email protected]>
* SPDX-License-Identifier: AGPL-3.0-or-later
*/

package com.nextcloud.utils.extensions

import com.nextcloud.model.SearchResultEntryType
import com.owncloud.android.lib.common.SearchResultEntry

fun SearchResultEntry.getType(): SearchResultEntryType {
return if (icon == "icon-folder") {
SearchResultEntryType.Folder
} else if (icon.startsWith("icon-note")) {
SearchResultEntryType.Note
} else if (icon.startsWith("icon-contacts")) {
SearchResultEntryType.Contact
} else if (icon.startsWith("icon-calendar")) {
SearchResultEntryType.CalendarEvent
} else if (icon.startsWith("icon-deck")) {
SearchResultEntryType.Deck
} else {
SearchResultEntryType.Unknown
}
}
Loading

0 comments on commit 62df824

Please sign in to comment.