diff --git a/build.gradle b/build.gradle
deleted file mode 100644
index b8a80c0..0000000
--- a/build.gradle
+++ /dev/null
@@ -1,27 +0,0 @@
-// Top-level build file where you can add configuration options common to all sub-projects/modules.
-
-buildscript {
- ext.kotlin_version = '1.2.10'
- repositories {
- jcenter()
- google()
- }
- dependencies {
- classpath 'com.android.tools.build:gradle:3.0.1'
- classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
- classpath 'com.jfrog.bintray.gradle:gradle-bintray-plugin:1.7.2'
- classpath 'com.github.dcendents:android-maven-gradle-plugin:1.4.1'
- }
-}
-
-allprojects {
- repositories {
- jcenter()
- mavenCentral()
- google()
- }
-}
-
-task clean(type: Delete) {
- delete rootProject.buildDir
-}
diff --git a/build.gradle.kts b/build.gradle.kts
new file mode 100644
index 0000000..b5cd7bf
--- /dev/null
+++ b/build.gradle.kts
@@ -0,0 +1,7 @@
+plugins {
+ alias(libs.plugins.kotlinMultiplatform) apply false
+ alias(libs.plugins.kotlinCompose) apply false
+ alias(libs.plugins.composeMultiplatform) apply false
+ alias(libs.plugins.androidLibrary) apply false
+ alias(libs.plugins.androidApplication) apply false
+}
diff --git a/gradle.properties b/gradle.properties
index 727136e..227b0dc 100644
--- a/gradle.properties
+++ b/gradle.properties
@@ -1,23 +1,9 @@
-# Project-wide Gradle settings.
-
-# IDE (e.g. Android Studio) users:
-# Gradle settings configured through the IDE *will override*
-# any settings specified in this file.
-
-# For more details on how to configure your build environment visit
-# http://www.gradle.org/docs/current/userguide/build_environment.html
-
-# Specifies the JVM arguments used for the daemon process.
-# The setting is particularly useful for tweaking memory settings.
-org.gradle.jvmargs=-Xmx1536m
-
-# When configured, Gradle will run in incubating parallel mode.
-# This option should only be used with decoupled projects. More details, visit
-# http://www.gradle.org/docs/current/userguide/multi_project_builds.html#sec:decoupled_projects
-# org.gradle.parallel=true
-
-kotlin.incremental=true
-
-
-
-version=1.0.0
+org.gradle.jvmargs=-Xmx2048m -Dfile.encoding=UTF-8
+org.gradle.parallel=true
+org.gradle.caching=true
+
+android.useAndroidX=true
+android.nonTransitiveRClass=true
+
+kotlin.code.style=official
+kotlin.mpp.androidSourceSetLayoutVersion=2
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
new file mode 100644
index 0000000..34b53a9
--- /dev/null
+++ b/gradle/libs.versions.toml
@@ -0,0 +1,27 @@
+[versions]
+kotlin = "2.1.10"
+agp = "8.7.3"
+coroutines = "1.9.0"
+compose-multiplatform = "1.7.3"
+androidx-core = "1.15.0"
+androidx-activity-compose = "1.9.3"
+moko-permissions = "0.20.1"
+compileSdk = "35"
+minSdk = "24"
+targetSdk = "35"
+
+[libraries]
+kotlinx-coroutines-core = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-core", version.ref = "coroutines" }
+kotlinx-coroutines-android = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-android", version.ref = "coroutines" }
+kotlinx-coroutines-swing = { module = "org.jetbrains.kotlinx:kotlinx-coroutines-swing", version.ref = "coroutines" }
+androidx-core = { module = "androidx.core:core-ktx", version.ref = "androidx-core" }
+androidx-activity-compose = { module = "androidx.activity:activity-compose", version.ref = "androidx-activity-compose" }
+moko-permissions-core = { module = "dev.icerock.moko:permissions", version.ref = "moko-permissions" }
+moko-permissions-compose = { module = "dev.icerock.moko:permissions-compose", version.ref = "moko-permissions" }
+
+[plugins]
+kotlinMultiplatform = { id = "org.jetbrains.kotlin.multiplatform", version.ref = "kotlin" }
+kotlinCompose = { id = "org.jetbrains.kotlin.plugin.compose", version.ref = "kotlin" }
+composeMultiplatform = { id = "org.jetbrains.compose", version.ref = "compose-multiplatform" }
+androidLibrary = { id = "com.android.library", version.ref = "agp" }
+androidApplication = { id = "com.android.application", version.ref = "agp" }
diff --git a/kontact-rxjava/.gitignore b/kontact-rxjava/.gitignore
deleted file mode 100644
index 796b96d..0000000
--- a/kontact-rxjava/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
diff --git a/kontact-rxjava/build.gradle b/kontact-rxjava/build.gradle
deleted file mode 100644
index a4e84dc..0000000
--- a/kontact-rxjava/build.gradle
+++ /dev/null
@@ -1,70 +0,0 @@
-apply plugin: 'com.android.library'
-apply plugin: 'maven-publish'
-
-android {
- compileSdkVersion 26
- buildToolsVersion "26.0.2"
-
- defaultConfig {
- minSdkVersion 16
- targetSdkVersion 26
-
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
-
- }
-
- sourceSets {
- main.java.srcDirs += 'src/main/kotlin'
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-}
-
-dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
- exclude group: 'com.android.support', module: 'support-annotations'
- })
-
- compile "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
- provided project(path: ':kontact')
- testCompile 'junit:junit:4.12'
-
- compile 'io.reactivex:rxjava:1.2.9'
-}
-
-def artifactVersion = properties.version
-
-ext {
- bintrayRepo = 'Kontact'
- bintrayName = 'Kontact-rxJava'
-
- publishedGroupId = 'com.hackedcube'
- libraryName = 'Kontact'
- artifact = 'kontact-rxjava'
-
- libraryDescription = 'Full object mapping and querying of the Android Contact API'
-
- siteUrl = 'https://github.com/rubixhacker/Kontact'
- gitUrl = 'https://github.com/rubixhacker/Kontact.git'
-
- libraryVersion = artifactVersion
-
- developerId = 'rubixhacker'
- developerName = 'Stewart Boling'
- developerEmail = 'rubixhacker@gmail.com'
-
- licenseName = 'The Apache Software License, Version 2.0'
- licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
- allLicenses = ["Apache-2.0"]
-}
-
-tasks.withType(Javadoc).all {
- enabled = false
-}
-
-apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle'
-apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle'
diff --git a/kontact-rxjava/proguard-rules.pro b/kontact-rxjava/proguard-rules.pro
deleted file mode 100644
index c6e4461..0000000
--- a/kontact-rxjava/proguard-rules.pro
+++ /dev/null
@@ -1,25 +0,0 @@
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in C:\Users\rubix\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the proguardFiles
-# directive in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile
diff --git a/kontact-rxjava/src/main/AndroidManifest.xml b/kontact-rxjava/src/main/AndroidManifest.xml
deleted file mode 100644
index e6f756b..0000000
--- a/kontact-rxjava/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/kontact-rxjava/src/main/kotlin/com/hackedcube/kontact/rxjava/RxContactUtils.kt b/kontact-rxjava/src/main/kotlin/com/hackedcube/kontact/rxjava/RxContactUtils.kt
deleted file mode 100644
index 65f3ad6..0000000
--- a/kontact-rxjava/src/main/kotlin/com/hackedcube/kontact/rxjava/RxContactUtils.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-@file:JvmName("RxContactUtils")
-
-package com.hackedcube.kontact.rxjava
-
-import android.content.Context
-import android.net.Uri
-import com.hackedcube.kontact.Kontact
-import com.hackedcube.kontact.getContactFromId
-import com.hackedcube.kontact.queryAllContacts
-import rx.Single
-
-fun Context.allContactsSingle(): Single> {
- return Single.fromCallable { queryAllContacts() }
-}
-
-fun Context.contactSingle(uri: Uri): Single {
- return Single.fromCallable { getContactFromId(uri) }
-}
-
-
diff --git a/kontact-rxjava2/.gitignore b/kontact-rxjava2/.gitignore
deleted file mode 100644
index 796b96d..0000000
--- a/kontact-rxjava2/.gitignore
+++ /dev/null
@@ -1 +0,0 @@
-/build
diff --git a/kontact-rxjava2/build.gradle b/kontact-rxjava2/build.gradle
deleted file mode 100644
index dd3933e..0000000
--- a/kontact-rxjava2/build.gradle
+++ /dev/null
@@ -1,74 +0,0 @@
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
-apply plugin: 'maven-publish'
-
-android {
- compileSdkVersion 26
- buildToolsVersion "26.0.2"
-
- defaultConfig {
- minSdkVersion 16
- targetSdkVersion 26
-
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
-
- }
-
- sourceSets {
- main.java.srcDirs += 'src/main/kotlin'
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-}
-
-dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
- exclude group: 'com.android.support', module: 'support-annotations'
- })
-
- provided project(path: ':kontact')
- testCompile 'junit:junit:4.12'
-
- compile 'io.reactivex.rxjava2:rxjava:2.1.4'
- compile "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
-}
-
-def artifactVersion = properties.version
-
-ext {
- bintrayRepo = 'Kontact'
- bintrayName = 'Kontact-rxJava2'
-
- publishedGroupId = 'com.hackedcube'
- libraryName = 'Kontact'
- artifact = 'kontact-rxjava2'
-
- libraryDescription = 'Full object mapping and querying of the Android Contact API'
-
- siteUrl = 'https://github.com/rubixhacker/Kontact'
- gitUrl = 'https://github.com/rubixhacker/Kontact.git'
-
- libraryVersion = artifactVersion
-
- developerId = 'rubixhacker'
- developerName = 'Stewart Boling'
- developerEmail = 'rubixhacker@gmail.com'
-
- licenseName = 'The Apache Software License, Version 2.0'
- licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
- allLicenses = ["Apache-2.0"]
-}
-
-tasks.withType(Javadoc).all {
- enabled = false
-}
-
-apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle'
-apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle'
-repositories {
- mavenCentral()
-}
diff --git a/kontact-rxjava2/proguard-rules.pro b/kontact-rxjava2/proguard-rules.pro
deleted file mode 100644
index c6e4461..0000000
--- a/kontact-rxjava2/proguard-rules.pro
+++ /dev/null
@@ -1,25 +0,0 @@
-# Add project specific ProGuard rules here.
-# By default, the flags in this file are appended to flags specified
-# in C:\Users\rubix\AppData\Local\Android\Sdk/tools/proguard/proguard-android.txt
-# You can edit the include path and order by changing the proguardFiles
-# directive in build.gradle.
-#
-# For more details, see
-# http://developer.android.com/guide/developing/tools/proguard.html
-
-# Add any project specific keep options here:
-
-# If your project uses WebView with JS, uncomment the following
-# and specify the fully qualified class name to the JavaScript interface
-# class:
-#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
-# public *;
-#}
-
-# Uncomment this to preserve the line number information for
-# debugging stack traces.
-#-keepattributes SourceFile,LineNumberTable
-
-# If you keep the line number information, uncomment this to
-# hide the original source file name.
-#-renamesourcefileattribute SourceFile
diff --git a/kontact-rxjava2/src/main/AndroidManifest.xml b/kontact-rxjava2/src/main/AndroidManifest.xml
deleted file mode 100644
index 7255a09..0000000
--- a/kontact-rxjava2/src/main/AndroidManifest.xml
+++ /dev/null
@@ -1 +0,0 @@
-
\ No newline at end of file
diff --git a/kontact-rxjava2/src/main/kotlin/com/hackedcube/kontact/rxjava2/RxContactUtils.kt b/kontact-rxjava2/src/main/kotlin/com/hackedcube/kontact/rxjava2/RxContactUtils.kt
deleted file mode 100644
index 21d9ddc..0000000
--- a/kontact-rxjava2/src/main/kotlin/com/hackedcube/kontact/rxjava2/RxContactUtils.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-@file:JvmName("RxContactUtils")
-
-package com.hackedcube.kontact.rxjava2
-
-import android.content.Context
-import android.net.Uri
-import com.hackedcube.kontact.Kontact
-import com.hackedcube.kontact.getContactFromId
-import com.hackedcube.kontact.queryAllContacts
-import io.reactivex.Single
-
-fun Context.allContacts(): Single> {
- return Single.fromCallable { queryAllContacts() }
-}
-
-fun Context.contact(uri: Uri): Single {
- return Single.fromCallable { getContactFromId(uri) }
-}
\ No newline at end of file
diff --git a/kontact/build.gradle b/kontact/build.gradle
deleted file mode 100644
index 7628546..0000000
--- a/kontact/build.gradle
+++ /dev/null
@@ -1,78 +0,0 @@
-apply plugin: 'com.android.library'
-apply plugin: 'kotlin-android'
-apply plugin: 'kotlin-kapt'
-
-android {
- compileSdkVersion 26
- buildToolsVersion "26.0.2"
-
- defaultConfig {
- minSdkVersion 16
- targetSdkVersion 26
-
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
- }
-
- sourceSets {
- main.java.srcDirs += 'src/main/kotlin'
- }
-
- buildTypes {
- debug { }
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-}
-
-dependencies {
- implementation 'com.android.support:support-annotations:27.0.2'
- implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
-
- //AutoValue
- kapt 'com.google.auto.value:auto-value:1.5'
- compileOnly 'com.jakewharton.auto.value:auto-value-annotations:1.5'
- kapt 'com.gabrielittner.auto.value:auto-value-with:1.0.0'
- kapt 'com.gabrielittner.auto.value:auto-value-cursor:1.1.0'
- implementation 'com.gabrielittner.auto.value:auto-value-cursor-annotations:1.1.0'
-
- testCompile 'junit:junit:4.12'
-}
-repositories {
- mavenCentral()
-}
-
-def artifactVersion = properties.version
-
-ext {
- bintrayRepo = 'Kontact'
- bintrayName = 'Kontact'
-
- publishedGroupId = 'com.hackedcube'
- libraryName = 'Kontact'
- artifact = 'kontact'
-
- libraryDescription = 'Full object mapping and querying of the Android Contact API'
-
- siteUrl = 'https://github.com/rubixhacker/Kontact'
- gitUrl = 'https://github.com/rubixhacker/Kontact.git'
-
- libraryVersion = artifactVersion
-
- developerId = 'rubixhacker'
- developerName = 'Stewart Boling'
- developerEmail = 'rubixhacker@gmail.com'
-
- licenseName = 'The Apache Software License, Version 2.0'
- licenseUrl = 'http://www.apache.org/licenses/LICENSE-2.0.txt'
- allLicenses = ["Apache-2.0"]
-}
-
-tasks.withType(Javadoc).all {
- enabled = false
-}
-
-apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/installv1.gradle'
-apply from: 'https://raw.githubusercontent.com/nuuneoi/JCenter/master/bintrayv1.gradle'
\ No newline at end of file
diff --git a/kontact/build.gradle.kts b/kontact/build.gradle.kts
new file mode 100644
index 0000000..82c2ec1
--- /dev/null
+++ b/kontact/build.gradle.kts
@@ -0,0 +1,64 @@
+plugins {
+ alias(libs.plugins.kotlinMultiplatform)
+ alias(libs.plugins.androidLibrary)
+}
+
+kotlin {
+ androidTarget {
+ compilations.all {
+ compileTaskProvider.configure {
+ compilerOptions {
+ jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
+ }
+ }
+ }
+ }
+
+ jvm("desktop")
+
+ listOf(
+ iosX64(),
+ iosArm64(),
+ iosSimulatorArm64(),
+ ).forEach {
+ it.binaries.framework {
+ baseName = "Kontact"
+ isStatic = true
+ }
+ }
+
+ macosX64()
+ macosArm64()
+
+ @OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
+ wasmJs {
+ browser()
+ }
+
+ // Default hierarchy template creates appleMain automatically,
+ // shared by iosMain + macosMain — CNContacts code lives there.
+
+ sourceSets {
+ commonMain.dependencies {
+ implementation(libs.kotlinx.coroutines.core)
+ }
+ androidMain.dependencies {
+ implementation(libs.androidx.core)
+ implementation(libs.kotlinx.coroutines.android)
+ }
+ }
+}
+
+android {
+ namespace = "com.hackedcube.kontact"
+ compileSdk = libs.versions.compileSdk.get().toInt()
+
+ defaultConfig {
+ minSdk = libs.versions.minSdk.get().toInt()
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+}
diff --git a/kontact/src/main/AndroidManifest.xml b/kontact/src/androidMain/AndroidManifest.xml
similarity index 74%
rename from kontact/src/main/AndroidManifest.xml
rename to kontact/src/androidMain/AndroidManifest.xml
index cd78d8c..bc3866d 100644
--- a/kontact/src/main/AndroidManifest.xml
+++ b/kontact/src/androidMain/AndroidManifest.xml
@@ -1,6 +1,3 @@
-
-
-
-
-
+
+
+
diff --git a/kontact/src/androidMain/kotlin/com/hackedcube/kontact/AndroidKontactRepository.kt b/kontact/src/androidMain/kotlin/com/hackedcube/kontact/AndroidKontactRepository.kt
new file mode 100644
index 0000000..f127e05
--- /dev/null
+++ b/kontact/src/androidMain/kotlin/com/hackedcube/kontact/AndroidKontactRepository.kt
@@ -0,0 +1,174 @@
+package com.hackedcube.kontact
+
+import android.content.ContentResolver
+import android.content.Context
+import android.database.Cursor
+import android.provider.ContactsContract
+import android.provider.ContactsContract.CommonDataKinds
+import android.provider.ContactsContract.Contacts
+import android.provider.ContactsContract.Data
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import kotlinx.coroutines.flow.flowOn
+import kotlinx.coroutines.withContext
+
+class AndroidKontactRepository(private val context: Context) : KontactRepository {
+
+ private val contentResolver: ContentResolver
+ get() = context.contentResolver
+
+ override suspend fun queryAllContacts(): List = withContext(Dispatchers.IO) {
+ contentResolver.query(Contacts.CONTENT_URI, null, null, null, null)
+ ?.use { cursor ->
+ cursor.asSequence().map { buildKontact(it) }.toList()
+ } ?: emptyList()
+ }
+
+ override suspend fun getContact(id: String): Kontact? = withContext(Dispatchers.IO) {
+ contentResolver.query(
+ Contacts.CONTENT_URI,
+ null,
+ "${Contacts._ID} = ?",
+ arrayOf(id),
+ null,
+ )?.use { cursor ->
+ if (cursor.moveToFirst()) buildKontact(cursor) else null
+ }
+ }
+
+ override fun observeAllContacts(): Flow> = flow {
+ emit(queryAllContacts())
+ }.flowOn(Dispatchers.IO)
+
+ private fun buildKontact(cursor: Cursor): Kontact {
+ val id = cursor.getString(Contacts._ID)
+ val hasPhone = cursor.getInt(Contacts.HAS_PHONE_NUMBER) == 1
+
+ val base = Kontact(
+ id = id,
+ lookupKey = cursor.getString(Contacts.LOOKUP_KEY),
+ displayNamePrimary = cursor.getString(Contacts.DISPLAY_NAME_PRIMARY),
+ photoId = cursor.getLongOrNull(Contacts.PHOTO_ID),
+ photoUri = cursor.getStringOrNull(Contacts.PHOTO_URI),
+ thumbnailPhotoUri = cursor.getStringOrNull(Contacts.PHOTO_THUMBNAIL_URI),
+ isVisible = cursor.getInt(Contacts.IN_VISIBLE_GROUP) == 1,
+ hasPhoneNumber = hasPhone,
+ timesContacted = cursor.getInt(Contacts.TIMES_CONTACTED),
+ lastTimeContacted = cursor.getLong(Contacts.LAST_TIME_CONTACTED),
+ isStarred = cursor.getInt(Contacts.STARRED) == 1,
+ customRingtone = cursor.getStringOrNull(Contacts.CUSTOM_RINGTONE),
+ sendToVoicemail = cursor.getInt(Contacts.SEND_TO_VOICEMAIL) == 1,
+ )
+
+ val phoneNumbers = if (hasPhone) queryPhoneNumbers(id) else emptyList()
+ val emailAddresses = queryEmailAddresses(id)
+ val (events, nicknames, postalAddresses, relations) = queryAdditionalData(id)
+
+ return base.copy(
+ phoneNumbers = phoneNumbers,
+ emailAddresses = emailAddresses,
+ events = events,
+ nicknames = nicknames,
+ postalAddresses = postalAddresses,
+ relations = relations,
+ )
+ }
+
+ private fun queryPhoneNumbers(contactId: String): List {
+ return contentResolver.query(
+ CommonDataKinds.Phone.CONTENT_URI,
+ null,
+ "${CommonDataKinds.Phone.CONTACT_ID} = ?",
+ arrayOf(contactId),
+ null,
+ )?.use { cursor ->
+ cursor.asSequence().map { c ->
+ PhoneNumber(
+ id = c.getString(CommonDataKinds.Phone._ID),
+ number = c.getString(CommonDataKinds.Phone.NUMBER),
+ type = c.getInt(CommonDataKinds.Phone.TYPE).toPhoneType(),
+ label = c.getStringOrNull(CommonDataKinds.Phone.LABEL),
+ )
+ }.toList()
+ } ?: emptyList()
+ }
+
+ private fun queryEmailAddresses(contactId: String): List {
+ return contentResolver.query(
+ CommonDataKinds.Email.CONTENT_URI,
+ null,
+ "${CommonDataKinds.Email.CONTACT_ID} = ?",
+ arrayOf(contactId),
+ null,
+ )?.use { cursor ->
+ cursor.asSequence().map { c ->
+ EmailAddress(
+ id = c.getString(CommonDataKinds.Email._ID),
+ address = c.getString(CommonDataKinds.Email.ADDRESS),
+ type = c.getInt(CommonDataKinds.Email.TYPE).toEmailType(),
+ label = c.getStringOrNull(CommonDataKinds.Email.LABEL),
+ )
+ }.toList()
+ } ?: emptyList()
+ }
+
+ private data class AdditionalData(
+ val events: List,
+ val nicknames: List,
+ val postalAddresses: List,
+ val relations: List,
+ )
+
+ private fun queryAdditionalData(contactId: String): AdditionalData {
+ val events = mutableListOf()
+ val nicknames = mutableListOf()
+ val postalAddresses = mutableListOf()
+ val relations = mutableListOf()
+
+ val where = "${Data.CONTACT_ID} = ? AND ${Data.MIMETYPE} IN (?, ?, ?, ?)"
+ val whereParams = arrayOf(
+ contactId,
+ CommonDataKinds.Event.CONTENT_ITEM_TYPE,
+ CommonDataKinds.Nickname.CONTENT_ITEM_TYPE,
+ CommonDataKinds.Relation.CONTENT_ITEM_TYPE,
+ CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE,
+ )
+
+ contentResolver.query(Data.CONTENT_URI, null, where, whereParams, null)?.use { cursor ->
+ for (c in cursor.asSequence()) {
+ when (c.getString(Data.MIMETYPE)) {
+ CommonDataKinds.Event.CONTENT_ITEM_TYPE -> events += Event(
+ startDate = c.getStringOrNull(CommonDataKinds.Event.START_DATE),
+ type = c.getIntOrNull(CommonDataKinds.Event.TYPE)?.toEventType(),
+ label = c.getStringOrNull(CommonDataKinds.Event.LABEL),
+ )
+ CommonDataKinds.Nickname.CONTENT_ITEM_TYPE -> nicknames += Nickname(
+ name = c.getStringOrNull(CommonDataKinds.Nickname.NAME),
+ type = c.getIntOrNull(CommonDataKinds.Nickname.TYPE)?.toNicknameType(),
+ label = c.getStringOrNull(CommonDataKinds.Nickname.LABEL),
+ )
+ CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE -> postalAddresses += PostalAddress(
+ formattedAddress = c.getString(CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS),
+ type = c.getInt(CommonDataKinds.StructuredPostal.TYPE).toPostalAddressType(),
+ label = c.getStringOrNull(CommonDataKinds.StructuredPostal.LABEL),
+ street = c.getStringOrNull(CommonDataKinds.StructuredPostal.STREET),
+ pobox = c.getStringOrNull(CommonDataKinds.StructuredPostal.POBOX),
+ neighborhood = c.getStringOrNull(CommonDataKinds.StructuredPostal.NEIGHBORHOOD),
+ city = c.getStringOrNull(CommonDataKinds.StructuredPostal.CITY),
+ region = c.getStringOrNull(CommonDataKinds.StructuredPostal.REGION),
+ postcode = c.getStringOrNull(CommonDataKinds.StructuredPostal.POSTCODE),
+ country = c.getStringOrNull(CommonDataKinds.StructuredPostal.COUNTRY),
+ )
+ CommonDataKinds.Relation.CONTENT_ITEM_TYPE -> relations += Relation(
+ name = c.getString(CommonDataKinds.Relation.NAME),
+ type = c.getInt(CommonDataKinds.Relation.TYPE).toRelationType(),
+ label = c.getStringOrNull(CommonDataKinds.Relation.LABEL),
+ )
+ }
+ }
+ }
+
+ return AdditionalData(events, nicknames, postalAddresses, relations)
+ }
+}
diff --git a/kontact/src/androidMain/kotlin/com/hackedcube/kontact/CursorExtensions.kt b/kontact/src/androidMain/kotlin/com/hackedcube/kontact/CursorExtensions.kt
new file mode 100644
index 0000000..26a62fb
--- /dev/null
+++ b/kontact/src/androidMain/kotlin/com/hackedcube/kontact/CursorExtensions.kt
@@ -0,0 +1,34 @@
+package com.hackedcube.kontact
+
+import android.database.Cursor
+
+internal fun Cursor.getString(columnName: String): String {
+ return getString(getColumnIndexOrThrow(columnName))
+}
+
+internal fun Cursor.getStringOrNull(columnName: String): String? {
+ val index = getColumnIndex(columnName)
+ return if (index == -1 || isNull(index)) null else getString(index)
+}
+
+internal fun Cursor.getInt(columnName: String): Int {
+ return getInt(getColumnIndexOrThrow(columnName))
+}
+
+internal fun Cursor.getIntOrNull(columnName: String): Int? {
+ val index = getColumnIndex(columnName)
+ return if (index == -1 || isNull(index)) null else getInt(index)
+}
+
+internal fun Cursor.getLong(columnName: String): Long {
+ return getLong(getColumnIndexOrThrow(columnName))
+}
+
+internal fun Cursor.getLongOrNull(columnName: String): Long? {
+ val index = getColumnIndex(columnName)
+ return if (index == -1 || isNull(index)) null else getLong(index)
+}
+
+internal fun Cursor.asSequence(): Sequence = generateSequence {
+ if (moveToNext()) this else null
+}
diff --git a/kontact/src/androidMain/kotlin/com/hackedcube/kontact/TypeMappers.kt b/kontact/src/androidMain/kotlin/com/hackedcube/kontact/TypeMappers.kt
new file mode 100644
index 0000000..fece03a
--- /dev/null
+++ b/kontact/src/androidMain/kotlin/com/hackedcube/kontact/TypeMappers.kt
@@ -0,0 +1,76 @@
+package com.hackedcube.kontact
+
+import android.provider.ContactsContract.CommonDataKinds
+
+internal fun Int.toPhoneType(): PhoneNumber.Type = when (this) {
+ CommonDataKinds.Phone.TYPE_HOME -> PhoneNumber.Type.HOME
+ CommonDataKinds.Phone.TYPE_MOBILE -> PhoneNumber.Type.MOBILE
+ CommonDataKinds.Phone.TYPE_WORK -> PhoneNumber.Type.WORK
+ CommonDataKinds.Phone.TYPE_FAX_WORK -> PhoneNumber.Type.FAX_WORK
+ CommonDataKinds.Phone.TYPE_FAX_HOME -> PhoneNumber.Type.FAX_HOME
+ CommonDataKinds.Phone.TYPE_PAGER -> PhoneNumber.Type.PAGER
+ CommonDataKinds.Phone.TYPE_OTHER -> PhoneNumber.Type.OTHER
+ CommonDataKinds.Phone.TYPE_CALLBACK -> PhoneNumber.Type.CALLBACK
+ CommonDataKinds.Phone.TYPE_CAR -> PhoneNumber.Type.CAR
+ CommonDataKinds.Phone.TYPE_COMPANY_MAIN -> PhoneNumber.Type.COMPANY_MAIN
+ CommonDataKinds.Phone.TYPE_ISDN -> PhoneNumber.Type.ISDN
+ CommonDataKinds.Phone.TYPE_MAIN -> PhoneNumber.Type.MAIN
+ CommonDataKinds.Phone.TYPE_OTHER_FAX -> PhoneNumber.Type.OTHER_FAX
+ CommonDataKinds.Phone.TYPE_RADIO -> PhoneNumber.Type.RADIO
+ CommonDataKinds.Phone.TYPE_TELEX -> PhoneNumber.Type.TELEX
+ CommonDataKinds.Phone.TYPE_TTY_TDD -> PhoneNumber.Type.TTY_TDD
+ CommonDataKinds.Phone.TYPE_WORK_MOBILE -> PhoneNumber.Type.WORK_MOBILE
+ CommonDataKinds.Phone.TYPE_WORK_PAGER -> PhoneNumber.Type.WORK_PAGER
+ CommonDataKinds.Phone.TYPE_ASSISTANT -> PhoneNumber.Type.ASSISTANT
+ CommonDataKinds.Phone.TYPE_MMS -> PhoneNumber.Type.MMS
+ else -> PhoneNumber.Type.CUSTOM
+}
+
+internal fun Int.toEmailType(): EmailAddress.Type = when (this) {
+ CommonDataKinds.Email.TYPE_HOME -> EmailAddress.Type.HOME
+ CommonDataKinds.Email.TYPE_WORK -> EmailAddress.Type.WORK
+ CommonDataKinds.Email.TYPE_OTHER -> EmailAddress.Type.OTHER
+ CommonDataKinds.Email.TYPE_MOBILE -> EmailAddress.Type.MOBILE
+ else -> EmailAddress.Type.CUSTOM
+}
+
+internal fun Int.toPostalAddressType(): PostalAddress.Type = when (this) {
+ CommonDataKinds.StructuredPostal.TYPE_HOME -> PostalAddress.Type.HOME
+ CommonDataKinds.StructuredPostal.TYPE_WORK -> PostalAddress.Type.WORK
+ CommonDataKinds.StructuredPostal.TYPE_OTHER -> PostalAddress.Type.OTHER
+ else -> PostalAddress.Type.CUSTOM
+}
+
+internal fun Int.toEventType(): Event.Type = when (this) {
+ CommonDataKinds.Event.TYPE_ANNIVERSARY -> Event.Type.ANNIVERSARY
+ CommonDataKinds.Event.TYPE_OTHER -> Event.Type.OTHER
+ CommonDataKinds.Event.TYPE_BIRTHDAY -> Event.Type.BIRTHDAY
+ else -> Event.Type.CUSTOM
+}
+
+internal fun Int.toNicknameType(): Nickname.Type = when (this) {
+ CommonDataKinds.Nickname.TYPE_DEFAULT -> Nickname.Type.DEFAULT
+ CommonDataKinds.Nickname.TYPE_OTHER_NAME -> Nickname.Type.OTHER
+ CommonDataKinds.Nickname.TYPE_MAIDEN_NAME -> Nickname.Type.MAIDEN
+ CommonDataKinds.Nickname.TYPE_SHORT_NAME -> Nickname.Type.SHORT
+ CommonDataKinds.Nickname.TYPE_INITIALS -> Nickname.Type.INITIALS
+ else -> Nickname.Type.CUSTOM
+}
+
+internal fun Int.toRelationType(): Relation.Type = when (this) {
+ CommonDataKinds.Relation.TYPE_ASSISTANT -> Relation.Type.ASSISTANT
+ CommonDataKinds.Relation.TYPE_BROTHER -> Relation.Type.BROTHER
+ CommonDataKinds.Relation.TYPE_CHILD -> Relation.Type.CHILD
+ CommonDataKinds.Relation.TYPE_DOMESTIC_PARTNER -> Relation.Type.DOMESTIC_PARTNER
+ CommonDataKinds.Relation.TYPE_FATHER -> Relation.Type.FATHER
+ CommonDataKinds.Relation.TYPE_FRIEND -> Relation.Type.FRIEND
+ CommonDataKinds.Relation.TYPE_MANAGER -> Relation.Type.MANAGER
+ CommonDataKinds.Relation.TYPE_MOTHER -> Relation.Type.MOTHER
+ CommonDataKinds.Relation.TYPE_PARENT -> Relation.Type.PARENT
+ CommonDataKinds.Relation.TYPE_PARTNER -> Relation.Type.PARTNER
+ CommonDataKinds.Relation.TYPE_REFERRED_BY -> Relation.Type.REFERRED_BY
+ CommonDataKinds.Relation.TYPE_RELATIVE -> Relation.Type.RELATIVE
+ CommonDataKinds.Relation.TYPE_SISTER -> Relation.Type.SISTER
+ CommonDataKinds.Relation.TYPE_SPOUSE -> Relation.Type.SPOUSE
+ else -> Relation.Type.CUSTOM
+}
diff --git a/kontact/src/appleMain/kotlin/com/hackedcube/kontact/AppleKontactRepository.kt b/kontact/src/appleMain/kotlin/com/hackedcube/kontact/AppleKontactRepository.kt
new file mode 100644
index 0000000..3a644b3
--- /dev/null
+++ b/kontact/src/appleMain/kotlin/com/hackedcube/kontact/AppleKontactRepository.kt
@@ -0,0 +1,114 @@
+package com.hackedcube.kontact
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+import platform.Contacts.CNContact
+import platform.Contacts.CNContactFetchRequest
+import platform.Contacts.CNContactFormatter
+import platform.Contacts.CNContactFormatterStyle
+import platform.Contacts.CNContactStore
+import platform.Contacts.CNLabelHome
+import platform.Contacts.CNLabelOther
+import platform.Contacts.CNLabelPhoneNumberMobile
+import platform.Contacts.CNLabelWork
+
+/**
+ * Apple-platform implementation of [KontactRepository] using the CNContacts framework.
+ * Shared by iOS and macOS targets.
+ */
+class AppleKontactRepository : KontactRepository {
+
+ private val store = CNContactStore()
+
+ override suspend fun queryAllContacts(): List {
+ val contacts = mutableListOf()
+ val keysToFetch = contactKeysToFetch()
+ val request = CNContactFetchRequest(keysToFetch = keysToFetch)
+
+ store.enumerateContactsWithFetchRequest(request, error = null) { contact, _ ->
+ if (contact != null) {
+ contacts += contact.toKontact()
+ }
+ }
+ return contacts
+ }
+
+ override suspend fun getContact(id: String): Kontact? {
+ val keysToFetch = contactKeysToFetch()
+ return try {
+ val contact = store.unifiedContactWithIdentifier(id, keysToFetch = keysToFetch, error = null)
+ contact?.toKontact()
+ } catch (_: Exception) {
+ null
+ }
+ }
+
+ override fun observeAllContacts(): Flow> = flow {
+ emit(queryAllContacts())
+ }
+
+ @Suppress("UNCHECKED_CAST")
+ private fun contactKeysToFetch(): List = listOf(
+ platform.Contacts.CNContactIdentifierKey,
+ platform.Contacts.CNContactGivenNameKey,
+ platform.Contacts.CNContactFamilyNameKey,
+ platform.Contacts.CNContactPhoneNumbersKey,
+ platform.Contacts.CNContactEmailAddressesKey,
+ platform.Contacts.CNContactPostalAddressesKey,
+ platform.Contacts.CNContactDatesKey,
+ platform.Contacts.CNContactNicknameKey,
+ platform.Contacts.CNContactRelationsKey,
+ platform.Contacts.CNContactImageDataAvailableKey,
+ CNContactFormatter.descriptorForRequiredKeysForStyle(CNContactFormatterStyle.CNContactFormatterStyleFullName),
+ )
+
+ private fun CNContact.toKontact(): Kontact {
+ val fullName = CNContactFormatter.stringFromContact(this, CNContactFormatterStyle.CNContactFormatterStyleFullName) ?: ""
+
+ return Kontact(
+ id = identifier,
+ lookupKey = identifier,
+ displayNamePrimary = fullName,
+ hasPhoneNumber = phoneNumbers.isNotEmpty(),
+ phoneNumbers = phoneNumbers.mapNotNull { labeled ->
+ @Suppress("UNCHECKED_CAST")
+ val lv = labeled as? platform.Contacts.CNLabeledValue
+ lv?.let {
+ PhoneNumber(
+ id = identifier,
+ number = (it.value as platform.Contacts.CNPhoneNumber).stringValue,
+ type = it.label.toPhoneType(),
+ label = it.label,
+ )
+ }
+ },
+ emailAddresses = emailAddresses.mapNotNull { labeled ->
+ @Suppress("UNCHECKED_CAST")
+ val lv = labeled as? platform.Contacts.CNLabeledValue<*>
+ lv?.let {
+ EmailAddress(
+ id = identifier,
+ address = it.value.toString(),
+ type = it.label.toEmailType(),
+ label = it.label,
+ )
+ }
+ },
+ )
+ }
+
+ private fun String?.toPhoneType(): PhoneNumber.Type = when (this) {
+ CNLabelHome -> PhoneNumber.Type.HOME
+ CNLabelWork -> PhoneNumber.Type.WORK
+ CNLabelPhoneNumberMobile -> PhoneNumber.Type.MOBILE
+ CNLabelOther -> PhoneNumber.Type.OTHER
+ else -> PhoneNumber.Type.CUSTOM
+ }
+
+ private fun String?.toEmailType(): EmailAddress.Type = when (this) {
+ CNLabelHome -> EmailAddress.Type.HOME
+ CNLabelWork -> EmailAddress.Type.WORK
+ CNLabelOther -> EmailAddress.Type.OTHER
+ else -> EmailAddress.Type.CUSTOM
+ }
+}
diff --git a/kontact/src/commonMain/kotlin/com/hackedcube/kontact/KontactRepository.kt b/kontact/src/commonMain/kotlin/com/hackedcube/kontact/KontactRepository.kt
new file mode 100644
index 0000000..1410237
--- /dev/null
+++ b/kontact/src/commonMain/kotlin/com/hackedcube/kontact/KontactRepository.kt
@@ -0,0 +1,12 @@
+package com.hackedcube.kontact
+
+import kotlinx.coroutines.flow.Flow
+
+/**
+ * Platform-agnostic interface for querying device contacts.
+ */
+interface KontactRepository {
+ suspend fun queryAllContacts(): List
+ suspend fun getContact(id: String): Kontact?
+ fun observeAllContacts(): Flow>
+}
diff --git a/kontact/src/commonMain/kotlin/com/hackedcube/kontact/Models.kt b/kontact/src/commonMain/kotlin/com/hackedcube/kontact/Models.kt
new file mode 100644
index 0000000..4f7a132
--- /dev/null
+++ b/kontact/src/commonMain/kotlin/com/hackedcube/kontact/Models.kt
@@ -0,0 +1,96 @@
+package com.hackedcube.kontact
+
+data class Kontact(
+ val id: String,
+ val lookupKey: String,
+ val displayNamePrimary: String,
+ val photoId: Long? = null,
+ val photoUri: String? = null,
+ val thumbnailPhotoUri: String? = null,
+ val isVisible: Boolean = true,
+ val hasPhoneNumber: Boolean = false,
+ val timesContacted: Int = 0,
+ val lastTimeContacted: Long = 0L,
+ val isStarred: Boolean = false,
+ val customRingtone: String? = null,
+ val sendToVoicemail: Boolean = false,
+ val phoneNumbers: List = emptyList(),
+ val emailAddresses: List = emptyList(),
+ val relations: List = emptyList(),
+ val postalAddresses: List = emptyList(),
+ val nicknames: List = emptyList(),
+ val events: List = emptyList(),
+)
+
+data class PhoneNumber(
+ val id: String,
+ val number: String,
+ val type: Type,
+ val label: String? = null,
+) {
+ enum class Type {
+ CUSTOM, HOME, MOBILE, WORK, FAX_WORK, FAX_HOME, PAGER, OTHER,
+ CALLBACK, CAR, COMPANY_MAIN, ISDN, MAIN, OTHER_FAX, RADIO,
+ TELEX, TTY_TDD, WORK_MOBILE, WORK_PAGER, ASSISTANT, MMS,
+ }
+}
+
+data class EmailAddress(
+ val id: String,
+ val address: String,
+ val type: Type,
+ val label: String? = null,
+) {
+ enum class Type {
+ CUSTOM, HOME, WORK, OTHER, MOBILE,
+ }
+}
+
+data class PostalAddress(
+ val formattedAddress: String,
+ val type: Type,
+ val label: String? = null,
+ val street: String? = null,
+ val pobox: String? = null,
+ val neighborhood: String? = null,
+ val city: String? = null,
+ val region: String? = null,
+ val postcode: String? = null,
+ val country: String? = null,
+) {
+ enum class Type {
+ CUSTOM, HOME, WORK, OTHER,
+ }
+}
+
+data class Event(
+ val startDate: String? = null,
+ val type: Type? = null,
+ val label: String? = null,
+) {
+ enum class Type {
+ CUSTOM, ANNIVERSARY, OTHER, BIRTHDAY,
+ }
+}
+
+data class Nickname(
+ val name: String? = null,
+ val type: Type? = null,
+ val label: String? = null,
+) {
+ enum class Type {
+ CUSTOM, DEFAULT, OTHER, MAIDEN, SHORT, INITIALS,
+ }
+}
+
+data class Relation(
+ val name: String,
+ val type: Type,
+ val label: String? = null,
+) {
+ enum class Type {
+ CUSTOM, ASSISTANT, BROTHER, CHILD, DOMESTIC_PARTNER, FATHER,
+ FRIEND, MANAGER, MOTHER, PARENT, PARTNER, REFERRED_BY,
+ RELATIVE, SISTER, SPOUSE,
+ }
+}
diff --git a/kontact/src/desktopMain/kotlin/com/hackedcube/kontact/DesktopKontactRepository.kt b/kontact/src/desktopMain/kotlin/com/hackedcube/kontact/DesktopKontactRepository.kt
new file mode 100644
index 0000000..34012df
--- /dev/null
+++ b/kontact/src/desktopMain/kotlin/com/hackedcube/kontact/DesktopKontactRepository.kt
@@ -0,0 +1,22 @@
+package com.hackedcube.kontact
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+/**
+ * Desktop (JVM) implementation of [KontactRepository].
+ *
+ * Desktop platforms do not have a standardised contacts API.
+ * This stub returns empty results; consumers can extend it to
+ * integrate with platform-specific stores (e.g. macOS Contacts via JNI).
+ */
+class DesktopKontactRepository : KontactRepository {
+
+ override suspend fun queryAllContacts(): List = emptyList()
+
+ override suspend fun getContact(id: String): Kontact? = null
+
+ override fun observeAllContacts(): Flow> = flow {
+ emit(emptyList())
+ }
+}
diff --git a/kontact/src/main/java/com/hackedcube/kontact/EmailAddress.java b/kontact/src/main/java/com/hackedcube/kontact/EmailAddress.java
deleted file mode 100644
index 8fe9ada..0000000
--- a/kontact/src/main/java/com/hackedcube/kontact/EmailAddress.java
+++ /dev/null
@@ -1,50 +0,0 @@
-package com.hackedcube.kontact;
-
-import android.database.Cursor;
-import android.provider.ContactsContract;
-import android.support.annotation.Nullable;
-
-import com.gabrielittner.auto.value.cursor.ColumnAdapter;
-import com.gabrielittner.auto.value.cursor.ColumnName;
-import com.google.auto.value.AutoValue;
-import com.hackedcube.kontact.columnadapters.EmailTypeIntAdapter;
-
-@AutoValue
-public abstract class EmailAddress {
-
- @ColumnName(ContactsContract.CommonDataKinds.Email._ID)
- public abstract String id();
-
- @ColumnName(ContactsContract.CommonDataKinds.Email.ADDRESS)
- public abstract String number();
-
- @ColumnAdapter(EmailTypeIntAdapter.class)
- @ColumnName(ContactsContract.CommonDataKinds.Email.TYPE)
- public abstract Type type();
-
- @Nullable
- @ColumnName(ContactsContract.CommonDataKinds.Email.LABEL)
- public abstract String label();
-
- public static EmailAddress create(Cursor cursor) {
- return AutoValue_EmailAddress.createFromCursor(cursor);
- }
-
- public enum Type {
- CUSTOM(ContactsContract.CommonDataKinds.Email.TYPE_CUSTOM),
- HOME(ContactsContract.CommonDataKinds.Email.TYPE_HOME),
- WORK(ContactsContract.CommonDataKinds.Email.TYPE_WORK),
- OTHER(ContactsContract.CommonDataKinds.Email.TYPE_OTHER),
- MOBILE(ContactsContract.CommonDataKinds.Email.TYPE_MOBILE);
-
- int typeMap;
-
- Type(int columnName) {
- this.typeMap = columnName;
- }
-
- public int getTypeMap() {
- return typeMap;
- }
- }
-}
diff --git a/kontact/src/main/java/com/hackedcube/kontact/Event.java b/kontact/src/main/java/com/hackedcube/kontact/Event.java
deleted file mode 100644
index b780563..0000000
--- a/kontact/src/main/java/com/hackedcube/kontact/Event.java
+++ /dev/null
@@ -1,49 +0,0 @@
-package com.hackedcube.kontact;
-
-import android.database.Cursor;
-import android.provider.ContactsContract;
-import android.support.annotation.Nullable;
-
-import com.gabrielittner.auto.value.cursor.ColumnAdapter;
-import com.gabrielittner.auto.value.cursor.ColumnName;
-import com.google.auto.value.AutoValue;
-import com.hackedcube.kontact.columnadapters.EventTypeIntAdapter;
-
-@AutoValue
-public abstract class Event {
-
- @Nullable
- @ColumnName(ContactsContract.CommonDataKinds.Event.START_DATE)
- public abstract String startDate();
-
- @Nullable
- @ColumnAdapter(EventTypeIntAdapter.class)
- @ColumnName(ContactsContract.CommonDataKinds.Event.TYPE)
- public abstract Event.Type type();
-
- @Nullable
- @ColumnName(ContactsContract.CommonDataKinds.Event.LABEL)
- public abstract String label();
-
- public static Event create(Cursor cursor) {
- return AutoValue_Event.createFromCursor(cursor);
- }
-
- public enum Type {
- CUSTOM(ContactsContract.CommonDataKinds.Event.TYPE_CUSTOM),
- ANNIVERSARY(ContactsContract.CommonDataKinds.Event.TYPE_ANNIVERSARY),
- OTHER(ContactsContract.CommonDataKinds.Event.TYPE_OTHER),
- BIRTHDAY(ContactsContract.CommonDataKinds.Event.TYPE_BIRTHDAY);
-
- int typeMap;
-
- Type(int columnName) {
- this.typeMap = columnName;
- }
-
- public int getTypeMap() {
- return typeMap;
- }
- }
-
-}
diff --git a/kontact/src/main/java/com/hackedcube/kontact/Kontact.java b/kontact/src/main/java/com/hackedcube/kontact/Kontact.java
deleted file mode 100644
index f89a81c..0000000
--- a/kontact/src/main/java/com/hackedcube/kontact/Kontact.java
+++ /dev/null
@@ -1,138 +0,0 @@
-package com.hackedcube.kontact;
-
-import android.database.Cursor;
-import android.provider.ContactsContract;
-import android.support.annotation.Nullable;
-
-import com.gabrielittner.auto.value.cursor.ColumnAdapter;
-import com.gabrielittner.auto.value.cursor.ColumnName;
-import com.google.auto.value.AutoValue;
-import com.hackedcube.kontact.columnadapters.BooleanIntAdapter;
-
-import java.util.List;
-
-@AutoValue
-public abstract class Kontact {
-
- @ColumnName(ContactsContract.Contacts._ID)
- public abstract String id();
-
- @ColumnName(ContactsContract.Contacts.LOOKUP_KEY)
- public abstract String lookupKey();
-
- @ColumnName(ContactsContract.Contacts.DISPLAY_NAME_PRIMARY)
- public abstract String displayNamePrimary();
-
- @Nullable
- @ColumnName(ContactsContract.Contacts.PHOTO_ID)
- public abstract Long photoId();
-
- @Nullable
- @ColumnName(ContactsContract.Contacts.PHOTO_URI)
- public abstract Long photoUri();
-
- @Nullable
- @ColumnName(ContactsContract.Contacts.PHOTO_THUMBNAIL_URI)
- public abstract Long thumbnailPhotoUri();
-
- @ColumnAdapter(BooleanIntAdapter.class)
- @ColumnName(ContactsContract.Contacts.IN_VISIBLE_GROUP)
- public abstract Boolean isVisible();
-
- @ColumnAdapter(BooleanIntAdapter.class)
- @ColumnName(ContactsContract.Contacts.HAS_PHONE_NUMBER)
- public abstract Boolean hasPhoneNumber();
-
- @ColumnName(ContactsContract.Contacts.TIMES_CONTACTED)
- public abstract Integer timesContacted();
-
- @ColumnName(ContactsContract.Contacts.LAST_TIME_CONTACTED)
- public abstract Long lastTimeContacted();
-
- @ColumnAdapter(BooleanIntAdapter.class)
- @ColumnName(ContactsContract.Contacts.STARRED)
- public abstract Boolean isStarred();
-
- @Nullable
- @ColumnName(ContactsContract.Contacts.CUSTOM_RINGTONE)
- public abstract String customRingtone();
-
- @ColumnAdapter(BooleanIntAdapter.class)
- @ColumnName(ContactsContract.Contacts.SEND_TO_VOICEMAIL)
- public abstract Boolean sendToVoicemail();
-
- @Nullable
- public abstract List phoneNumbers();
-
- @Nullable
- public abstract List emailAddresses();
-
- @Nullable
- public abstract List relations();
-
- @Nullable
- public abstract List postalAddresses();
-
- @Nullable
- public abstract List nicknames();
-
- @Nullable
- public abstract List events();
-
- public static Kontact create(Cursor cursor) {
- return AutoValue_Kontact.createFromCursor(cursor);
- }
-
- public static Builder builder() {
- return new AutoValue_Kontact.Builder();
- }
-
- public abstract Builder toBuilder();
-
- public abstract Kontact withPhoneNumbers(List phoneNumbers);
-
- public abstract Kontact withEmailAddresses(List emailAddresses);
-
- @AutoValue.Builder
- public abstract static class Builder {
- public abstract Builder id(String id);
-
- public abstract Builder lookupKey(String lookupKey);
-
- public abstract Builder displayNamePrimary(String displayNamePrimary);
-
- public abstract Builder photoId(Long photoId);
-
- public abstract Builder photoUri(Long photoUri);
-
- public abstract Builder thumbnailPhotoUri(Long thumbnailPhotoUri);
-
- public abstract Builder isVisible(Boolean isVisible);
-
- public abstract Builder hasPhoneNumber(Boolean hasPhoneNumber);
-
- public abstract Builder timesContacted(Integer timesContacted);
-
- public abstract Builder lastTimeContacted(Long lastTimeContacted);
-
- public abstract Builder isStarred(Boolean isStarred);
-
- public abstract Builder customRingtone(String customRingtone);
-
- public abstract Builder sendToVoicemail(Boolean sendToVoicemail);
-
- public abstract Builder phoneNumbers(List phoneNumbers);
-
- public abstract Builder emailAddresses(List emailAddresses);
-
- public abstract Builder relations(List relations);
-
- public abstract Builder postalAddresses(List postalAddresses);
-
- public abstract Builder nicknames(List nicknames);
-
- public abstract Builder events(List events);
-
- public abstract Kontact build();
- }
-}
diff --git a/kontact/src/main/java/com/hackedcube/kontact/Nickname.java b/kontact/src/main/java/com/hackedcube/kontact/Nickname.java
deleted file mode 100644
index be15699..0000000
--- a/kontact/src/main/java/com/hackedcube/kontact/Nickname.java
+++ /dev/null
@@ -1,52 +0,0 @@
-package com.hackedcube.kontact;
-
-import android.database.Cursor;
-import android.provider.ContactsContract;
-import android.support.annotation.Nullable;
-
-import com.gabrielittner.auto.value.cursor.ColumnAdapter;
-import com.gabrielittner.auto.value.cursor.ColumnName;
-import com.google.auto.value.AutoValue;
-import com.hackedcube.kontact.columnadapters.NicknameTypeIntAdapter;
-import com.hackedcube.kontact.columnadapters.RelationTypeIntAdapter;
-
-@AutoValue
-public abstract class Nickname {
-
- @Nullable
- @ColumnName(ContactsContract.CommonDataKinds.Nickname.NAME)
- public abstract String name();
-
- @Nullable
- @ColumnAdapter(NicknameTypeIntAdapter.class)
- @ColumnName(ContactsContract.CommonDataKinds.Nickname.TYPE)
- public abstract Nickname.Type type();
-
- @Nullable
- @ColumnName(ContactsContract.CommonDataKinds.Nickname.LABEL)
- public abstract String label();
-
- public static Nickname create(Cursor cursor) {
- return AutoValue_Nickname.createFromCursor(cursor);
- }
-
- public enum Type {
- CUSTOM(ContactsContract.CommonDataKinds.Nickname.TYPE_CUSTOM),
- DEFAULT(ContactsContract.CommonDataKinds.Nickname.TYPE_DEFAULT),
- OTHER(ContactsContract.CommonDataKinds.Nickname.TYPE_OTHER_NAME),
- MAIDEN(ContactsContract.CommonDataKinds.Nickname.TYPE_MAIDEN_NAME),
- SHORT(ContactsContract.CommonDataKinds.Nickname.TYPE_SHORT_NAME),
- INITIALS(ContactsContract.CommonDataKinds.Nickname.TYPE_INITIALS);
-
- int typeMap;
-
- Type(int columnName) {
- this.typeMap = columnName;
- }
-
- public int getTypeMap() {
- return typeMap;
- }
- }
-
-}
diff --git a/kontact/src/main/java/com/hackedcube/kontact/PhoneNumber.java b/kontact/src/main/java/com/hackedcube/kontact/PhoneNumber.java
deleted file mode 100644
index 924687e..0000000
--- a/kontact/src/main/java/com/hackedcube/kontact/PhoneNumber.java
+++ /dev/null
@@ -1,69 +0,0 @@
-package com.hackedcube.kontact;
-
-
-import android.database.Cursor;
-import android.provider.ContactsContract;
-import android.support.annotation.Nullable;
-
-import com.gabrielittner.auto.value.cursor.ColumnAdapter;
-import com.gabrielittner.auto.value.cursor.ColumnName;
-import com.google.auto.value.AutoValue;
-import com.hackedcube.kontact.columnadapters.PhoneTypeIntAdapter;
-
-@AutoValue
-public abstract class PhoneNumber {
-
-
- @ColumnName(ContactsContract.CommonDataKinds.Phone._ID)
- public abstract String id();
-
- @ColumnName(ContactsContract.CommonDataKinds.Phone.NUMBER)
- public abstract String number();
-
- @ColumnAdapter(PhoneTypeIntAdapter.class)
- @ColumnName(ContactsContract.CommonDataKinds.Phone.TYPE)
- public abstract Type type();
-
- @Nullable
- @ColumnName(ContactsContract.CommonDataKinds.Phone.LABEL)
- public abstract String label();
-
- public static PhoneNumber create(Cursor cursor) {
- return AutoValue_PhoneNumber.createFromCursor(cursor);
- }
-
- public enum Type {
- CUSTOM(ContactsContract.CommonDataKinds.Phone.TYPE_CUSTOM),
- HOME(ContactsContract.CommonDataKinds.Phone.TYPE_HOME),
- MOBILE(ContactsContract.CommonDataKinds.Phone.TYPE_MOBILE),
- WORK(ContactsContract.CommonDataKinds.Phone.TYPE_WORK),
- FAX_WORK(ContactsContract.CommonDataKinds.Phone.TYPE_FAX_WORK),
- FAX_HOME(ContactsContract.CommonDataKinds.Phone.TYPE_FAX_HOME),
- PAGER(ContactsContract.CommonDataKinds.Phone.TYPE_PAGER),
- OTHER(ContactsContract.CommonDataKinds.Phone.TYPE_OTHER),
- CALLBACK(ContactsContract.CommonDataKinds.Phone.TYPE_CALLBACK),
- CAR(ContactsContract.CommonDataKinds.Phone.TYPE_CAR),
- COMPANY_MAIN(ContactsContract.CommonDataKinds.Phone.TYPE_COMPANY_MAIN),
- ISDN(ContactsContract.CommonDataKinds.Phone.TYPE_ISDN),
- MAIN(ContactsContract.CommonDataKinds.Phone.TYPE_MAIN),
- OTHER_FAX(ContactsContract.CommonDataKinds.Phone.TYPE_OTHER_FAX),
- RADIO(ContactsContract.CommonDataKinds.Phone.TYPE_RADIO),
- TELEX(ContactsContract.CommonDataKinds.Phone.TYPE_TELEX),
- TTY_TDD(ContactsContract.CommonDataKinds.Phone.TYPE_TTY_TDD),
- WORK_MOBILE(ContactsContract.CommonDataKinds.Phone.TYPE_WORK_MOBILE),
- WORK_PAGER(ContactsContract.CommonDataKinds.Phone.TYPE_WORK_PAGER),
- ASSISTANT(ContactsContract.CommonDataKinds.Phone.TYPE_ASSISTANT),
- MMS(ContactsContract.CommonDataKinds.Phone.TYPE_MMS);
-
- int typeMap;
-
- Type(int columnName) {
- this.typeMap = columnName;
- }
-
- public int getTypeMap() {
- return typeMap;
- }
- }
-
-}
diff --git a/kontact/src/main/java/com/hackedcube/kontact/PostalAddress.java b/kontact/src/main/java/com/hackedcube/kontact/PostalAddress.java
deleted file mode 100644
index b01ba57..0000000
--- a/kontact/src/main/java/com/hackedcube/kontact/PostalAddress.java
+++ /dev/null
@@ -1,76 +0,0 @@
-package com.hackedcube.kontact;
-
-import android.database.Cursor;
-import android.provider.ContactsContract;
-import android.support.annotation.Nullable;
-
-import com.gabrielittner.auto.value.cursor.ColumnAdapter;
-import com.gabrielittner.auto.value.cursor.ColumnName;
-import com.google.auto.value.AutoValue;
-import com.hackedcube.kontact.columnadapters.PostalAddressTypeIntAdapter;
-
-@AutoValue
-public abstract class PostalAddress {
-
-
- @ColumnName(ContactsContract.CommonDataKinds.StructuredPostal.FORMATTED_ADDRESS)
- public abstract String formattedAddress();
-
- @ColumnAdapter(PostalAddressTypeIntAdapter.class)
- @ColumnName(ContactsContract.CommonDataKinds.StructuredPostal.TYPE)
- public abstract PostalAddress.Type type();
-
- @Nullable
- @ColumnName(ContactsContract.CommonDataKinds.StructuredPostal.LABEL)
- public abstract String label();
-
- @Nullable
- @ColumnName(ContactsContract.CommonDataKinds.StructuredPostal.STREET)
- public abstract String street();
-
- @Nullable
- @ColumnName(ContactsContract.CommonDataKinds.StructuredPostal.POBOX)
- public abstract String pobox();
-
- @Nullable
- @ColumnName(ContactsContract.CommonDataKinds.StructuredPostal.NEIGHBORHOOD)
- public abstract String neighborhood();
-
- @Nullable
- @ColumnName(ContactsContract.CommonDataKinds.StructuredPostal.CITY)
- public abstract String city();
-
- @Nullable
- @ColumnName(ContactsContract.CommonDataKinds.StructuredPostal.REGION)
- public abstract String region();
-
- @Nullable
- @ColumnName(ContactsContract.CommonDataKinds.StructuredPostal.POSTCODE)
- public abstract String postcode();
-
- @Nullable
- @ColumnName(ContactsContract.CommonDataKinds.StructuredPostal.COUNTRY)
- public abstract String country();
-
- public static PostalAddress create(Cursor cursor) {
- return AutoValue_PostalAddress.createFromCursor(cursor);
- }
-
- public enum Type {
- CUSTOM(ContactsContract.CommonDataKinds.StructuredPostal.TYPE_CUSTOM),
- HOME(ContactsContract.CommonDataKinds.StructuredPostal.TYPE_HOME),
- WORK(ContactsContract.CommonDataKinds.StructuredPostal.TYPE_WORK),
- OTHER(ContactsContract.CommonDataKinds.StructuredPostal.TYPE_OTHER);
-
- int typeMap;
-
- Type(int columnName) {
- this.typeMap = columnName;
- }
-
- public int getTypeMap() {
- return typeMap;
- }
- }
-
-}
diff --git a/kontact/src/main/java/com/hackedcube/kontact/Relation.java b/kontact/src/main/java/com/hackedcube/kontact/Relation.java
deleted file mode 100644
index 239e61e..0000000
--- a/kontact/src/main/java/com/hackedcube/kontact/Relation.java
+++ /dev/null
@@ -1,60 +0,0 @@
-package com.hackedcube.kontact;
-
-import android.database.Cursor;
-import android.provider.ContactsContract;
-import android.support.annotation.Nullable;
-
-import com.gabrielittner.auto.value.cursor.ColumnAdapter;
-import com.gabrielittner.auto.value.cursor.ColumnName;
-import com.google.auto.value.AutoValue;
-import com.hackedcube.kontact.columnadapters.RelationTypeIntAdapter;
-
-@AutoValue
-public abstract class Relation {
-
- @ColumnName(ContactsContract.CommonDataKinds.Relation.NAME)
- public abstract String name();
-
- @ColumnAdapter(RelationTypeIntAdapter.class)
- @ColumnName(ContactsContract.CommonDataKinds.Relation.TYPE)
- public abstract Relation.Type type();
-
- @Nullable
- @ColumnName(ContactsContract.CommonDataKinds.Relation.LABEL)
- public abstract String label();
-
- public static Relation create(Cursor cursor) {
- return AutoValue_Relation.createFromCursor(cursor);
- }
-
-
- public enum Type {
- CUSTOM(ContactsContract.CommonDataKinds.Relation.TYPE_CUSTOM),
- ASSISTANT(ContactsContract.CommonDataKinds.Relation.TYPE_ASSISTANT),
- BROTHER(ContactsContract.CommonDataKinds.Relation.TYPE_BROTHER),
- CHILD(ContactsContract.CommonDataKinds.Relation.TYPE_CHILD),
- DOMESTIC_PARTNER(ContactsContract.CommonDataKinds.Relation.TYPE_DOMESTIC_PARTNER),
- FATHER(ContactsContract.CommonDataKinds.Relation.TYPE_FATHER),
- FRIEND(ContactsContract.CommonDataKinds.Relation.TYPE_FRIEND),
- MANAGER(ContactsContract.CommonDataKinds.Relation.TYPE_MANAGER),
- MOTHER(ContactsContract.CommonDataKinds.Relation.TYPE_MOTHER),
- PARENT(ContactsContract.CommonDataKinds.Relation.TYPE_PARENT),
- PARTNER(ContactsContract.CommonDataKinds.Relation.TYPE_PARTNER),
- REFERRED_BY(ContactsContract.CommonDataKinds.Relation.TYPE_REFERRED_BY),
- RELATIVE(ContactsContract.CommonDataKinds.Relation.TYPE_RELATIVE),
- SISTER(ContactsContract.CommonDataKinds.Relation.TYPE_SISTER),
- SPOUSE(ContactsContract.CommonDataKinds.Relation.TYPE_SPOUSE);
-
- int typeMap;
-
- Type(int columnName) {
- this.typeMap = columnName;
- }
-
- public int getTypeMap() {
- return typeMap;
- }
- }
-
-
-}
diff --git a/kontact/src/main/kotlin/com/hackedcube/kontact/ContactUtils.kt b/kontact/src/main/kotlin/com/hackedcube/kontact/ContactUtils.kt
deleted file mode 100644
index 83fe17a..0000000
--- a/kontact/src/main/kotlin/com/hackedcube/kontact/ContactUtils.kt
+++ /dev/null
@@ -1,94 +0,0 @@
-@file:JvmName("ContactUtils")
-
-package com.hackedcube.kontact
-
-import android.content.Context
-import android.database.Cursor
-import android.net.Uri
-import android.provider.ContactsContract
-
-
-fun Context.queryAllContacts(): List {
- contentResolver.query(ContactsContract.Contacts.CONTENT_URI, null, null, null, null).use {
- return generateSequence { if (it.moveToNext()) it else null }
- .map { kontactFromCursor(this, it) }
- .toList()
- }
-}
-
-fun Context.getContactFromId(uri: Uri): Kontact? {
- contentResolver.query(uri, null, null, null, null).use { cursorContact ->
- cursorContact.moveToFirst()
- return kontactFromCursor(this, cursorContact)
- }
-}
-
-private fun kontactFromCursor(context: Context, cursor: Cursor): Kontact {
- var kontact = Kontact.create(cursor)
-
- // Fetch Phone Numbers
- if (kontact.hasPhoneNumber()) {
- context.contentResolver.query(ContactsContract.CommonDataKinds.Phone.CONTENT_URI, null, ContactsContract.CommonDataKinds.Phone.CONTACT_ID + " = ?", arrayOf(kontact.id()), null).use { phoneCursor ->
- val phoneNumbers = phoneCursor.toSequence()
- .map { PhoneNumber.create(it) }
- .toList()
-
- kontact = kontact.withPhoneNumbers(phoneNumbers)
- }
- }
-
- // Fetch Email addresses
- context.contentResolver.query(ContactsContract.CommonDataKinds.Email.CONTENT_URI, null, ContactsContract.CommonDataKinds.Email.CONTACT_ID + " = ?", arrayOf(kontact.id()), null).use { emailCursor ->
- val emailAddresses = emailCursor.toSequence()
- .map { EmailAddress.create(it) }
- .toList()
-
- kontact = kontact.withEmailAddresses(emailAddresses)
- }
-
- val select = arrayOf(ContactsContract.Data.MIMETYPE, "data1", "data2", "data3", "data4",
- "data5", "data6", "data7", "data8", "data9", "data10", "data11", "data12", "data13",
- "data14", "data15")
-
- // Fetch additional info
- val where = "${ContactsContract.Data.CONTACT_ID} = ? AND ${ContactsContract.Data.MIMETYPE} IN (?, ?, ?, ?)"
-
- val whereParams = arrayOf(
- kontact.id(),
- ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE,
- ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE,
- ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE,
- ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE
- )
-
- context.contentResolver.query(ContactsContract.Data.CONTENT_URI, select, where, whereParams, null).use { dataCursor ->
-
- val data = dataCursor.toSequence()
- .map {
- val columnType = it.getString(it.getColumnIndex(ContactsContract.Data.MIMETYPE))
-
- when(columnType) {
- ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE -> columnType to Event.create(it)
- ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE -> columnType to Nickname.create(it)
- ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE -> columnType to Relation.create(it)
- ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE -> columnType to PostalAddress.create(it)
- else -> columnType to null
- }
- }
- .groupBy({it.first}, {it.second})
-
-
-
-
- kontact = kontact.toBuilder()
- .events(data[ContactsContract.CommonDataKinds.Event.CONTENT_ITEM_TYPE] as MutableList?)
- .nicknames(data[ContactsContract.CommonDataKinds.Nickname.CONTENT_ITEM_TYPE] as MutableList?)
- .postalAddresses(data[ContactsContract.CommonDataKinds.StructuredPostal.CONTENT_ITEM_TYPE] as MutableList?)
- .relations(data[ContactsContract.CommonDataKinds.Relation.CONTENT_ITEM_TYPE] as MutableList?)
- .build()
- }
-
-
-
- return kontact
-}
\ No newline at end of file
diff --git a/kontact/src/main/kotlin/com/hackedcube/kontact/Extensions.kt b/kontact/src/main/kotlin/com/hackedcube/kontact/Extensions.kt
deleted file mode 100644
index 9060d70..0000000
--- a/kontact/src/main/kotlin/com/hackedcube/kontact/Extensions.kt
+++ /dev/null
@@ -1,20 +0,0 @@
-package com.hackedcube.kontact
-
-import android.database.Cursor
-
-fun Boolean.toFlag(): Int {
- val flag = when (this) {
- true -> 1
- false -> 0
- }
-
- return flag
-}
-
-fun Cursor.toSequence(): Sequence {
- return if (this.moveToNext()) {
- generateSequence(this, { if (this.moveToNext()) this else null })
- } else {
- emptySequence()
- }
-}
diff --git a/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/BooleanIntAdapter.kt b/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/BooleanIntAdapter.kt
deleted file mode 100644
index a44dd92..0000000
--- a/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/BooleanIntAdapter.kt
+++ /dev/null
@@ -1,14 +0,0 @@
-package com.hackedcube.kontact.columnadapters
-
-import android.content.ContentValues
-import android.database.Cursor
-import com.gabrielittner.auto.value.cursor.ColumnTypeAdapter
-import com.hackedcube.kontact.toFlag
-
-class BooleanIntAdapter : ColumnTypeAdapter {
- override fun fromCursor(cursor: Cursor, columnName: String) = cursor.getInt(cursor.getColumnIndex(columnName)) == 1
-
- override fun toContentValues(contentValues: ContentValues, columnName: String, value: Boolean) {
- contentValues.put(columnName, value.toFlag())
- }
-}
\ No newline at end of file
diff --git a/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/EmailTypeIntAdapter.kt b/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/EmailTypeIntAdapter.kt
deleted file mode 100644
index 1b9e2e0..0000000
--- a/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/EmailTypeIntAdapter.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.hackedcube.kontact.columnadapters
-
-import android.content.ContentValues
-import android.database.Cursor
-import com.gabrielittner.auto.value.cursor.ColumnTypeAdapter
-import com.hackedcube.kontact.EmailAddress
-import com.hackedcube.kontact.PhoneNumber
-
-class EmailTypeIntAdapter : ColumnTypeAdapter {
-
- override fun fromCursor(cursor: Cursor, columnName: String): EmailAddress.Type {
- val typeInt = cursor.getInt(cursor.getColumnIndex(columnName))
- return EmailAddress.Type.values().first { it.typeMap == typeInt }
- }
-
- override fun toContentValues(contentValues: ContentValues, columnName: String, value: EmailAddress.Type) {
- contentValues.put(columnName, value.typeMap)
- }
-}
\ No newline at end of file
diff --git a/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/EventTypeIntAdapter.kt b/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/EventTypeIntAdapter.kt
deleted file mode 100644
index 9467256..0000000
--- a/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/EventTypeIntAdapter.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.hackedcube.kontact.columnadapters
-
-import android.content.ContentValues
-import android.database.Cursor
-import com.gabrielittner.auto.value.cursor.ColumnTypeAdapter
-import com.hackedcube.kontact.Event
-
-
-class EventTypeIntAdapter : ColumnTypeAdapter {
-
- override fun fromCursor(cursor: Cursor, columnName: String): Event.Type {
- val typeInt = cursor.getInt(cursor.getColumnIndex(columnName))
- return Event.Type.values().first { it.typeMap == typeInt }
- }
-
- override fun toContentValues(contentValues: ContentValues, columnName: String, value: Event.Type) {
- contentValues.put(columnName, value.typeMap)
- }
-}
\ No newline at end of file
diff --git a/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/NicknameTypeIntAdapter.kt b/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/NicknameTypeIntAdapter.kt
deleted file mode 100644
index 9b42a6f..0000000
--- a/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/NicknameTypeIntAdapter.kt
+++ /dev/null
@@ -1,19 +0,0 @@
-package com.hackedcube.kontact.columnadapters
-
-import android.content.ContentValues
-import android.database.Cursor
-import com.gabrielittner.auto.value.cursor.ColumnTypeAdapter
-import com.hackedcube.kontact.Nickname
-
-
-class NicknameTypeIntAdapter : ColumnTypeAdapter {
-
- override fun fromCursor(cursor: Cursor, columnName: String): Nickname.Type {
- val typeInt = cursor.getInt(cursor.getColumnIndex(columnName))
- return Nickname.Type.values().first { it.typeMap == typeInt }
- }
-
- override fun toContentValues(contentValues: ContentValues, columnName: String, value: Nickname.Type) {
- contentValues.put(columnName, value.typeMap)
- }
-}
\ No newline at end of file
diff --git a/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/PhoneTypeIntAdapter.kt b/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/PhoneTypeIntAdapter.kt
deleted file mode 100644
index 2e07fd2..0000000
--- a/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/PhoneTypeIntAdapter.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.hackedcube.kontact.columnadapters
-
-import android.content.ContentValues
-import android.database.Cursor
-import com.gabrielittner.auto.value.cursor.ColumnTypeAdapter
-import com.hackedcube.kontact.PhoneNumber
-
-class PhoneTypeIntAdapter : ColumnTypeAdapter {
-
- override fun fromCursor(cursor: Cursor, columnName: String): PhoneNumber.Type {
- val typeInt = cursor.getInt(cursor.getColumnIndex(columnName))
- return PhoneNumber.Type.values().first { it.typeMap == typeInt }
- }
-
- override fun toContentValues(contentValues: ContentValues, columnName: String, value: PhoneNumber.Type) {
- contentValues.put(columnName, value.typeMap)
- }
-}
\ No newline at end of file
diff --git a/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/PostalAddressTypeIntAdapter.kt b/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/PostalAddressTypeIntAdapter.kt
deleted file mode 100644
index eeeaa64..0000000
--- a/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/PostalAddressTypeIntAdapter.kt
+++ /dev/null
@@ -1,18 +0,0 @@
-package com.hackedcube.kontact.columnadapters
-
-import android.content.ContentValues
-import android.database.Cursor
-import com.gabrielittner.auto.value.cursor.ColumnTypeAdapter
-import com.hackedcube.kontact.PostalAddress
-
-class PostalAddressTypeIntAdapter : ColumnTypeAdapter {
-
- override fun fromCursor(cursor: Cursor, columnName: String): PostalAddress.Type {
- val typeInt = cursor.getInt(cursor.getColumnIndex(columnName))
- return PostalAddress.Type.values().first { it.typeMap == typeInt }
- }
-
- override fun toContentValues(contentValues: ContentValues, columnName: String, value: PostalAddress.Type) {
- contentValues.put(columnName, value.typeMap)
- }
-}
diff --git a/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/RelationTypeIntAdapter.kt b/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/RelationTypeIntAdapter.kt
deleted file mode 100644
index aa26a30..0000000
--- a/kontact/src/main/kotlin/com/hackedcube/kontact/columnadapters/RelationTypeIntAdapter.kt
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.hackedcube.kontact.columnadapters
-
-import android.content.ContentValues
-import android.database.Cursor
-import com.gabrielittner.auto.value.cursor.ColumnTypeAdapter
-import com.hackedcube.kontact.Relation
-
-class RelationTypeIntAdapter : ColumnTypeAdapter {
- override fun fromCursor(cursor: Cursor, columnName: String): Relation.Type {
- val typeInt = cursor.getInt(cursor.getColumnIndex(columnName))
- return Relation.Type.values().first { it.typeMap == typeInt }
- }
-
- override fun toContentValues(contentValues: ContentValues, columnName: String, value: Relation.Type) {
- contentValues.put(columnName, value.typeMap)
- }
-}
\ No newline at end of file
diff --git a/kontact/src/wasmJsMain/kotlin/com/hackedcube/kontact/WasmKontactRepository.kt b/kontact/src/wasmJsMain/kotlin/com/hackedcube/kontact/WasmKontactRepository.kt
new file mode 100644
index 0000000..ddfdbd4
--- /dev/null
+++ b/kontact/src/wasmJsMain/kotlin/com/hackedcube/kontact/WasmKontactRepository.kt
@@ -0,0 +1,22 @@
+package com.hackedcube.kontact
+
+import kotlinx.coroutines.flow.Flow
+import kotlinx.coroutines.flow.flow
+
+/**
+ * WasmJs (browser) implementation of [KontactRepository].
+ *
+ * The Contact Picker API is experimental and only available in
+ * Chromium-based browsers. This stub returns empty results;
+ * consumers can extend it to integrate with the browser API.
+ */
+class WasmKontactRepository : KontactRepository {
+
+ override suspend fun queryAllContacts(): List = emptyList()
+
+ override suspend fun getContact(id: String): Kontact? = null
+
+ override fun observeAllContacts(): Flow> = flow {
+ emit(emptyList())
+ }
+}
diff --git a/sample/build.gradle b/sample/build.gradle
deleted file mode 100644
index f9bd0d4..0000000
--- a/sample/build.gradle
+++ /dev/null
@@ -1,38 +0,0 @@
-apply plugin: 'com.android.application'
-
-android {
- compileSdkVersion 26
- buildToolsVersion "26.0.2"
- defaultConfig {
- applicationId "com.hackedcube.kontact.sample"
- minSdkVersion 16
- targetSdkVersion 26
- versionCode 1
- versionName properties.version
- testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
- }
- buildTypes {
- release {
- minifyEnabled false
- proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
- }
- }
-
- compileOptions {
- sourceCompatibility JavaVersion.VERSION_1_8
- targetCompatibility JavaVersion.VERSION_1_8
- }
-}
-
-dependencies {
- compile fileTree(dir: 'libs', include: ['*.jar'])
- androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
- exclude group: 'com.android.support', module: 'support-annotations'
- })
-
- compile project(path: ':kontact')
- compile 'com.android.support:appcompat-v7:26.1.0'
- compile 'com.android.support.constraint:constraint-layout:1.0.2'
- compile 'com.karumi:dexter:4.2.0'
- testCompile 'junit:junit:4.12'
-}
diff --git a/sample/build.gradle.kts b/sample/build.gradle.kts
new file mode 100644
index 0000000..2525a17
--- /dev/null
+++ b/sample/build.gradle.kts
@@ -0,0 +1,98 @@
+plugins {
+ alias(libs.plugins.kotlinMultiplatform)
+ alias(libs.plugins.androidApplication)
+ alias(libs.plugins.composeMultiplatform)
+ alias(libs.plugins.kotlinCompose)
+}
+
+kotlin {
+ androidTarget {
+ compilations.all {
+ compileTaskProvider.configure {
+ compilerOptions {
+ jvmTarget.set(org.jetbrains.kotlin.gradle.dsl.JvmTarget.JVM_17)
+ }
+ }
+ }
+ }
+
+ jvm("desktop")
+
+ listOf(
+ iosX64(),
+ iosArm64(),
+ iosSimulatorArm64(),
+ ).forEach { target ->
+ target.binaries.framework {
+ baseName = "sample"
+ isStatic = true
+ }
+ }
+
+ macosX64 {
+ binaries.executable { entryPoint = "com.hackedcube.kontact.sample.main" }
+ }
+ macosArm64 {
+ binaries.executable { entryPoint = "com.hackedcube.kontact.sample.main" }
+ }
+
+ @OptIn(org.jetbrains.kotlin.gradle.ExperimentalWasmDsl::class)
+ wasmJs {
+ browser()
+ binaries.executable()
+ }
+
+ sourceSets {
+ commonMain.dependencies {
+ implementation(project(":kontact"))
+ implementation(compose.runtime)
+ implementation(compose.foundation)
+ implementation(compose.material3)
+ implementation(compose.components.resources)
+ implementation(libs.kotlinx.coroutines.core)
+ implementation(libs.moko.permissions.core)
+ implementation(libs.moko.permissions.compose)
+ }
+ androidMain.dependencies {
+ implementation(libs.androidx.activity.compose)
+ implementation(libs.kotlinx.coroutines.android)
+ }
+ val desktopMain by getting {
+ dependencies {
+ implementation(compose.desktop.currentOs)
+ implementation(libs.kotlinx.coroutines.swing)
+ }
+ }
+ }
+}
+
+android {
+ namespace = "com.hackedcube.kontact.sample"
+ compileSdk = libs.versions.compileSdk.get().toInt()
+
+ defaultConfig {
+ applicationId = "com.hackedcube.kontact.sample"
+ minSdk = libs.versions.minSdk.get().toInt()
+ targetSdk = libs.versions.targetSdk.get().toInt()
+ versionCode = 1
+ versionName = "2.0.0"
+ }
+
+ buildTypes {
+ release {
+ isMinifyEnabled = false
+ proguardFiles(getDefaultProguardFile("proguard-android-optimize.txt"), "proguard-rules.pro")
+ }
+ }
+
+ compileOptions {
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+}
+
+compose.desktop {
+ application {
+ mainClass = "com.hackedcube.kontact.sample.MainKt"
+ }
+}
diff --git a/sample/src/main/AndroidManifest.xml b/sample/src/androidMain/AndroidManifest.xml
similarity index 63%
rename from sample/src/main/AndroidManifest.xml
rename to sample/src/androidMain/AndroidManifest.xml
index 94bfed2..a75b576 100644
--- a/sample/src/main/AndroidManifest.xml
+++ b/sample/src/androidMain/AndroidManifest.xml
@@ -1,21 +1,19 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/sample/src/androidMain/kotlin/com/hackedcube/kontact/sample/MainActivity.kt b/sample/src/androidMain/kotlin/com/hackedcube/kontact/sample/MainActivity.kt
new file mode 100644
index 0000000..94ebbf3
--- /dev/null
+++ b/sample/src/androidMain/kotlin/com/hackedcube/kontact/sample/MainActivity.kt
@@ -0,0 +1,16 @@
+package com.hackedcube.kontact.sample
+
+import android.os.Bundle
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import com.hackedcube.kontact.AndroidKontactRepository
+
+class MainActivity : ComponentActivity() {
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ val repository = AndroidKontactRepository(this)
+ setContent {
+ App(repository)
+ }
+ }
+}
diff --git a/sample/src/main/res/values/colors.xml b/sample/src/androidMain/res/values/colors.xml
similarity index 78%
rename from sample/src/main/res/values/colors.xml
rename to sample/src/androidMain/res/values/colors.xml
index 2a12c47..6e10013 100644
--- a/sample/src/main/res/values/colors.xml
+++ b/sample/src/androidMain/res/values/colors.xml
@@ -1,6 +1,5 @@
-
-
- #3F51B5
- #303F9F
- #FF4081
-
+
+ #3F51B5
+ #303F9F
+ #FF4081
+
diff --git a/sample/src/main/res/values/strings.xml b/sample/src/androidMain/res/values/strings.xml
similarity index 95%
rename from sample/src/main/res/values/strings.xml
rename to sample/src/androidMain/res/values/strings.xml
index 341077a..1842251 100644
--- a/sample/src/main/res/values/strings.xml
+++ b/sample/src/androidMain/res/values/strings.xml
@@ -1,3 +1,3 @@
-
- Kontact
-
+
+ Kontact
+
diff --git a/sample/src/androidTest/java/com/hackedcube/kontact/ExampleInstrumentedTest.java b/sample/src/androidTest/java/com/hackedcube/kontact/ExampleInstrumentedTest.java
deleted file mode 100644
index a16de70..0000000
--- a/sample/src/androidTest/java/com/hackedcube/kontact/ExampleInstrumentedTest.java
+++ /dev/null
@@ -1,26 +0,0 @@
-package com.hackedcube.kontact;
-
-import android.content.Context;
-import android.support.test.InstrumentationRegistry;
-import android.support.test.runner.AndroidJUnit4;
-
-import org.junit.Test;
-import org.junit.runner.RunWith;
-
-import static org.junit.Assert.*;
-
-/**
- * Instrumentation test, which will execute on an Android device.
- *
- * @see Testing documentation
- */
-@RunWith(AndroidJUnit4.class)
-public class ExampleInstrumentedTest {
- @Test
- public void useAppContext() throws Exception {
- // Context of the app under test.
- Context appContext = InstrumentationRegistry.getTargetContext();
-
- assertEquals("com.hackedcube.kontact", appContext.getPackageName());
- }
-}
diff --git a/sample/src/commonMain/kotlin/com/hackedcube/kontact/sample/App.kt b/sample/src/commonMain/kotlin/com/hackedcube/kontact/sample/App.kt
new file mode 100644
index 0000000..4bfac80
--- /dev/null
+++ b/sample/src/commonMain/kotlin/com/hackedcube/kontact/sample/App.kt
@@ -0,0 +1,160 @@
+package com.hackedcube.kontact.sample
+
+import androidx.compose.foundation.layout.Arrangement
+import androidx.compose.foundation.layout.Column
+import androidx.compose.foundation.layout.PaddingValues
+import androidx.compose.foundation.layout.Row
+import androidx.compose.foundation.layout.Spacer
+import androidx.compose.foundation.layout.fillMaxSize
+import androidx.compose.foundation.layout.fillMaxWidth
+import androidx.compose.foundation.layout.height
+import androidx.compose.foundation.layout.padding
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.foundation.lazy.items
+import androidx.compose.material3.Button
+import androidx.compose.material3.Card
+import androidx.compose.material3.ExperimentalMaterial3Api
+import androidx.compose.material3.MaterialTheme
+import androidx.compose.material3.Scaffold
+import androidx.compose.material3.Text
+import androidx.compose.material3.TopAppBar
+import androidx.compose.runtime.Composable
+import androidx.compose.runtime.getValue
+import androidx.compose.runtime.mutableStateOf
+import androidx.compose.runtime.remember
+import androidx.compose.runtime.rememberCoroutineScope
+import androidx.compose.runtime.setValue
+import androidx.compose.ui.Alignment
+import androidx.compose.ui.Modifier
+import androidx.compose.ui.unit.dp
+import com.hackedcube.kontact.Kontact
+import com.hackedcube.kontact.KontactRepository
+import dev.icerock.moko.permissions.Permission
+import dev.icerock.moko.permissions.PermissionState
+import dev.icerock.moko.permissions.compose.BindEffect
+import dev.icerock.moko.permissions.compose.rememberPermissionsControllerFactory
+import kotlinx.coroutines.launch
+
+@OptIn(ExperimentalMaterial3Api::class)
+@Composable
+fun App(repository: KontactRepository) {
+ MaterialTheme {
+ val scope = rememberCoroutineScope()
+ var contacts by remember { mutableStateOf>(emptyList()) }
+ var loading by remember { mutableStateOf(false) }
+ var permissionGranted by remember { mutableStateOf(false) }
+ var permissionDenied by remember { mutableStateOf(false) }
+
+ val factory = rememberPermissionsControllerFactory()
+ val controller = remember(factory) { factory.createPermissionsController() }
+ BindEffect(controller)
+
+ fun loadContacts() {
+ loading = true
+ scope.launch {
+ val state = controller.getPermissionState(Permission.CONTACTS)
+ if (state == PermissionState.Granted) {
+ permissionGranted = true
+ contacts = repository.queryAllContacts()
+ } else {
+ try {
+ controller.providePermission(Permission.CONTACTS)
+ permissionGranted = true
+ contacts = repository.queryAllContacts()
+ } catch (_: Exception) {
+ permissionDenied = true
+ }
+ }
+ loading = false
+ }
+ }
+
+ Scaffold(
+ topBar = {
+ TopAppBar(title = { Text("Kontact") })
+ },
+ ) { padding ->
+ Column(
+ modifier = Modifier.fillMaxSize().padding(padding).padding(16.dp),
+ horizontalAlignment = Alignment.CenterHorizontally,
+ ) {
+ Button(
+ onClick = { loadContacts() },
+ enabled = !loading,
+ ) {
+ Text(if (loading) "Loading..." else "Import All Contacts")
+ }
+
+ Spacer(Modifier.height(8.dp))
+
+ when {
+ permissionDenied -> Text(
+ text = "Contacts permission denied",
+ style = MaterialTheme.typography.bodyMedium,
+ color = MaterialTheme.colorScheme.error,
+ )
+ contacts.isNotEmpty() -> Text(
+ text = "${contacts.size} contacts loaded",
+ style = MaterialTheme.typography.bodyMedium,
+ )
+ }
+
+ Spacer(Modifier.height(16.dp))
+
+ LazyColumn(
+ modifier = Modifier.fillMaxWidth(),
+ contentPadding = PaddingValues(vertical = 4.dp),
+ verticalArrangement = Arrangement.spacedBy(8.dp),
+ ) {
+ items(contacts, key = { it.id }) { contact ->
+ ContactCard(contact)
+ }
+ }
+ }
+ }
+ }
+}
+
+@Composable
+private fun ContactCard(contact: Kontact) {
+ Card(modifier = Modifier.fillMaxWidth()) {
+ Column(modifier = Modifier.padding(16.dp)) {
+ Text(
+ text = contact.displayNamePrimary,
+ style = MaterialTheme.typography.titleMedium,
+ )
+
+ if (contact.phoneNumbers.isNotEmpty()) {
+ Spacer(Modifier.height(4.dp))
+ contact.phoneNumbers.forEach { phone ->
+ Row {
+ Text(
+ text = "${phone.type}: ",
+ style = MaterialTheme.typography.labelSmall,
+ )
+ Text(
+ text = phone.number,
+ style = MaterialTheme.typography.bodySmall,
+ )
+ }
+ }
+ }
+
+ if (contact.emailAddresses.isNotEmpty()) {
+ Spacer(Modifier.height(4.dp))
+ contact.emailAddresses.forEach { email ->
+ Row {
+ Text(
+ text = "${email.type}: ",
+ style = MaterialTheme.typography.labelSmall,
+ )
+ Text(
+ text = email.address,
+ style = MaterialTheme.typography.bodySmall,
+ )
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/sample/src/desktopMain/kotlin/com/hackedcube/kontact/sample/Main.kt b/sample/src/desktopMain/kotlin/com/hackedcube/kontact/sample/Main.kt
new file mode 100644
index 0000000..e7de265
--- /dev/null
+++ b/sample/src/desktopMain/kotlin/com/hackedcube/kontact/sample/Main.kt
@@ -0,0 +1,14 @@
+package com.hackedcube.kontact.sample
+
+import androidx.compose.ui.window.Window
+import androidx.compose.ui.window.application
+import com.hackedcube.kontact.DesktopKontactRepository
+
+fun main() = application {
+ Window(
+ onCloseRequest = ::exitApplication,
+ title = "Kontact",
+ ) {
+ App(DesktopKontactRepository())
+ }
+}
diff --git a/sample/src/iosMain/kotlin/com/hackedcube/kontact/sample/MainViewController.kt b/sample/src/iosMain/kotlin/com/hackedcube/kontact/sample/MainViewController.kt
new file mode 100644
index 0000000..dd2b73f
--- /dev/null
+++ b/sample/src/iosMain/kotlin/com/hackedcube/kontact/sample/MainViewController.kt
@@ -0,0 +1,8 @@
+package com.hackedcube.kontact.sample
+
+import androidx.compose.ui.window.ComposeUIViewController
+import com.hackedcube.kontact.AppleKontactRepository
+
+fun MainViewController() = ComposeUIViewController {
+ App(AppleKontactRepository())
+}
diff --git a/sample/src/macosMain/kotlin/com/hackedcube/kontact/sample/Main.kt b/sample/src/macosMain/kotlin/com/hackedcube/kontact/sample/Main.kt
new file mode 100644
index 0000000..97d1a75
--- /dev/null
+++ b/sample/src/macosMain/kotlin/com/hackedcube/kontact/sample/Main.kt
@@ -0,0 +1,14 @@
+package com.hackedcube.kontact.sample
+
+import androidx.compose.ui.window.Window
+import com.hackedcube.kontact.AppleKontactRepository
+import platform.AppKit.NSApp
+import platform.AppKit.NSApplication
+
+fun main() {
+ NSApplication.sharedApplication()
+ Window(title = "Kontact") {
+ App(AppleKontactRepository())
+ }
+ NSApp?.run()
+}
diff --git a/sample/src/main/java/com/hackedcube/kontact/sample/MainActivity.java b/sample/src/main/java/com/hackedcube/kontact/sample/MainActivity.java
deleted file mode 100644
index a2b9d0b..0000000
--- a/sample/src/main/java/com/hackedcube/kontact/sample/MainActivity.java
+++ /dev/null
@@ -1,86 +0,0 @@
-package com.hackedcube.kontact.sample;
-
-import android.Manifest;
-import android.app.Activity;
-import android.content.Intent;
-import android.os.Bundle;
-import android.provider.ContactsContract;
-import android.util.Log;
-import android.view.View;
-import android.widget.Button;
-
-import com.hackedcube.kontact.ContactUtils;
-import com.hackedcube.kontact.Kontact;
-import com.karumi.dexter.Dexter;
-import com.karumi.dexter.PermissionToken;
-import com.karumi.dexter.listener.PermissionDeniedResponse;
-import com.karumi.dexter.listener.PermissionGrantedResponse;
-import com.karumi.dexter.listener.PermissionRequest;
-import com.karumi.dexter.listener.single.PermissionListener;
-
-import java.util.List;
-
-public class MainActivity extends Activity {
-
- private final int PICK_CONTACT = 1;
-
- @Override
- protected void onCreate(Bundle savedInstanceState) {
- super.onCreate(savedInstanceState);
- setContentView(R.layout.activity_main);
-
-
- Dexter.withActivity(this)
- .withPermission(Manifest.permission.READ_CONTACTS)
- .withListener(new PermissionListener() {
- @Override
- public void onPermissionGranted(PermissionGrantedResponse response) {
- // NO OP
- }
-
- @Override
- public void onPermissionDenied(PermissionDeniedResponse response) {
- // NO-OP
- }
-
- @Override
- public void onPermissionRationaleShouldBeShown(PermissionRequest permission, PermissionToken token) {
- token.continuePermissionRequest();
- }
- })
- .check();
-
-
- Button buttonImportContact = (Button) findViewById(R.id.buttonImport);
-
- buttonImportContact.setOnClickListener(v -> {
- Intent intent = new Intent(Intent.ACTION_PICK, ContactsContract.Contacts.CONTENT_URI);
- startActivityForResult(intent, PICK_CONTACT);
- });
-
-
- Button buttonImportAllContact = (Button) findViewById(R.id.buttonImportAllContacts);
-
-
- buttonImportAllContact.setOnClickListener(v -> {
- List kontacts = ContactUtils.queryAllContacts(this);
- Log.d("Kontacts", "Kontacts Count: " + kontacts);
- });
-
-
-
- }
-
-
- @Override
- protected void onActivityResult(int requestCode, int resultCode, Intent data) {
- super.onActivityResult(requestCode, resultCode, data);
-
- if (resultCode == Activity.RESULT_OK && requestCode == PICK_CONTACT) {
- Kontact kontact = ContactUtils.getContactFromId(this, data.getData());
-
- Log.d("Kontact Import", "Kontact: " + kontact.toString());
- }
-
- }
-}
diff --git a/sample/src/main/res/layout/activity_main.xml b/sample/src/main/res/layout/activity_main.xml
deleted file mode 100644
index c3ff86e..0000000
--- a/sample/src/main/res/layout/activity_main.xml
+++ /dev/null
@@ -1,34 +0,0 @@
-
-
-
-
-
-
-
diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher.png b/sample/src/main/res/mipmap-hdpi/ic_launcher.png
deleted file mode 100644
index cde69bc..0000000
Binary files a/sample/src/main/res/mipmap-hdpi/ic_launcher.png and /dev/null differ
diff --git a/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png
deleted file mode 100644
index 9a078e3..0000000
Binary files a/sample/src/main/res/mipmap-hdpi/ic_launcher_round.png and /dev/null differ
diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher.png b/sample/src/main/res/mipmap-mdpi/ic_launcher.png
deleted file mode 100644
index c133a0c..0000000
Binary files a/sample/src/main/res/mipmap-mdpi/ic_launcher.png and /dev/null differ
diff --git a/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png
deleted file mode 100644
index efc028a..0000000
Binary files a/sample/src/main/res/mipmap-mdpi/ic_launcher_round.png and /dev/null differ
diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher.png
deleted file mode 100644
index bfa42f0..0000000
Binary files a/sample/src/main/res/mipmap-xhdpi/ic_launcher.png and /dev/null differ
diff --git a/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png
deleted file mode 100644
index 3af2608..0000000
Binary files a/sample/src/main/res/mipmap-xhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png
deleted file mode 100644
index 324e72c..0000000
Binary files a/sample/src/main/res/mipmap-xxhdpi/ic_launcher.png and /dev/null differ
diff --git a/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
deleted file mode 100644
index 9bec2e6..0000000
Binary files a/sample/src/main/res/mipmap-xxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png
deleted file mode 100644
index aee44e1..0000000
Binary files a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher.png and /dev/null differ
diff --git a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
deleted file mode 100644
index 34947cd..0000000
Binary files a/sample/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png and /dev/null differ
diff --git a/sample/src/main/res/values/styles.xml b/sample/src/main/res/values/styles.xml
deleted file mode 100644
index 6f19b47..0000000
--- a/sample/src/main/res/values/styles.xml
+++ /dev/null
@@ -1,11 +0,0 @@
-
-
-
-
-
-
diff --git a/sample/src/test/java/com/hackedcube/kontact/ExampleUnitTest.java b/sample/src/test/java/com/hackedcube/kontact/ExampleUnitTest.java
deleted file mode 100644
index 9bb5863..0000000
--- a/sample/src/test/java/com/hackedcube/kontact/ExampleUnitTest.java
+++ /dev/null
@@ -1,17 +0,0 @@
-package com.hackedcube.kontact;
-
-import org.junit.Test;
-
-import static org.junit.Assert.*;
-
-/**
- * Example local unit test, which will execute on the development machine (host).
- *
- * @see Testing documentation
- */
-public class ExampleUnitTest {
- @Test
- public void addition_isCorrect() throws Exception {
- assertEquals(4, 2 + 2);
- }
-}
\ No newline at end of file
diff --git a/sample/src/wasmJsMain/kotlin/com/hackedcube/kontact/sample/Main.kt b/sample/src/wasmJsMain/kotlin/com/hackedcube/kontact/sample/Main.kt
new file mode 100644
index 0000000..285a9cf
--- /dev/null
+++ b/sample/src/wasmJsMain/kotlin/com/hackedcube/kontact/sample/Main.kt
@@ -0,0 +1,12 @@
+package com.hackedcube.kontact.sample
+
+import androidx.compose.ui.ExperimentalComposeUiApi
+import androidx.compose.ui.window.CanvasBasedWindow
+import com.hackedcube.kontact.WasmKontactRepository
+
+@OptIn(ExperimentalComposeUiApi::class)
+fun main() {
+ CanvasBasedWindow(canvasElementId = "ComposeTarget") {
+ App(WasmKontactRepository())
+ }
+}
diff --git a/sample/src/wasmJsMain/resources/index.html b/sample/src/wasmJsMain/resources/index.html
new file mode 100644
index 0000000..61ffacf
--- /dev/null
+++ b/sample/src/wasmJsMain/resources/index.html
@@ -0,0 +1,25 @@
+
+
+
+
+
+ Kontact
+
+
+
+
+
+
+
diff --git a/settings.gradle b/settings.gradle
deleted file mode 100644
index 530d1d0..0000000
--- a/settings.gradle
+++ /dev/null
@@ -1 +0,0 @@
-include ':sample', ':kontact', ':kontact-rxjava', ':kontact-rxjava2'
diff --git a/settings.gradle.kts b/settings.gradle.kts
new file mode 100644
index 0000000..725fe26
--- /dev/null
+++ b/settings.gradle.kts
@@ -0,0 +1,19 @@
+pluginManagement {
+ repositories {
+ google()
+ mavenCentral()
+ gradlePluginPortal()
+ }
+}
+
+dependencyResolution {
+ repositories {
+ google()
+ mavenCentral()
+ }
+}
+
+rootProject.name = "Kontact"
+
+include(":kontact")
+include(":sample")