Use dependency injection for most singletons

This commit is contained in:
MM20
2021-10-10 12:15:54 +02:00
parent 023bb2cbb1
commit 087d4fd455
144 changed files with 1789 additions and 1277 deletions
@@ -0,0 +1,291 @@
package de.mm20.launcher2.files
import android.content.Context
import android.provider.MediaStore
import androidx.core.database.getStringOrNull
import de.mm20.launcher2.ktx.jsonObjectOf
import de.mm20.launcher2.permissions.PermissionsManager
import de.mm20.launcher2.search.SearchableDeserializer
import de.mm20.launcher2.search.SearchableSerializer
import de.mm20.launcher2.search.data.*
import org.json.JSONObject
class FileSerializer : SearchableSerializer {
override fun serialize(searchable: Searchable): String {
searchable as File
return jsonObjectOf(
"id" to searchable.id
).toString()
}
override val typePrefix: String
get() = "file"
}
class FileDeserializer(
val context: Context
) : SearchableDeserializer {
override fun deserialize(serialized: String): Searchable? {
if (!PermissionsManager.checkPermission(
context,
PermissionsManager.EXTERNAL_STORAGE
)
) return null
val json = JSONObject(serialized)
val uri = MediaStore.Files.getContentUri("external")
val proj = arrayOf(
MediaStore.Files.FileColumns._ID,
MediaStore.Files.FileColumns.SIZE,
MediaStore.Files.FileColumns.DATA,
MediaStore.Files.FileColumns.MIME_TYPE
)
val sel = "${MediaStore.Files.FileColumns._ID} = ?"
val selArgs = arrayOf(json.getLong("id").toString())
val cursor = context.contentResolver.query(uri, proj, sel, selArgs, null) ?: return null
if (cursor.moveToNext()) {
val path = cursor.getString(2)
if (!java.io.File(path).exists()) return null
val directory = java.io.File(path).isDirectory
val id = cursor.getLong(0)
val mimeType = cursor.getStringOrNull(3)
?: if (directory) "inode/directory" else File.getMimetypeByFileExtension(
path.substringAfterLast(
'.'
)
)
val size = cursor.getLong(1)
cursor.close()
return File(
path = path,
mimeType = mimeType,
size = size,
isDirectory = directory,
id = id,
metaData = File.getMetaData(context, mimeType, path)
)
}
cursor.close()
return null
}
}
class GDriveFileSerializer : SearchableSerializer {
override fun serialize(searchable: Searchable): String {
searchable as GDriveFile
return jsonObjectOf(
"id" to searchable.fileId,
"label" to searchable.label,
"path" to searchable.path,
"mimeType" to searchable.mimeType,
"size" to searchable.size,
"directory" to searchable.isDirectory,
"color" to searchable.directoryColor,
"uri" to searchable.viewUri
).apply {
for ((k, v) in searchable.metaData) {
put(
when (k) {
R.string.file_meta_owner -> "owner"
R.string.file_meta_dimensions -> "dimensions"
else -> "other"
}, v
)
}
}.toString()
}
override val typePrefix: String
get() = "gdrive"
}
class GDriveFileDeserializer : SearchableDeserializer {
override fun deserialize(serialized: String): Searchable? {
val json = JSONObject(serialized)
val id = json.getString("id")
val label = json.getString("label")
val path = json.getString("path")
val mimeType = json.getString("mimeType")
val size = json.getLong("size")
val directory = json.getBoolean("directory")
val color = json.optString("color")
val uri = json.getString("uri")
val owner = json.optString("owner")
val dimensions = json.optString("dimensions")
val metaData = mutableListOf<Pair<Int, String>>()
owner.takeIf { it.isNotEmpty() }?.let { metaData.add(R.string.file_meta_owner to it) }
dimensions.takeIf { it.isNotEmpty() }
?.let { metaData.add(R.string.file_meta_dimensions to it) }
return GDriveFile(
fileId = id,
label = label,
path = path,
mimeType = mimeType,
size = size,
directoryColor = color,
isDirectory = directory,
viewUri = uri,
metaData = metaData
)
}
}
class OneDriveFileSerializer : SearchableSerializer {
override fun serialize(searchable: Searchable): String {
searchable as OneDriveFile
return jsonObjectOf(
"id" to searchable.fileId,
"label" to searchable.label,
"mimeType" to searchable.mimeType,
"size" to searchable.size,
"directory" to searchable.isDirectory,
"webUrl" to searchable.webUrl
).apply {
for ((k, v) in searchable.metaData) {
put(
when (k) {
R.string.file_meta_owner -> "owner"
R.string.file_meta_dimensions -> "dimensions"
else -> "other"
}, v
)
}
}.toString()
}
override val typePrefix: String
get() = "onedrive"
}
class OneDriveFileDeserializer : SearchableDeserializer {
override fun deserialize(serialized: String): Searchable? {
val json = JSONObject(serialized)
val fileId = json.getString("id")
val label = json.getString("label")
val mimeType = json.getString("mimeType")
val size = json.getLong("size")
val isDirectory = json.getBoolean("directory")
val webUrl = json.getString("webUrl")
val owner = json.optString("owner")
val dimensions = json.optString("dimensions")
val metaData = mutableListOf<Pair<Int, String>>()
owner.takeIf { it.isNotEmpty() }?.let { metaData.add(R.string.file_meta_owner to it) }
dimensions.takeIf { it.isNotEmpty() }
?.let { metaData.add(R.string.file_meta_dimensions to it) }
return OneDriveFile(
fileId = fileId,
label = label,
path = "",
mimeType = mimeType,
size = size,
isDirectory = isDirectory,
metaData = metaData,
webUrl = webUrl
)
}
}
class NextcloudFileSerializer : SearchableSerializer {
override fun serialize(searchable: Searchable): String {
searchable as NextcloudFile
return jsonObjectOf(
"id" to searchable.id,
"label" to searchable.label,
"path" to searchable.path,
"mimeType" to searchable.mimeType,
"size" to searchable.size,
"isDirectory" to searchable.isDirectory,
"server" to searchable.server
).apply {
for ((k, v) in searchable.metaData) {
put(
when (k) {
R.string.file_meta_owner -> "owner"
else -> "other"
}, v
)
}
}.toString()
}
override val typePrefix: String
get() = "nextcloud"
}
class NextcloudFileDeserializer : SearchableDeserializer {
override fun deserialize(serialized: String): Searchable? {
val json = JSONObject(serialized)
val id = json.getLong("id")
val label = json.getString("label")
val path = json.getString("path")
val mimeType = json.getString("mimeType")
val size = json.getLong("size")
val isDirectory = json.getBoolean("isDirectory")
val server = json.getString("server")
val owner = json.optString("owner").takeIf { it.isNotEmpty() }
return NextcloudFile(
fileId = id,
label = label,
path = path,
mimeType = mimeType,
size = size,
isDirectory = isDirectory,
server = server,
metaData = owner?.let { listOf(R.string.file_meta_owner to it) } ?: emptyList()
)
}
}
class OwncloudFileSerializer : SearchableSerializer {
override fun serialize(searchable: Searchable): String {
searchable as OwncloudFile
return jsonObjectOf(
"id" to searchable.id,
"label" to searchable.label,
"path" to searchable.path,
"mimeType" to searchable.mimeType,
"size" to searchable.size,
"isDirectory" to searchable.isDirectory,
"server" to searchable.server
).apply {
for ((k, v) in searchable.metaData) {
put(
when (k) {
R.string.file_meta_owner -> "owner"
else -> "other"
}, v
)
}
}.toString()
}
override val typePrefix: String
get() = "owncloud"
}
class OwncloudFileDeserializer : SearchableDeserializer {
override fun deserialize(serialized: String): Searchable? {
val json = JSONObject(serialized)
val id = json.getLong("id")
val label = json.getString("label")
val path = json.getString("path")
val mimeType = json.getString("mimeType")
val size = json.getLong("size")
val isDirectory = json.getBoolean("isDirectory")
val server = json.getString("server")
val owner = json.optString("owner").takeIf { it.isNotEmpty() }
return OwncloudFile(
fileId = id,
label = label,
path = path,
mimeType = mimeType,
size = size,
isDirectory = isDirectory,
server = server,
metaData = owner?.let { listOf(R.string.file_meta_owner to it) } ?: emptyList()
)
}
}
@@ -10,12 +10,15 @@ import de.mm20.launcher2.search.BaseSearchableRepository
import de.mm20.launcher2.search.data.*
import kotlinx.coroutines.*
class FilesRepository private constructor(val context: Context) : BaseSearchableRepository() {
class FilesRepository(
val context: Context,
hiddenItemsRepository: HiddenItemsRepository
) : BaseSearchableRepository() {
val files = MediatorLiveData<List<File>?>()
private val allFiles = MutableLiveData<List<File>?>(emptyList())
private val hiddenItemKeys = HiddenItemsRepository.getInstance(context).hiddenItemsKeys
private val hiddenItemKeys = hiddenItemsRepository.hiddenItemsKeys
private val nextcloudClient by lazy {
NextcloudApiHelper(context)
@@ -46,10 +49,10 @@ class FilesRepository private constructor(val context: Context) : BaseSearchable
val cloudFiles = withContext(Dispatchers.IO) {
delay(300)
listOf(
async { OneDriveFile.search(context, query) },
async { GDriveFile.search(context, query) },
async { NextcloudFile.search(context, query, nextcloudClient) },
async { OwncloudFile.search(context, query, owncloudClient) }
async { OneDriveFile.search(context, query) },
async { GDriveFile.search(context, query) },
async { NextcloudFile.search(context, query, nextcloudClient) },
async { OwncloudFile.search(context, query, owncloudClient) }
).awaitAll().flatten()
}
yield()
@@ -59,12 +62,4 @@ class FilesRepository private constructor(val context: Context) : BaseSearchable
fun removeFile(file: File) {
allFiles.value = allFiles.value?.filter { it != file }
}
companion object {
private lateinit var instance: FilesRepository
fun getInstance(context: Context): FilesRepository {
if (!::instance.isInitialized) instance = FilesRepository(context.applicationContext)
return instance
}
}
}
@@ -1,16 +1,16 @@
package de.mm20.launcher2.files
import android.app.Application
import androidx.lifecycle.AndroidViewModel
import androidx.lifecycle.ViewModel
import de.mm20.launcher2.search.data.File
class FilesViewModel(app: Application): AndroidViewModel(app) {
class FilesViewModel(
private val filesRepository: FilesRepository
): ViewModel() {
private val repository = FilesRepository.getInstance(app)
val files = repository.files
val files = filesRepository.files
fun removeFile(file: File) {
repository.removeFile(file)
filesRepository.removeFile(file)
}
}
@@ -0,0 +1,10 @@
package de.mm20.launcher2.files
import org.koin.android.ext.koin.androidContext
import org.koin.androidx.viewmodel.dsl.viewModel
import org.koin.dsl.module
val filesModule = module {
single { FilesRepository(androidContext(), get()) }
viewModel { FilesViewModel(get()) }
}
@@ -158,12 +158,6 @@ open class File(
.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
override fun serialize(): String {
return jsonObjectOf(
"id" to id
).toString()
}
fun getFileType(context: Context): String {
if (isDirectory) return context.getString(R.string.file_type_directory)
val resource = when (mimeType) {
@@ -261,7 +255,7 @@ open class File(
return results.sortedBy { it }
}
private fun getMimetypeByFileExtension(extension: String): String {
internal fun getMimetypeByFileExtension(extension: String): String {
return when (extension) {
"apk" -> "application/vnd.android.package-archive"
"zip" -> "application/zip"
@@ -287,7 +281,7 @@ open class File(
}
private fun getMetaData(context: Context, mimeType: String, path: String): List<Pair<Int, String>> {
internal fun getMetaData(context: Context, mimeType: String, path: String): List<Pair<Int, String>> {
val metaData = mutableListOf<Pair<Int, String>>()
when {
mimeType.startsWith("audio/") -> {
@@ -365,37 +359,5 @@ open class File(
}
return metaData
}
fun deserialize(context: Context, serialized: String): File? {
if (!PermissionsManager.checkPermission(context, PermissionsManager.EXTERNAL_STORAGE)) return null
val json = JSONObject(serialized)
val uri = MediaStore.Files.getContentUri("external")
val proj = arrayOf(MediaStore.Files.FileColumns._ID,
MediaStore.Files.FileColumns.SIZE,
MediaStore.Files.FileColumns.DATA,
MediaStore.Files.FileColumns.MIME_TYPE)
val sel = "${MediaStore.Files.FileColumns._ID} = ?"
val selArgs = arrayOf(json.getLong("id").toString())
val cursor = context.contentResolver.query(uri, proj, sel, selArgs, null) ?: return null
if (cursor.moveToNext()) {
val path = cursor.getString(2)
if (!JavaIOFile(path).exists()) return null
val directory = JavaIOFile(path).isDirectory
val id = cursor.getLong(0)
val mimeType = cursor.getStringOrNull(3)
?: if (directory) "inode/directory" else getMimetypeByFileExtension(path.substringAfterLast('.'))
val size = cursor.getLong(1)
cursor.close()
return File(
path = path,
mimeType = mimeType,
size = size,
isDirectory = directory,
id = id,
metaData = getMetaData(context, mimeType, path))
}
cursor.close()
return null
}
}
}
@@ -8,20 +8,18 @@ import de.mm20.launcher2.gservices.DriveFileMeta
import de.mm20.launcher2.gservices.GoogleApiHelper
import de.mm20.launcher2.helper.NetworkUtils
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.ktx.jsonObjectOf
import de.mm20.launcher2.preferences.LauncherPreferences
import org.json.JSONObject
class GDriveFile(
val fileId: String,
override val label: String,
path: String,
mimeType: String,
size: Long,
isDirectory: Boolean,
metaData: List<Pair<Int, String>>,
val directoryColor: String?,
val viewUri: String
val fileId: String,
override val label: String,
path: String,
mimeType: String,
size: Long,
isDirectory: Boolean,
metaData: List<Pair<Int, String>>,
val directoryColor: String?,
val viewUri: String
) : File(0, path, mimeType, size, isDirectory, metaData) {
override val key: String = "gdrive://$fileId"
@@ -29,27 +27,6 @@ class GDriveFile(
override val badgeKey: String
get() = "gdrive://"
override fun serialize(): String {
return jsonObjectOf(
"id" to fileId,
"label" to label,
"path" to path,
"mimeType" to mimeType,
"size" to size,
"directory" to isDirectory,
"color" to directoryColor,
"uri" to viewUri
).apply {
for ((k, v) in metaData) {
put(when (k) {
R.string.file_meta_owner -> "owner"
R.string.file_meta_dimensions -> "dimensions"
else -> "other"
}, v)
}
}.toString()
}
override val isStoredInCloud = true
override fun getLaunchIntent(context: Context): Intent? {
@@ -72,15 +49,15 @@ class GDriveFile(
val driveFiles = GoogleApiHelper.getInstance(context).queryGDriveFiles(query)
return driveFiles.map {
GDriveFile(
fileId = it.fileId,
label = it.label,
size = it.size,
mimeType = it.mimeType,
isDirectory = it.isDirectory,
path = "",
directoryColor = it.directoryColor,
viewUri = it.viewUri,
metaData = getMetadata(it.metadata)
fileId = it.fileId,
label = it.label,
size = it.size,
mimeType = it.mimeType,
isDirectory = it.isDirectory,
path = "",
directoryColor = it.directoryColor,
viewUri = it.viewUri,
metaData = getMetadata(it.metadata)
)
}.sorted()
}
@@ -94,33 +71,5 @@ class GDriveFile(
if (width != null && height != null) metaData.add(R.string.file_meta_dimensions to "${width}x$height")
return metaData
}
fun deserialize(serialized: String): GDriveFile? {
val json = JSONObject(serialized)
val id = json.getString("id")
val label = json.getString("label")
val path = json.getString("path")
val mimeType = json.getString("mimeType")
val size = json.getLong("size")
val directory = json.getBoolean("directory")
val color = json.optString("color")
val uri = json.getString("uri")
val owner = json.optString("owner")
val dimensions = json.optString("dimensions")
val metaData = mutableListOf<Pair<Int, String>>()
owner.takeIf { it.isNotEmpty() }?.let { metaData.add(R.string.file_meta_owner to it) }
dimensions.takeIf { it.isNotEmpty() }?.let { metaData.add(R.string.file_meta_dimensions to it) }
return GDriveFile(
fileId = id,
label = label,
path = path,
mimeType = mimeType,
size = size,
directoryColor = color,
isDirectory = directory,
viewUri = uri,
metaData = metaData
)
}
}
}
@@ -34,25 +34,6 @@ class NextcloudFile(
}
}
override fun serialize(): String {
return jsonObjectOf(
"id" to id,
"label" to label,
"path" to path,
"mimeType" to mimeType,
"size" to size,
"isDirectory" to isDirectory,
"server" to server
).apply {
for ((k, v) in metaData) {
put(when (k) {
R.string.file_meta_owner -> "owner"
else -> "other"
}, v)
}
}.toString()
}
companion object {
suspend fun search(context: Context, query: String, nextcloudClient: NextcloudApiHelper) : List<NextcloudFile> {
if (!LauncherPreferences.instance.searchNextcloud) return emptyList()
@@ -73,29 +54,5 @@ class NextcloudFile(
}
}
fun deserialize(serialized: String): NextcloudFile? {
val json = JSONObject(serialized)
val id = json.getLong("id")
val label = json.getString("label")
val path = json.getString("path")
val mimeType = json.getString("mimeType")
val size = json.getLong("size")
val isDirectory = json.getBoolean("isDirectory")
val server = json.getString("server")
val owner = json.optString("owner").takeIf { it.isNotEmpty() }
return NextcloudFile(
fileId = id,
label = label,
path = path,
mimeType = mimeType,
size = size,
isDirectory = isDirectory,
server = server,
metaData = owner?.let { listOf(R.string.file_meta_owner to it) } ?: emptyList()
)
}
}
}
@@ -6,10 +6,8 @@ import android.net.Uri
import de.mm20.launcher2.msservices.DriveItem
import de.mm20.launcher2.files.R
import de.mm20.launcher2.msservices.MicrosoftGraphApiHelper
import de.mm20.launcher2.ktx.jsonObjectOf
import de.mm20.launcher2.icons.LauncherIcon
import de.mm20.launcher2.preferences.LauncherPreferences
import org.json.JSONObject
class OneDriveFile(
val fileId: String,
@@ -39,25 +37,6 @@ class OneDriveFile(
}
}
override fun serialize(): String {
return jsonObjectOf(
"id" to fileId,
"label" to label,
"mimeType" to mimeType,
"size" to size,
"directory" to isDirectory,
"webUrl" to webUrl
).apply {
for ((k, v) in metaData) {
put(when (k) {
R.string.file_meta_owner -> "owner"
R.string.file_meta_dimensions -> "dimensions"
else -> "other"
}, v)
}
}.toString()
}
companion object {
suspend fun search(context: Context, query: String): List<File> {
if (query.length < 4) return emptyList()
@@ -79,31 +58,6 @@ class OneDriveFile(
return files.sorted()
}
fun deserialize(serialized: String): OneDriveFile? {
val json = JSONObject(serialized)
val fileId = json.getString("id")
val label = json.getString("label")
val mimeType = json.getString("mimeType")
val size = json.getLong("size")
val isDirectory = json.getBoolean("directory")
val webUrl = json.getString("webUrl")
val owner = json.optString("owner")
val dimensions = json.optString("dimensions")
val metaData = mutableListOf<Pair<Int, String>>()
owner.takeIf { it.isNotEmpty() }?.let { metaData.add(R.string.file_meta_owner to it) }
dimensions.takeIf { it.isNotEmpty() }?.let { metaData.add(R.string.file_meta_dimensions to it) }
return OneDriveFile(
fileId = fileId,
label = label,
path = "",
mimeType = mimeType,
size = size,
isDirectory = isDirectory,
metaData = metaData,
webUrl = webUrl
)
}
private fun getMetaData(driveItem: DriveItem): List<Pair<Int, String>> {
val metaData = mutableListOf<Pair<Int, String>>()
driveItem.meta.owner?.let {
@@ -34,25 +34,6 @@ class OwncloudFile(
}
}
override fun serialize(): String {
return jsonObjectOf(
"id" to id,
"label" to label,
"path" to path,
"mimeType" to mimeType,
"size" to size,
"isDirectory" to isDirectory,
"server" to server
).apply {
for ((k, v) in metaData) {
put(when (k) {
R.string.file_meta_owner -> "owner"
else -> "other"
}, v)
}
}.toString()
}
companion object {
suspend fun search(context: Context, query: String, owncloudClient: OwncloudClient) : List<OwncloudFile> {
if (!LauncherPreferences.instance.searchOwncloud) return emptyList()
@@ -73,29 +54,5 @@ class OwncloudFile(
}
}
fun deserialize(serialized: String): OwncloudFile? {
val json = JSONObject(serialized)
val id = json.getLong("id")
val label = json.getString("label")
val path = json.getString("path")
val mimeType = json.getString("mimeType")
val size = json.getLong("size")
val isDirectory = json.getBoolean("isDirectory")
val server = json.getString("server")
val owner = json.optString("owner").takeIf { it.isNotEmpty() }
return OwncloudFile(
fileId = id,
label = label,
path = path,
mimeType = mimeType,
size = size,
isDirectory = isDirectory,
server = server,
metaData = owner?.let { listOf(R.string.file_meta_owner to it) } ?: emptyList()
)
}
}
}