Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
80 commits
Select commit Hold shift + click to select a range
837ab2e
feat(android): scaffold DocumentsProvider for SAF file picker integra…
terrerox Apr 17, 2026
f625885
chore(android): bump okhttp to 5.3.2 and security-crypto to 1.1.0
terrerox Apr 17, 2026
e202e27
feat(android): add native InternxtApiClient for DocumentsProvider
terrerox Apr 20, 2026
d201970
test(android): streamline InternxtApiClient tests
terrerox Apr 20, 2026
660a1a8
refactor(android): extract PARENT_UUID constant in InternxtApiClientTest
terrerox Apr 20, 2026
7a75302
refactor(android): source API client identity from package.json
terrerox Apr 24, 2026
fb889be
refactor(android): replace hardcoded bucket ID and folder name with c…
terrerox Apr 24, 2026
6dcdb32
refactor(android): replace readTimeout with callTimeout in default Ok…
terrerox May 1, 2026
63f4a7a
feat(android): show Internxt Drive in SAF picker per session state
terrerox Apr 23, 2026
2d10bd1
refactor(auth): replace InternxtAuthCredentialsModule with direct fun…
terrerox Apr 23, 2026
b7dc7d2
feat(auth): include client name and version in authentication credent…
terrerox Apr 24, 2026
33a713f
feat(auth): enhance credential management with error handling and roo…
terrerox May 13, 2026
3160266
refactor(documents): update authManager to nullable type for safer ac…
terrerox May 16, 2026
07aa7c4
feat(android): list folder contents in SAF documents provider
terrerox Apr 29, 2026
6cb015d
feat(documents): implement encoding and decoding for folder and file IDs
terrerox May 13, 2026
c0abba0
feat(documents): implement encoding and decoding for folder and file IDs
terrerox May 13, 2026
e65484f
feat(android): support rename, move, and trash in SAF picker
terrerox Apr 30, 2026
5e3a4f4
feat(documents): update folderRow to encode folder UUID and refactor …
terrerox May 14, 2026
908e48f
chore(android): reset docs provider + tests to PB-5927 base for split
terrerox May 20, 2026
a353c4a
feat(documents): implement document rename, move, and delete function…
terrerox May 20, 2026
26dec09
feat(tests): add unit tests for file and folder rename, move, and tra…
terrerox May 20, 2026
f421224
feat(android): support rename, move, and trash in SAF picker
terrerox Apr 30, 2026
13bbd2f
feat(auth): add mnemonic to authentication credentials and related mo…
terrerox May 12, 2026
ee5665d
feat(api): update download links retrieval to use new /info endpoint …
terrerox May 21, 2026
2e66bd2
feat(crypto): refactor FileKeyDeriver to use CryptoService for key de…
terrerox May 21, 2026
adb3411
feat(download): refactor download logic to improve readability and er…
terrerox May 21, 2026
f120a1e
feat(download): refactor download process and enhance error handling …
terrerox May 21, 2026
296cfff
feat(tests): add unit tests for FileKeyDeriver key and IV derivation
terrerox May 24, 2026
8006d65
feat(cache): enhance existingCacheFor method to retrieve non-empty ca…
terrerox Jun 1, 2026
8615411
feat(android): add encrypted file upload flow with multipart support
terrerox May 28, 2026
d5c381f
fix: correct encryption version format in CreateFileEntry
terrerox Jun 1, 2026
09db1ae
refactor(upload): streamline ETag handling in uploadMultipart method
terrerox Jun 5, 2026
2d5fb5c
feat(android): add DocumentsProvider write/upload pipeline
terrerox May 28, 2026
16103bd
feat(android): add foreground upload notifications with cancel and pr…
terrerox May 28, 2026
69e9315
test(android): add unit tests for upload API and encrypted uploader
terrerox May 28, 2026
1b4a08e
feat(android): create folders from DocumentsProvider createDocument
terrerox Jun 3, 2026
7b6f18c
refactor(android): remove obsolete uniqueChildName documentation
terrerox Jun 3, 2026
8c6940b
test(android): update DocumentNamingTest to use constants for folder …
terrerox Jun 3, 2026
1e95b36
fix(android): refresh file picker after uploads and folder creation
terrerox Jun 5, 2026
13059f4
refactor(test): streamline test cases by removing redundant comments
terrerox Jun 5, 2026
6dbca31
refactor(test): simplify test setup by using arrangePresentNative fun…
terrerox Jun 5, 2026
f69d15b
refactor(folder): improve error handling in createFolder method and c…
terrerox Jun 5, 2026
1281bc6
fix(native): handle errors in notifyParentChanged and log warnings
terrerox Jun 6, 2026
8f3c76b
refactor(test): replace 'it' with 'test' for consistency in upload si…
terrerox Jun 12, 2026
1b216f0
refactor(android): migrate DocumentsProvider IO to kotlinx coroutines
terrerox Jun 6, 2026
c29e486
refactor(test): replace hardcoded error message with constant in Cryp…
terrerox Jun 6, 2026
0b61efa
fix: improve exception handling in openDocument method
terrerox Jun 12, 2026
2075509
feat(ios): add File Provider extension sources (InternxtFileProvider)
terrerox Jun 8, 2026
cde2b09
feat(ios): wire InternxtFileProvider target into Xcode project (objec…
terrerox Jun 8, 2026
43ff69a
feat(ios): register File Provider domain on login/logout (PB-5920)
terrerox Jun 10, 2026
421a872
feat(ios): update SharedAuthKeychain to support JSON encoded data and…
terrerox Jun 10, 2026
8e3a930
feat(ios): add shared driveBaseUrl key for File Provider Drive API
terrerox Jun 12, 2026
78fb3d6
feat(ios): browse Drive folders in File Provider enumerator
terrerox Jun 12, 2026
9e39c36
feat(ios): refactor date parsing methods in FileProviderItem
terrerox Jul 6, 2026
b649e44
feat(ios): resolve File Provider items via item(for:) lookup
terrerox Jun 12, 2026
cfa3a5d
feat(auth): forward driveBaseUrl to shared keychain on credential write
terrerox Jun 12, 2026
33f3185
feat(ios): signal working set after domain registration
terrerox Jun 12, 2026
643ee40
feat(ios): download and decrypt File Provider files for open in Files
terrerox Jun 12, 2026
4368c77
feat(ios): create folders and upload encrypted files via File Provider
terrerox Jun 16, 2026
3dbfc46
refactor(ios): drop custom File Provider error messages, use plain NS…
terrerox Jun 16, 2026
d478e03
refactor(ios): split FileProviderExtension into focused services
terrerox Jun 16, 2026
8b324b5
test(ios): add FileProviderErrorMapper unit test target
terrerox Jun 16, 2026
a24c460
refactor(ios): update test method names for consistency and clarity
terrerox Jun 16, 2026
9eca260
feat(ios): File Provider rename, move and trash
terrerox Jun 16, 2026
1baaa2f
feat(ios): working-set change-feed signaling for File Provider
terrerox Jun 23, 2026
52316ef
refactor(tests): update test method names for consistency and clarity
terrerox Jun 25, 2026
1012a6c
feat: signal File Provider on RN drive mutations
terrerox Jun 23, 2026
4fd61f5
feat: add theme preference synchronization to SharedAuthKeychain
terrerox Jul 13, 2026
197fffd
fix: file-provider integration fixes for Android and iOS
terrerox Aug 23, 2026
8a3a85d
fix: address Sonar findings and repair auth credentials spec
terrerox Aug 24, 2026
72150db
refactor: consolidate file metadata into a single struct for improved…
terrerox Aug 24, 2026
7f38a93
fix: update invalidate method comment for clarity on resource management
terrerox Aug 24, 2026
6af3e44
chore: update version to 1.11.0 across configuration files
terrerox Aug 24, 2026
85c0d54
fix: skip metadata extraction for IDZSwiftCommonCrypto to avoid dupli…
terrerox Aug 24, 2026
1d2aa3f
fix: add document row for newly created folder in InternxtDocumentsPr…
terrerox Aug 25, 2026
01fb3f7
feat: add stabilize method to FileProviderDomainManager and integrate…
terrerox Aug 27, 2026
7490baf
chore: downgrade version to 1.11.0 in build.gradle, strings.xml, Info…
terrerox Aug 27, 2026
c0201fd
fix: keep item metadata accurate after rename in system file managers
terrerox Aug 27, 2026
e37645e
fix(android): make DocumentsProvider work through tree grants and kee…
terrerox Aug 28, 2026
af5f860
refactor(tests): replace hardcoded parent UUID with constant in Docum…
terrerox Aug 28, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .github/CODEOWNERS
Original file line number Diff line number Diff line change
Expand Up @@ -4,3 +4,4 @@ src/network/crypto.spec.ts @TamaraFinogina
src/shareExtension/services/shareEncryptionService.ts @TamaraFinogina
src/shareExtension/services/shareUploadService.ts @TamaraFinogina
src/network/NetworkFacade.ts @TamaraFinogina
android/app/src/main/java/com/internxt/cloud/documents/crypto/ @TamaraFinogina
18 changes: 16 additions & 2 deletions android/app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ apply plugin: "com.facebook.react"

def projectRoot = rootDir.getAbsoluteFile().getParentFile().getAbsolutePath()

def packageJson = new groovy.json.JsonSlurper().parse(file("${projectRoot}/package.json"))

/**
* This is the configuration block to customize your React Native Android app.
* By default you don't need to apply any configuration, just uncomment the lines you need.
Expand Down Expand Up @@ -93,10 +95,12 @@ android {
applicationId 'com.internxt.cloud'
minSdkVersion rootProject.ext.minSdkVersion
targetSdkVersion rootProject.ext.targetSdkVersion
versionCode 129
versionName "1.10.2"
versionCode 130
versionName "1.11.0"

buildConfigField "String", "REACT_NATIVE_RELEASE_LEVEL", "\"${findProperty('reactNativeReleaseLevel') ?: 'stable'}\""
buildConfigField "String", "INTERNXT_CLIENT_NAME", "\"${packageJson.name}\""
buildConfigField "String", "INTERNXT_CLIENT_VERSION", "\"${packageJson.version}\""
}
flavorDimensions "react-native-capture-protection"
productFlavors {
Expand Down Expand Up @@ -192,4 +196,14 @@ dependencies {
} else {
implementation jscFlavor
}

implementation("com.squareup.okhttp3:okhttp:5.3.2")
implementation("androidx.security:security-crypto:1.1.0")
implementation("org.bouncycastle:bcprov-jdk15to18:1.78.1")
implementation("org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2")

testImplementation("junit:junit:4.13.2")
testImplementation("com.squareup.okhttp3:mockwebserver:5.3.2")
testImplementation("org.json:json:20240303")
testImplementation("org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2")
}
1 change: 1 addition & 0 deletions android/app/proguard-rules.pro
Original file line number Diff line number Diff line change
Expand Up @@ -12,3 +12,4 @@
-keep class com.facebook.react.turbomodule.** { *; }

# Add any project specific keep options here:
-keep class com.internxt.cloud.documents.** { *; }
22 changes: 22 additions & 0 deletions android/app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@
<uses-permission android:name="android.permission.USE_FINGERPRINT"/>
<uses-permission android:name="android.permission.VIBRATE"/>
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_DATA_SYNC"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<queries>
<intent>
<action android:name="android.intent.action.VIEW"/>
Expand Down Expand Up @@ -59,5 +62,24 @@
<category android:name="android.intent.category.DEFAULT"/>
</intent-filter>
</activity>
<service
android:name="com.internxt.cloud.documents.upload.UploadForegroundService"
android:foregroundServiceType="dataSync"
android:exported="false"/>
<receiver
android:name="com.internxt.cloud.documents.upload.CancelUploadReceiver"
android:exported="false"/>
<provider
android:name="com.internxt.cloud.documents.InternxtDocumentsProvider"
android:authorities="com.internxt.cloud.documents"
android:exported="true"
android:grantUriPermissions="true"
android:permission="android.permission.MANAGE_DOCUMENTS"

Check warning on line 77 in android/app/src/main/AndroidManifest.xml

View check run for this annotation

SonarQubeCloud / SonarCloud Code Analysis

Make sure using a single permission for read and write is safe here.

See more on https://sonarcloud.io/project/issues?id=internxt_drive-mobile&issues=AaAtCbX_Lm9l5kt0oQAE&open=AaAtCbX_Lm9l5kt0oQAE&pullRequest=533
android:label="@string/documents_provider_label"
android:icon="@mipmap/ic_launcher">
<intent-filter>
<action android:name="android.content.action.DOCUMENTS_PROVIDER"/>
</intent-filter>
</provider>
</application>
</manifest>
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ import com.facebook.react.common.ReleaseLevel
import com.facebook.react.defaults.DefaultNewArchitectureEntryPoint
import com.facebook.react.defaults.DefaultReactNativeHost

import com.internxt.cloud.auth.InternxtAuthCredentialsPackage
import com.internxt.cloud.documents.signaling.InternxtSignalingPackage

import expo.modules.ApplicationLifecycleDispatcher
import expo.modules.ReactNativeHostWrapper

Expand All @@ -24,6 +27,8 @@ class MainApplication : Application(), ReactApplication {
override fun getPackages(): List<ReactPackage> =
PackageList(this).packages.apply {
add(ShareIntentPackage())
add(InternxtAuthCredentialsPackage())
add(InternxtSignalingPackage())
}

override fun getJSMainModuleName(): String = ".expo/.virtual-metro-entry"
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
package com.internxt.cloud.auth

import android.provider.DocumentsContract
import com.facebook.react.bridge.Promise
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.bridge.ReactContextBaseJavaModule
import com.facebook.react.bridge.ReactMethod
import com.facebook.react.bridge.ReadableMap
import com.internxt.cloud.documents.InternxtDocumentsProvider
import com.internxt.cloud.documents.auth.InternxtAuthManager

class InternxtAuthCredentialsModule(private val ctx: ReactApplicationContext) :
ReactContextBaseJavaModule(ctx) {

private val authManager by lazy { InternxtAuthManager.create(ctx.applicationContext) }

override fun getName() = MODULE_NAME

@ReactMethod
fun setCredentials(map: ReadableMap, promise: Promise) {
val manager = authManager ?: run {
promise.reject("E_AUTH_UNAVAILABLE", "Encrypted credential storage unavailable")
return
}
try {
val creds = InternxtAuthManager.Credentials(
bearerToken = map.requireString("bearerToken"),
userId = map.requireString("userId"),
bridgeUser = map.requireString("bridgeUser"),
mnemonic = map.requireString("mnemonic"),
rootFolderUuid = map.requireString("rootFolderUuid"),
email = map.optString("email"),
driveBaseUrl = map.requireString("driveBaseUrl"),
bridgeBaseUrl = map.requireString("bridgeBaseUrl"),
desktopToken = map.optString("desktopToken"),
)
if (!manager.saveCredentials(creds)) {
promise.reject("E_SAVE_CREDENTIALS", "Failed to persist credentials")
return
}
notifyRootsChanged()
promise.resolve(null)
} catch (e: IllegalArgumentException) {
promise.reject("E_MISSING_FIELD", e.message, e)
} catch (e: Exception) {
promise.reject("E_SAVE_CREDENTIALS", e.message, e)
}
}

@ReactMethod
fun clearCredentials(promise: Promise) {
val manager = authManager ?: run {
promise.reject("E_AUTH_UNAVAILABLE", "Encrypted credential storage unavailable")
return
}
try {
if (!manager.clear()) {
promise.reject("E_CLEAR_CREDENTIALS", "Failed to clear credentials")
return
}
notifyRootsChanged()
promise.resolve(null)
} catch (e: Exception) {
promise.reject("E_CLEAR_CREDENTIALS", e.message, e)
}
}

private fun notifyRootsChanged() {
ctx.contentResolver.notifyChange(
DocumentsContract.buildRootsUri(InternxtDocumentsProvider.AUTHORITY),
null,
)
}

private fun ReadableMap.nonBlankString(key: String): String? =
if (hasKey(key) && !isNull(key)) getString(key)?.takeIf { it.isNotBlank() } else null

private fun ReadableMap.requireString(key: String): String =
nonBlankString(key) ?: throw IllegalArgumentException("Missing or blank credential field: $key")

private fun ReadableMap.optString(key: String): String? = nonBlankString(key)

companion object {
const val MODULE_NAME = "InternxtAuthCredentialsModule"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
package com.internxt.cloud.auth

import com.facebook.react.ReactPackage
import com.facebook.react.bridge.NativeModule
import com.facebook.react.bridge.ReactApplicationContext
import com.facebook.react.uimanager.ViewManager

class InternxtAuthCredentialsPackage : ReactPackage {
override fun createNativeModules(context: ReactApplicationContext): List<NativeModule> =
listOf(InternxtAuthCredentialsModule(context))

override fun createViewManagers(context: ReactApplicationContext): List<ViewManager<*, *>> =
emptyList()
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
package com.internxt.cloud.documents

import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.runBlocking

/**
* Bridges a binder thread to a suspending body, running it on [Dispatchers.IO].
*
* SAF entry points such as `openDocument` arrive on a binder thread governed by a StrictMode
* policy that forbids blocking network. Some collaborators (e.g. the synchronous OkHttp calls in
* InternxtApiClient) still block, so the body must not run on the caller thread or it trips
* NetworkOnMainThreadException. Plain `runBlocking { }` would do exactly that.
*/
internal fun <T> runBlockingIo(body: suspend CoroutineScope.() -> T): T =
runBlocking(Dispatchers.IO, body)
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.internxt.cloud.documents

internal object DocumentAncestry {

private const val MAX_HOPS = 64

fun isDescendant(childUuid: String, parentUuid: String, parentOf: (String) -> String?): Boolean {
if (childUuid == parentUuid) return true
val visited = mutableSetOf(childUuid)
var current = childUuid
repeat(MAX_HOPS) {
val next = parentOf(current) ?: return false
if (next == parentUuid) return true
if (!visited.add(next)) return false
current = next
}
return false
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
package com.internxt.cloud.documents

object DocumentId {

enum class Kind { FOLDER, FILE }

data class Decoded(val kind: Kind, val uuid: String)

private const val FOLDER_PREFIX = "f:"
private const val FILE_PREFIX = "d:"
const val UPLOAD_PREFIX = "u:"

fun encodeFolder(uuid: String): String = FOLDER_PREFIX + uuid
fun encodeFile(uuid: String): String = FILE_PREFIX + uuid
fun encodeUpload(token: String): String = UPLOAD_PREFIX + token

fun isUploadToken(id: String): Boolean = id.startsWith(UPLOAD_PREFIX)
fun decodeUpload(id: String): String? =
if (isUploadToken(id)) id.removePrefix(UPLOAD_PREFIX) else null

fun decode(id: String): Decoded? = when {
id.startsWith(FOLDER_PREFIX) -> Decoded(Kind.FOLDER, id.removePrefix(FOLDER_PREFIX))
id.startsWith(FILE_PREFIX) -> Decoded(Kind.FILE, id.removePrefix(FILE_PREFIX))
else -> null
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
package com.internxt.cloud.documents

import java.util.UUID

/** Pure naming helpers shared by the folder and file branches of [InternxtDocumentsProvider.createDocument]. */
object DocumentNaming {

private const val MAX_SUFFIX_ATTEMPTS = 1000

/**
* Returns [requested] if it is not already in [existing]; otherwise appends a
* ` (n)` suffix (preserving any file extension) until a free name is found.
*/
fun uniqueName(requested: String, existing: Set<String>): String {
if (requested !in existing) return requested
val (base, ext) = splitNameExt(requested)
for (i in 1..MAX_SUFFIX_ATTEMPTS) {
val candidate = "$base ($i)$ext"
if (candidate !in existing) return candidate
}
// Backstop for the (practically impossible) case where 1..1000 are all taken —
// the backend permits duplicate names, so a UUID-suffixed name is always safe.
return "$base (${UUID.randomUUID()})$ext"
}

fun splitNameExt(name: String): Pair<String, String> {
val dot = name.lastIndexOf('.')
val hasExt = dot > 0 && dot < name.length - 1
return if (hasExt) name.substring(0, dot) to name.substring(dot) else name to ""
}

fun joinNameType(plainName: String, type: String?): String {
if (type.isNullOrBlank()) return plainName
val alreadySuffixed = plainName.endsWith(".$type", ignoreCase = true)
return if (alreadySuffixed) plainName else "$plainName.$type"
}

fun extensionOf(plainName: String, type: String?): String? =
type?.takeIf { it.isNotBlank() } ?: splitNameExt(plainName).second.removePrefix(".").takeIf { it.isNotEmpty() }

fun renameTarget(requestedDisplayName: String, currentType: String?): Pair<String, String>? {
val newBase = splitNameExt(requestedDisplayName.trim()).first
if (newBase.isBlank()) return null
return newBase to joinNameType(newBase, currentType)
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
package com.internxt.cloud.documents

import android.provider.DocumentsContract.Document
import com.internxt.cloud.documents.api.model.DriveFile
import com.internxt.cloud.documents.api.model.DriveFolder
import java.time.Instant
import java.time.format.DateTimeParseException

object DocumentRowBuilder {

private const val MUTATION_FLAGS =
Document.FLAG_SUPPORTS_RENAME or
Document.FLAG_SUPPORTS_DELETE or
Document.FLAG_SUPPORTS_MOVE

private const val FOLDER_FLAGS_BASIC = Document.FLAG_DIR_SUPPORTS_CREATE
private const val FOLDER_FLAGS = FOLDER_FLAGS_BASIC or MUTATION_FLAGS
private const val FILE_FLAGS = MUTATION_FLAGS

const val COLUMN_PARENT_UUID = "internxt_parent_uuid"

fun folderRow(folder: DriveFolder): Map<String, Any?> = mapOf(
Document.COLUMN_DOCUMENT_ID to DocumentId.encodeFolder(folder.uuid),
Document.COLUMN_MIME_TYPE to Document.MIME_TYPE_DIR,
Document.COLUMN_DISPLAY_NAME to folder.plainName,
Document.COLUMN_LAST_MODIFIED to parseIsoToMillis(folder.updatedAt),
Document.COLUMN_FLAGS to FOLDER_FLAGS,
Document.COLUMN_SIZE to null,
COLUMN_PARENT_UUID to folder.parentUuid,
)

fun folderRow(uuid: String, displayName: String, lastModified: Long? = null): Map<String, Any?> = mapOf(
Document.COLUMN_DOCUMENT_ID to DocumentId.encodeFolder(uuid),
Document.COLUMN_MIME_TYPE to Document.MIME_TYPE_DIR,
Document.COLUMN_DISPLAY_NAME to displayName,
Document.COLUMN_LAST_MODIFIED to lastModified,
Document.COLUMN_FLAGS to FOLDER_FLAGS_BASIC,
Document.COLUMN_SIZE to null,
)

fun fileRow(file: DriveFile): Map<String, Any?> = mapOf(
Document.COLUMN_DOCUMENT_ID to DocumentId.encodeFile(file.uuid),
Document.COLUMN_MIME_TYPE to MimeTypes.fromExtension(DocumentNaming.extensionOf(file.plainName, file.type)),
Document.COLUMN_DISPLAY_NAME to DocumentNaming.joinNameType(file.plainName, file.type),
Document.COLUMN_LAST_MODIFIED to parseIsoToMillis(file.updatedAt),
Document.COLUMN_FLAGS to FILE_FLAGS,
Document.COLUMN_SIZE to file.size,
COLUMN_PARENT_UUID to file.folderUuid,
)

internal fun parseIsoToMillis(iso: String?): Long? {
if (iso.isNullOrBlank()) return null
return try {
Instant.parse(iso).toEpochMilli()
} catch (_: DateTimeParseException) {
null
}
}
}
Loading
Loading