Annual refactor
This commit is contained in:
@@ -1,8 +1,13 @@
|
||||
package de.mm20.launcher2.websites
|
||||
|
||||
import de.mm20.launcher2.search.SearchableDeserializer
|
||||
import de.mm20.launcher2.search.SearchableRepository
|
||||
import de.mm20.launcher2.search.Website
|
||||
import org.koin.android.ext.koin.androidContext
|
||||
import org.koin.core.qualifier.named
|
||||
import org.koin.dsl.module
|
||||
|
||||
val websitesModule = module {
|
||||
single<WebsiteRepository> { WebsiteRepositoryImpl(androidContext()) }
|
||||
single<SearchableRepository<Website>>(named<Website>()) { WebsiteRepository(androidContext()) }
|
||||
factory<SearchableDeserializer>(named(WebsiteImpl.Domain)) { WebsiteDeserializer() }
|
||||
}
|
||||
+19
-13
@@ -1,4 +1,4 @@
|
||||
package de.mm20.launcher2.search.data
|
||||
package de.mm20.launcher2.websites
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
@@ -9,19 +9,19 @@ import coil.imageLoader
|
||||
import coil.request.ImageRequest
|
||||
import de.mm20.launcher2.icons.*
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.websites.R
|
||||
import de.mm20.launcher2.search.SearchableSerializer
|
||||
import de.mm20.launcher2.search.Website
|
||||
import java.util.concurrent.ExecutionException
|
||||
|
||||
data class Website(
|
||||
internal data class WebsiteImpl(
|
||||
override val label: String,
|
||||
val url: String,
|
||||
val description: String,
|
||||
val image: String,
|
||||
val favicon: String,
|
||||
val color: Int,
|
||||
override val url: String,
|
||||
override val description: String,
|
||||
override val imageUrl: String,
|
||||
override val faviconUrl: String,
|
||||
override val color: Int,
|
||||
override val labelOverride: String? = null,
|
||||
) : SavableSearchable {
|
||||
) : Website {
|
||||
|
||||
override val domain: String = Domain
|
||||
|
||||
@@ -38,10 +38,10 @@ data class Website(
|
||||
size: Int,
|
||||
themed: Boolean,
|
||||
): LauncherIcon? {
|
||||
if (favicon.isEmpty()) return null
|
||||
if (faviconUrl.isEmpty()) return null
|
||||
try {
|
||||
val request = ImageRequest.Builder(context)
|
||||
.data(favicon)
|
||||
.data(faviconUrl)
|
||||
.size(size)
|
||||
.allowHardware(false)
|
||||
.build()
|
||||
@@ -90,7 +90,9 @@ data class Website(
|
||||
return context.tryStartActivity(getLaunchIntent(), options)
|
||||
}
|
||||
|
||||
fun share(context: Context) {
|
||||
override val canShare: Boolean = true
|
||||
|
||||
override fun share(context: Context) {
|
||||
val shareIntent = Intent(Intent.ACTION_SEND)
|
||||
shareIntent.putExtra(
|
||||
Intent.EXTRA_TEXT,
|
||||
@@ -100,6 +102,10 @@ data class Website(
|
||||
context.startActivity(Intent.createChooser(shareIntent, null))
|
||||
}
|
||||
|
||||
override fun getSerializer(): SearchableSerializer {
|
||||
return WebsiteSerializer()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val Domain = "web"
|
||||
}
|
||||
@@ -2,8 +2,12 @@ package de.mm20.launcher2.websites
|
||||
|
||||
import android.content.Context
|
||||
import android.webkit.URLUtil
|
||||
import androidx.compose.runtime.Immutable
|
||||
import androidx.core.graphics.toColorInt
|
||||
import de.mm20.launcher2.search.data.Website
|
||||
import de.mm20.launcher2.search.SearchableRepository
|
||||
import de.mm20.launcher2.search.Website
|
||||
import kotlinx.collections.immutable.ImmutableList
|
||||
import kotlinx.collections.immutable.persistentListOf
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.Flow
|
||||
import kotlinx.coroutines.flow.channelFlow
|
||||
@@ -20,11 +24,8 @@ import java.net.URISyntaxException
|
||||
import java.net.URL
|
||||
import java.util.concurrent.TimeUnit
|
||||
|
||||
interface WebsiteRepository {
|
||||
fun search(query: String): Flow<Website?>
|
||||
}
|
||||
|
||||
internal class WebsiteRepositoryImpl(val context: Context) : WebsiteRepository, KoinComponent {
|
||||
internal class WebsiteRepository(val context: Context) : SearchableRepository<Website> {
|
||||
|
||||
private val httpClient = OkHttpClient
|
||||
.Builder()
|
||||
@@ -33,16 +34,17 @@ internal class WebsiteRepositoryImpl(val context: Context) : WebsiteRepository,
|
||||
.writeTimeout(1000, TimeUnit.MILLISECONDS)
|
||||
.build()
|
||||
|
||||
override fun search(query: String): Flow<Website?> = channelFlow {
|
||||
send(null)
|
||||
override fun search(query: String): Flow<ImmutableList<Website>> = channelFlow {
|
||||
send(persistentListOf())
|
||||
withContext(Dispatchers.IO) {
|
||||
httpClient.dispatcher.cancelAll()
|
||||
}
|
||||
if (query.isBlank()) return@channelFlow
|
||||
|
||||
val website = queryWebsite(query)
|
||||
send(website)
|
||||
|
||||
website?.let {
|
||||
send(persistentListOf(it))
|
||||
}
|
||||
}
|
||||
|
||||
private suspend fun queryWebsite(query: String): Website? {
|
||||
@@ -84,12 +86,12 @@ internal class WebsiteRepositoryImpl(val context: Context) : WebsiteRepository,
|
||||
doc.head().select("link[href~=.*\\.(ico|png)]").attr("href")
|
||||
if (favicon.isNotBlank()) favicon = resolveUrl(response.request.url, favicon)
|
||||
if (image.isNotBlank()) image = resolveUrl(response.request.url, image)
|
||||
return@withContext Website(
|
||||
return@withContext WebsiteImpl(
|
||||
label = title,
|
||||
url = url,
|
||||
description = description,
|
||||
image = image,
|
||||
favicon = favicon,
|
||||
imageUrl = image,
|
||||
faviconUrl = favicon,
|
||||
color = color
|
||||
)
|
||||
} catch (e: IOException) {
|
||||
|
||||
@@ -4,18 +4,17 @@ import de.mm20.launcher2.ktx.jsonObjectOf
|
||||
import de.mm20.launcher2.search.SavableSearchable
|
||||
import de.mm20.launcher2.search.SearchableDeserializer
|
||||
import de.mm20.launcher2.search.SearchableSerializer
|
||||
import de.mm20.launcher2.search.data.Website
|
||||
import org.json.JSONObject
|
||||
|
||||
class WebsiteSerializer : SearchableSerializer {
|
||||
override fun serialize(searchable: SavableSearchable): String {
|
||||
searchable as Website
|
||||
searchable as WebsiteImpl
|
||||
return jsonObjectOf(
|
||||
"label" to searchable.label,
|
||||
"url" to searchable.url,
|
||||
"description" to searchable.description,
|
||||
"image" to searchable.image,
|
||||
"favicon" to searchable.favicon,
|
||||
"image" to searchable.imageUrl,
|
||||
"favicon" to searchable.faviconUrl,
|
||||
"color" to searchable.color
|
||||
).toString()
|
||||
}
|
||||
@@ -25,12 +24,12 @@ class WebsiteSerializer : SearchableSerializer {
|
||||
}
|
||||
|
||||
class WebsiteDeserializer: SearchableDeserializer {
|
||||
override fun deserialize(serialized: String): SavableSearchable? {
|
||||
override suspend fun deserialize(serialized: String): SavableSearchable? {
|
||||
val json = JSONObject(serialized)
|
||||
return Website(
|
||||
return WebsiteImpl(
|
||||
label = json.getString("label"),
|
||||
favicon = json.getString("favicon"),
|
||||
image = json.getString("image"),
|
||||
faviconUrl = json.getString("favicon"),
|
||||
imageUrl = json.getString("image"),
|
||||
description = json.getString("description"),
|
||||
url = json.getString("url"),
|
||||
color = json.getInt("color")
|
||||
|
||||
Reference in New Issue
Block a user