v0.6.5
This commit is contained in:
@@ -1,276 +0,0 @@
|
|||||||
package com.looker.droidify
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.app.Application
|
|
||||||
import android.content.BroadcastReceiver
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.IntentFilter
|
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
|
||||||
import androidx.hilt.work.HiltWorkerFactory
|
|
||||||
import androidx.work.Configuration
|
|
||||||
import androidx.work.NetworkType
|
|
||||||
import coil.ImageLoader
|
|
||||||
import coil.ImageLoaderFactory
|
|
||||||
import coil.disk.DiskCache
|
|
||||||
import coil.memory.MemoryCache
|
|
||||||
import com.looker.core.common.Constants
|
|
||||||
import com.looker.core.common.cache.Cache
|
|
||||||
import com.looker.core.common.extension.getInstalledPackagesCompat
|
|
||||||
import com.looker.core.common.extension.jobScheduler
|
|
||||||
import com.looker.core.common.log
|
|
||||||
import com.looker.core.datastore.SettingsRepository
|
|
||||||
import com.looker.core.datastore.get
|
|
||||||
import com.looker.core.datastore.model.AutoSync
|
|
||||||
import com.looker.core.datastore.model.InstallerType
|
|
||||||
import com.looker.core.datastore.model.ProxyPreference
|
|
||||||
import com.looker.core.datastore.model.ProxyType
|
|
||||||
import com.looker.droidify.content.ProductPreferences
|
|
||||||
import com.looker.droidify.database.Database
|
|
||||||
import com.looker.droidify.index.RepositoryUpdater
|
|
||||||
import com.looker.droidify.receivers.InstalledAppReceiver
|
|
||||||
import com.looker.droidify.service.Connection
|
|
||||||
import com.looker.droidify.service.SyncService
|
|
||||||
import com.looker.droidify.sync.SyncPreference
|
|
||||||
import com.looker.droidify.sync.toJobNetworkType
|
|
||||||
import com.looker.droidify.utility.extension.toInstalledItem
|
|
||||||
import com.looker.droidify.work.CleanUpWorker
|
|
||||||
import com.looker.installer.InstallManager
|
|
||||||
import com.looker.installer.installers.root.RootPermissionHandler
|
|
||||||
import com.looker.installer.installers.shizuku.ShizukuPermissionHandler
|
|
||||||
import com.looker.network.Downloader
|
|
||||||
import dagger.hilt.android.HiltAndroidApp
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.Dispatchers
|
|
||||||
import kotlinx.coroutines.SupervisorJob
|
|
||||||
import kotlinx.coroutines.cancel
|
|
||||||
import kotlinx.coroutines.flow.collectIndexed
|
|
||||||
import kotlinx.coroutines.flow.drop
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import java.net.InetSocketAddress
|
|
||||||
import java.net.Proxy
|
|
||||||
import javax.inject.Inject
|
|
||||||
import kotlin.time.Duration.Companion.INFINITE
|
|
||||||
import kotlin.time.Duration.Companion.hours
|
|
||||||
import com.looker.core.common.R as CommonR
|
|
||||||
|
|
||||||
@HiltAndroidApp
|
|
||||||
class MainApplication : Application(), ImageLoaderFactory, Configuration.Provider {
|
|
||||||
|
|
||||||
private val parentJob = SupervisorJob()
|
|
||||||
private val appScope = CoroutineScope(Dispatchers.Default + parentJob)
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var settingsRepository: SettingsRepository
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var installer: InstallManager
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var downloader: Downloader
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var shizukuPermissionHandler: ShizukuPermissionHandler
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var rootPermissionHandler: RootPermissionHandler
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var workerFactory: HiltWorkerFactory
|
|
||||||
|
|
||||||
override fun onCreate() {
|
|
||||||
super.onCreate()
|
|
||||||
|
|
||||||
val databaseUpdated = Database.init(this)
|
|
||||||
ProductPreferences.init(this, appScope)
|
|
||||||
RepositoryUpdater.init(appScope, downloader)
|
|
||||||
listenApplications()
|
|
||||||
checkLanguage()
|
|
||||||
updatePreference()
|
|
||||||
setupInstaller()
|
|
||||||
|
|
||||||
if (databaseUpdated) forceSyncAll()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTerminate() {
|
|
||||||
super.onTerminate()
|
|
||||||
appScope.cancel("Application Terminated")
|
|
||||||
installer.close()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupInstaller() {
|
|
||||||
appScope.launch {
|
|
||||||
launch {
|
|
||||||
settingsRepository.get { installerType }.collect {
|
|
||||||
if (it == InstallerType.SHIZUKU) handleShizukuInstaller()
|
|
||||||
if (it == InstallerType.ROOT) {
|
|
||||||
if (!rootPermissionHandler.isGranted) {
|
|
||||||
settingsRepository.setInstallerType(InstallerType.Default)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
installer()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun CoroutineScope.handleShizukuInstaller() = launch {
|
|
||||||
shizukuPermissionHandler.state.collect { (isGranted, isAlive, _) ->
|
|
||||||
if (isAlive && isGranted) {
|
|
||||||
settingsRepository.setInstallerType(InstallerType.SHIZUKU)
|
|
||||||
return@collect
|
|
||||||
}
|
|
||||||
if (isAlive) {
|
|
||||||
settingsRepository.setInstallerType(InstallerType.Default)
|
|
||||||
shizukuPermissionHandler.requestPermission()
|
|
||||||
return@collect
|
|
||||||
}
|
|
||||||
settingsRepository.setInstallerType(InstallerType.Default)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun listenApplications() {
|
|
||||||
registerReceiver(
|
|
||||||
InstalledAppReceiver(packageManager),
|
|
||||||
IntentFilter().apply {
|
|
||||||
addAction(Intent.ACTION_PACKAGE_ADDED)
|
|
||||||
addAction(Intent.ACTION_PACKAGE_REMOVED)
|
|
||||||
addDataScheme("package")
|
|
||||||
}
|
|
||||||
)
|
|
||||||
val installedItems =
|
|
||||||
packageManager.getInstalledPackagesCompat()
|
|
||||||
?.map { it.toInstalledItem() }
|
|
||||||
?: return
|
|
||||||
Database.InstalledAdapter.putAll(installedItems)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun checkLanguage() {
|
|
||||||
appScope.launch {
|
|
||||||
val lastSetLanguage = settingsRepository.getInitial().language
|
|
||||||
val systemSetLanguage = AppCompatDelegate.getApplicationLocales().toLanguageTags()
|
|
||||||
if (systemSetLanguage != lastSetLanguage && lastSetLanguage != "system") {
|
|
||||||
settingsRepository.setLanguage(systemSetLanguage)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updatePreference() {
|
|
||||||
appScope.launch {
|
|
||||||
launch {
|
|
||||||
settingsRepository.get { unstableUpdate }.drop(1).collect {
|
|
||||||
forceSyncAll()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
launch {
|
|
||||||
settingsRepository.get { autoSync }.collectIndexed { index, syncMode ->
|
|
||||||
// Don't update sync job on initial collect
|
|
||||||
updateSyncJob(index > 0, syncMode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
launch {
|
|
||||||
settingsRepository.get { cleanUpInterval }.drop(1).collect {
|
|
||||||
if (it == INFINITE) {
|
|
||||||
CleanUpWorker.removeAllSchedules(applicationContext)
|
|
||||||
} else {
|
|
||||||
CleanUpWorker.scheduleCleanup(applicationContext, it)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
launch {
|
|
||||||
settingsRepository.get { proxy }.collect(::updateProxy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateProxy(proxyPreference: ProxyPreference) {
|
|
||||||
val type = proxyPreference.type
|
|
||||||
val host = proxyPreference.host
|
|
||||||
val port = proxyPreference.port
|
|
||||||
val socketAddress = when (type) {
|
|
||||||
ProxyType.DIRECT -> null
|
|
||||||
ProxyType.HTTP, ProxyType.SOCKS -> {
|
|
||||||
try {
|
|
||||||
InetSocketAddress.createUnresolved(host, port)
|
|
||||||
} catch (e: IllegalArgumentException) {
|
|
||||||
log(e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val androidProxyType = when (type) {
|
|
||||||
ProxyType.DIRECT -> Proxy.Type.DIRECT
|
|
||||||
ProxyType.HTTP -> Proxy.Type.HTTP
|
|
||||||
ProxyType.SOCKS -> Proxy.Type.SOCKS
|
|
||||||
}
|
|
||||||
val determinedProxy = socketAddress?.let { Proxy(androidProxyType, it) } ?: Proxy.NO_PROXY
|
|
||||||
downloader.setProxy(determinedProxy)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateSyncJob(force: Boolean, autoSync: AutoSync) {
|
|
||||||
if (autoSync == AutoSync.NEVER) {
|
|
||||||
jobScheduler?.cancel(Constants.JOB_ID_SYNC)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val jobScheduler = jobScheduler
|
|
||||||
val syncConditions = when (autoSync) {
|
|
||||||
AutoSync.ALWAYS -> SyncPreference(NetworkType.CONNECTED)
|
|
||||||
AutoSync.WIFI_ONLY -> SyncPreference(NetworkType.UNMETERED)
|
|
||||||
AutoSync.WIFI_PLUGGED_IN -> SyncPreference(NetworkType.UNMETERED, pluggedIn = true)
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
val isCompleted = jobScheduler?.allPendingJobs
|
|
||||||
?.any { it.id == Constants.JOB_ID_SYNC } == false
|
|
||||||
if ((force || isCompleted) && syncConditions != null) {
|
|
||||||
val period = 12.hours.inWholeMilliseconds
|
|
||||||
val job = SyncService.Job.create(
|
|
||||||
context = this,
|
|
||||||
periodMillis = period,
|
|
||||||
networkType = syncConditions.toJobNetworkType(),
|
|
||||||
isCharging = syncConditions.pluggedIn,
|
|
||||||
isBatteryLow = syncConditions.batteryNotLow
|
|
||||||
)
|
|
||||||
jobScheduler?.schedule(job)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun forceSyncAll() {
|
|
||||||
Database.RepositoryAdapter.getAll().forEach {
|
|
||||||
if (it.lastModified.isNotEmpty() || it.entityTag.isNotEmpty()) {
|
|
||||||
Database.RepositoryAdapter.put(it.copy(lastModified = "", entityTag = ""))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Connection(SyncService::class.java, onBind = { connection, binder ->
|
|
||||||
binder.sync(SyncService.SyncRequest.FORCE)
|
|
||||||
connection.unbind(this)
|
|
||||||
}).bind(this)
|
|
||||||
}
|
|
||||||
|
|
||||||
class BootReceiver : BroadcastReceiver() {
|
|
||||||
@SuppressLint("UnsafeProtectedBroadcastReceiver")
|
|
||||||
override fun onReceive(context: Context, intent: Intent) = Unit
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun newImageLoader(): ImageLoader {
|
|
||||||
val memoryCache = MemoryCache.Builder(this)
|
|
||||||
.maxSizePercent(0.25)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
val diskCache = DiskCache.Builder()
|
|
||||||
.directory(Cache.getImagesDir(this))
|
|
||||||
.maxSizePercent(0.05)
|
|
||||||
.build()
|
|
||||||
|
|
||||||
return ImageLoader.Builder(this)
|
|
||||||
.memoryCache(memoryCache)
|
|
||||||
.diskCache(diskCache)
|
|
||||||
.error(CommonR.drawable.ic_cannot_load)
|
|
||||||
.crossfade(350)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
override val workManagerConfiguration: Configuration
|
|
||||||
get() = Configuration.Builder()
|
|
||||||
.setWorkerFactory(workerFactory)
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
@@ -1,308 +0,0 @@
|
|||||||
package com.looker.droidify
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.os.Parcelable
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.FrameLayout
|
|
||||||
import androidx.activity.OnBackPressedCallback
|
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.appcompat.widget.Toolbar
|
|
||||||
import androidx.core.view.WindowCompat
|
|
||||||
import androidx.fragment.app.Fragment
|
|
||||||
import androidx.fragment.app.commit
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import com.looker.core.common.DeeplinkType
|
|
||||||
import com.looker.core.common.SdkCheck
|
|
||||||
import com.looker.core.common.deeplinkType
|
|
||||||
import com.looker.core.common.extension.homeAsUp
|
|
||||||
import com.looker.core.common.extension.inputManager
|
|
||||||
import com.looker.core.common.requestNotificationPermission
|
|
||||||
import com.looker.core.datastore.SettingsRepository
|
|
||||||
import com.looker.core.datastore.extension.getThemeRes
|
|
||||||
import com.looker.core.datastore.get
|
|
||||||
import com.looker.droidify.database.CursorOwner
|
|
||||||
import com.looker.droidify.ui.appDetail.AppDetailFragment
|
|
||||||
import com.looker.droidify.ui.favourites.FavouritesFragment
|
|
||||||
import com.looker.droidify.ui.repository.EditRepositoryFragment
|
|
||||||
import com.looker.droidify.ui.repository.RepositoriesFragment
|
|
||||||
import com.looker.droidify.ui.repository.RepositoryFragment
|
|
||||||
import com.looker.droidify.ui.settings.SettingsFragment
|
|
||||||
import com.looker.droidify.ui.tabsFragment.TabsFragment
|
|
||||||
import com.looker.installer.InstallManager
|
|
||||||
import com.looker.installer.model.installFrom
|
|
||||||
import dagger.hilt.EntryPoint
|
|
||||||
import dagger.hilt.InstallIn
|
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
|
||||||
import dagger.hilt.android.EntryPointAccessors
|
|
||||||
import dagger.hilt.components.SingletonComponent
|
|
||||||
import kotlinx.coroutines.flow.drop
|
|
||||||
import kotlinx.coroutines.flow.first
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
import kotlinx.coroutines.runBlocking
|
|
||||||
import kotlinx.parcelize.Parcelize
|
|
||||||
import javax.inject.Inject
|
|
||||||
|
|
||||||
@AndroidEntryPoint
|
|
||||||
abstract class ScreenActivity : AppCompatActivity() {
|
|
||||||
companion object {
|
|
||||||
private const val STATE_FRAGMENT_STACK = "fragmentStack"
|
|
||||||
}
|
|
||||||
|
|
||||||
sealed interface SpecialIntent {
|
|
||||||
data object Updates : SpecialIntent
|
|
||||||
class Install(val packageName: String?, val cacheFileName: String?) : SpecialIntent
|
|
||||||
}
|
|
||||||
|
|
||||||
private val notificationPermission =
|
|
||||||
registerForActivityResult(ActivityResultContracts.RequestPermission()) { }
|
|
||||||
|
|
||||||
@Inject
|
|
||||||
lateinit var installer: InstallManager
|
|
||||||
|
|
||||||
@Parcelize
|
|
||||||
private class FragmentStackItem(
|
|
||||||
val className: String, val arguments: Bundle?, val savedState: Fragment.SavedState?
|
|
||||||
) : Parcelable
|
|
||||||
|
|
||||||
lateinit var cursorOwner: CursorOwner
|
|
||||||
private set
|
|
||||||
|
|
||||||
private var onBackPressedCallback: OnBackPressedCallback? = null
|
|
||||||
|
|
||||||
private val fragmentStack = mutableListOf<FragmentStackItem>()
|
|
||||||
|
|
||||||
private val currentFragment: Fragment?
|
|
||||||
get() {
|
|
||||||
supportFragmentManager.executePendingTransactions()
|
|
||||||
return supportFragmentManager.findFragmentById(R.id.main_content)
|
|
||||||
}
|
|
||||||
|
|
||||||
@EntryPoint
|
|
||||||
@InstallIn(SingletonComponent::class)
|
|
||||||
interface CustomUserRepositoryInjector {
|
|
||||||
fun settingsRepository(): SettingsRepository
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun collectChange() {
|
|
||||||
val hiltEntryPoint = EntryPointAccessors.fromApplication(
|
|
||||||
this, CustomUserRepositoryInjector::class.java
|
|
||||||
)
|
|
||||||
val newSettings = hiltEntryPoint.settingsRepository().get { theme to dynamicTheme }
|
|
||||||
runBlocking {
|
|
||||||
val theme = newSettings.first()
|
|
||||||
setTheme(
|
|
||||||
resources.configuration.getThemeRes(
|
|
||||||
theme = theme.first, dynamicTheme = theme.second
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
lifecycleScope.launch {
|
|
||||||
newSettings.drop(1).collect { themeAndDynamic ->
|
|
||||||
setTheme(
|
|
||||||
resources.configuration.getThemeRes(
|
|
||||||
theme = themeAndDynamic.first, dynamicTheme = themeAndDynamic.second
|
|
||||||
)
|
|
||||||
)
|
|
||||||
recreate()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
collectChange()
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
val rootView = FrameLayout(this).apply { id = R.id.main_content }
|
|
||||||
addContentView(
|
|
||||||
rootView, ViewGroup.LayoutParams(
|
|
||||||
ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
requestNotificationPermission(request = notificationPermission::launch)
|
|
||||||
|
|
||||||
supportFragmentManager.addFragmentOnAttachListener { _, _ ->
|
|
||||||
hideKeyboard()
|
|
||||||
}
|
|
||||||
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
cursorOwner = CursorOwner()
|
|
||||||
supportFragmentManager.commit {
|
|
||||||
add(cursorOwner, CursorOwner::class.java.name)
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
cursorOwner =
|
|
||||||
supportFragmentManager.findFragmentByTag(CursorOwner::class.java.name) as CursorOwner
|
|
||||||
}
|
|
||||||
|
|
||||||
savedInstanceState?.getParcelableArrayList<FragmentStackItem>(STATE_FRAGMENT_STACK)
|
|
||||||
?.let { fragmentStack += it }
|
|
||||||
if (savedInstanceState == null) {
|
|
||||||
replaceFragment(TabsFragment(), null)
|
|
||||||
if ((intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
|
|
||||||
handleIntent(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (SdkCheck.isR) {
|
|
||||||
window.statusBarColor = resources.getColor(android.R.color.transparent, theme)
|
|
||||||
window.navigationBarColor = resources.getColor(android.R.color.transparent, theme)
|
|
||||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
|
||||||
}
|
|
||||||
backHandler()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onSaveInstanceState(outState: Bundle) {
|
|
||||||
super.onSaveInstanceState(outState)
|
|
||||||
outState.putParcelableArrayList(STATE_FRAGMENT_STACK, ArrayList(fragmentStack))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun backHandler() {
|
|
||||||
if (onBackPressedCallback == null) {
|
|
||||||
onBackPressedCallback = object : OnBackPressedCallback(enabled = false) {
|
|
||||||
override fun handleOnBackPressed() {
|
|
||||||
hideKeyboard()
|
|
||||||
popFragment()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
onBackPressedDispatcher.addCallback(
|
|
||||||
this,
|
|
||||||
onBackPressedCallback!!,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
onBackPressedCallback?.isEnabled = fragmentStack.isNotEmpty()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun replaceFragment(fragment: Fragment, open: Boolean?) {
|
|
||||||
if (open != null) {
|
|
||||||
currentFragment?.view?.translationZ =
|
|
||||||
(if (open) Int.MIN_VALUE else Int.MAX_VALUE).toFloat()
|
|
||||||
}
|
|
||||||
supportFragmentManager.commit {
|
|
||||||
if (open != null) {
|
|
||||||
setCustomAnimations(
|
|
||||||
if (open) R.animator.slide_in else 0,
|
|
||||||
if (open) R.animator.slide_in_keep else R.animator.slide_out
|
|
||||||
)
|
|
||||||
}
|
|
||||||
setReorderingAllowed(true)
|
|
||||||
replace(R.id.main_content, fragment)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun pushFragment(fragment: Fragment) {
|
|
||||||
currentFragment?.let {
|
|
||||||
fragmentStack.add(
|
|
||||||
FragmentStackItem(
|
|
||||||
it::class.java.name,
|
|
||||||
it.arguments,
|
|
||||||
supportFragmentManager.saveFragmentInstanceState(it)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
replaceFragment(fragment, true)
|
|
||||||
backHandler()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun popFragment(): Boolean {
|
|
||||||
return fragmentStack.isNotEmpty() && run {
|
|
||||||
val stackItem = fragmentStack.removeAt(fragmentStack.size - 1)
|
|
||||||
val fragment = Class.forName(stackItem.className).newInstance() as Fragment
|
|
||||||
stackItem.arguments?.let(fragment::setArguments)
|
|
||||||
stackItem.savedState?.let(fragment::setInitialSavedState)
|
|
||||||
replaceFragment(fragment, false)
|
|
||||||
backHandler()
|
|
||||||
true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun hideKeyboard() {
|
|
||||||
inputManager?.hideSoftInputFromWindow((currentFocus ?: window.decorView).windowToken, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun onToolbarCreated(toolbar: Toolbar) {
|
|
||||||
if (fragmentStack.isNotEmpty()) {
|
|
||||||
toolbar.navigationIcon = toolbar.context.homeAsUp
|
|
||||||
toolbar.setNavigationOnClickListener { onBackPressedDispatcher.onBackPressed() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onNewIntent(intent: Intent) {
|
|
||||||
super.onNewIntent(intent)
|
|
||||||
handleIntent(intent)
|
|
||||||
}
|
|
||||||
|
|
||||||
protected fun handleSpecialIntent(specialIntent: SpecialIntent) {
|
|
||||||
when (specialIntent) {
|
|
||||||
is SpecialIntent.Updates -> {
|
|
||||||
if (currentFragment !is TabsFragment) {
|
|
||||||
fragmentStack.clear()
|
|
||||||
replaceFragment(TabsFragment(), true)
|
|
||||||
}
|
|
||||||
val tabsFragment = currentFragment as TabsFragment
|
|
||||||
tabsFragment.selectUpdates()
|
|
||||||
backHandler()
|
|
||||||
}
|
|
||||||
|
|
||||||
is SpecialIntent.Install -> {
|
|
||||||
val packageName = specialIntent.packageName
|
|
||||||
if (!packageName.isNullOrEmpty()) {
|
|
||||||
navigateProduct(packageName)
|
|
||||||
specialIntent.cacheFileName?.also { cacheFile ->
|
|
||||||
val installItem = packageName installFrom cacheFile
|
|
||||||
lifecycleScope.launch { installer install installItem }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Unit
|
|
||||||
}
|
|
||||||
}::class
|
|
||||||
}
|
|
||||||
|
|
||||||
open fun handleIntent(intent: Intent?) {
|
|
||||||
when (intent?.action) {
|
|
||||||
Intent.ACTION_VIEW -> {
|
|
||||||
when (val deeplink = intent.deeplinkType) {
|
|
||||||
is DeeplinkType.AppDetail -> {
|
|
||||||
val fragment = currentFragment
|
|
||||||
if (fragment !is AppDetailFragment) {
|
|
||||||
navigateProduct(deeplink.packageName, deeplink.repoAddress)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
is DeeplinkType.AddRepository -> {
|
|
||||||
navigateAddRepository(repoAddress = deeplink.address)
|
|
||||||
}
|
|
||||||
|
|
||||||
null -> {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Intent.ACTION_SHOW_APP_INFO -> {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
|
|
||||||
val packageName = intent.getStringExtra(Intent.EXTRA_PACKAGE_NAME)
|
|
||||||
|
|
||||||
if (packageName != null && currentFragment !is AppDetailFragment) {
|
|
||||||
navigateProduct(packageName)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun navigateFavourites() = pushFragment(FavouritesFragment())
|
|
||||||
internal fun navigateProduct(packageName: String, repoAddress: String? = null) =
|
|
||||||
pushFragment(AppDetailFragment(packageName, repoAddress))
|
|
||||||
|
|
||||||
internal fun navigateRepositories() = pushFragment(RepositoriesFragment())
|
|
||||||
internal fun navigatePreferences() = pushFragment(SettingsFragment.newInstance())
|
|
||||||
internal fun navigateAddRepository(repoAddress: String? = null) =
|
|
||||||
pushFragment(EditRepositoryFragment(null, repoAddress))
|
|
||||||
|
|
||||||
internal fun navigateRepository(repositoryId: Long) =
|
|
||||||
pushFragment(RepositoryFragment(repositoryId))
|
|
||||||
|
|
||||||
internal fun navigateEditRepository(repositoryId: Long) =
|
|
||||||
pushFragment(EditRepositoryFragment(repositoryId, null))
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
package com.looker.droidify.utility.extension
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import com.looker.core.common.Singleton
|
|
||||||
import com.looker.core.common.extension.dpi
|
|
||||||
import com.looker.droidify.model.Product
|
|
||||||
import com.looker.droidify.model.ProductItem
|
|
||||||
import com.looker.droidify.model.Repository
|
|
||||||
|
|
||||||
object ImageUtils {
|
|
||||||
private val SUPPORTED_DPI = listOf(120, 160, 240, 320, 480, 640)
|
|
||||||
private var DeviceDpi = Singleton<String>()
|
|
||||||
|
|
||||||
fun Product.Screenshot.url(
|
|
||||||
repository: Repository,
|
|
||||||
packageName: String
|
|
||||||
): String {
|
|
||||||
val phoneType = when (type) {
|
|
||||||
Product.Screenshot.Type.PHONE -> "phoneScreenshots"
|
|
||||||
Product.Screenshot.Type.SMALL_TABLET -> "sevenInchScreenshots"
|
|
||||||
Product.Screenshot.Type.LARGE_TABLET -> "tenInchScreenshots"
|
|
||||||
}
|
|
||||||
return "${repository.address}/$packageName/$locale/$phoneType/$path"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ProductItem.icon(
|
|
||||||
view: View,
|
|
||||||
repository: Repository
|
|
||||||
): String? {
|
|
||||||
if (packageName.isBlank()) return null
|
|
||||||
if (icon.isBlank() && metadataIcon.isBlank()) return null
|
|
||||||
if (repository.version < 11 && icon.isNotBlank()) {
|
|
||||||
return "${repository.address}/icons/$icon"
|
|
||||||
}
|
|
||||||
if (icon.isNotBlank()) {
|
|
||||||
val deviceDpi = DeviceDpi.getOrUpdate {
|
|
||||||
(SUPPORTED_DPI.find { it >= view.dpi } ?: SUPPORTED_DPI.last()).toString()
|
|
||||||
}
|
|
||||||
return "${repository.address}/icons-$deviceDpi/$icon"
|
|
||||||
}
|
|
||||||
if (metadataIcon.isNotBlank()) {
|
|
||||||
return "${repository.address}/$packageName/$metadataIcon"
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<menu xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item
|
|
||||||
android:id="@+id/exploreTab"
|
|
||||||
android:icon="@drawable/ic_public"
|
|
||||||
android:title="@string/explore" />
|
|
||||||
<item
|
|
||||||
android:id="@+id/latestTab"
|
|
||||||
android:icon="@drawable/ic_new_releases"
|
|
||||||
android:title="@string/latest" />
|
|
||||||
<item
|
|
||||||
android:id="@+id/installedTab"
|
|
||||||
android:icon="@drawable/ic_launch"
|
|
||||||
android:title="@string/installed" />
|
|
||||||
</menu>
|
|
||||||
Binary file not shown.
|
Before Width: | Height: | Size: 3.5 KiB |
@@ -1,3 +0,0 @@
|
|||||||
org.gradle.parallel=true
|
|
||||||
org.gradle.caching=true
|
|
||||||
org.gradle.configureondemand=true
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
dependencyResolutionManagement {
|
|
||||||
repositories {
|
|
||||||
gradlePluginPortal()
|
|
||||||
google()
|
|
||||||
mavenCentral()
|
|
||||||
}
|
|
||||||
versionCatalogs {
|
|
||||||
create("libs") {
|
|
||||||
from(files("../gradle/libs.versions.toml"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
rootProject.name = "build-logic"
|
|
||||||
include(":structure")
|
|
||||||
@@ -1,62 +0,0 @@
|
|||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
`kotlin-dsl`
|
|
||||||
}
|
|
||||||
|
|
||||||
group = "buildlogic"
|
|
||||||
|
|
||||||
java {
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_17
|
|
||||||
targetCompatibility = JavaVersion.VERSION_17
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlin {
|
|
||||||
compilerOptions {
|
|
||||||
jvmTarget = JvmTarget.JVM_17
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
compileOnly(libs.android.gradlePlugin)
|
|
||||||
compileOnly(libs.kotlin.gradlePlugin)
|
|
||||||
compileOnly(libs.kotlin.ktlint)
|
|
||||||
compileOnly(libs.ksp.gradlePlugin)
|
|
||||||
}
|
|
||||||
|
|
||||||
gradlePlugin {
|
|
||||||
plugins {
|
|
||||||
register("lintPlugin") {
|
|
||||||
id = "looker.lint"
|
|
||||||
implementationClass = "AndroidLintPlugin"
|
|
||||||
}
|
|
||||||
register("serializationPlugin") {
|
|
||||||
id = "looker.serialization"
|
|
||||||
implementationClass = "AndroidSerializationPlugin"
|
|
||||||
}
|
|
||||||
register("hiltPlugin") {
|
|
||||||
id = "looker.hilt"
|
|
||||||
implementationClass = "AndroidHiltPlugin"
|
|
||||||
}
|
|
||||||
register("hiltWorkPlugin") {
|
|
||||||
id = "looker.hilt.work"
|
|
||||||
implementationClass = "AndroidHiltWorkerPlugin"
|
|
||||||
}
|
|
||||||
register("roomPlugin") {
|
|
||||||
id = "looker.room"
|
|
||||||
implementationClass = "AndroidRoomPlugin"
|
|
||||||
}
|
|
||||||
register("androidApplicationPlugin") {
|
|
||||||
id = "looker.android.application"
|
|
||||||
implementationClass = "AndroidApplicationPlugin"
|
|
||||||
}
|
|
||||||
register("androidLibraryPlugin") {
|
|
||||||
id = "looker.android.library"
|
|
||||||
implementationClass = "AndroidLibraryPlugin"
|
|
||||||
}
|
|
||||||
register("jvmLibraryPlugin") {
|
|
||||||
id = "looker.jvm.library"
|
|
||||||
implementationClass = "JvmLibraryPlugin"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,34 +0,0 @@
|
|||||||
import com.android.build.api.dsl.ApplicationExtension
|
|
||||||
import com.looker.droidify.configureKotlinAndroid
|
|
||||||
import com.looker.droidify.kotlin2
|
|
||||||
import com.looker.droidify.libs
|
|
||||||
import org.gradle.api.Plugin
|
|
||||||
import org.gradle.api.Project
|
|
||||||
import org.gradle.kotlin.dsl.configure
|
|
||||||
import org.gradle.kotlin.dsl.dependencies
|
|
||||||
|
|
||||||
class AndroidApplicationPlugin : Plugin<Project> {
|
|
||||||
override fun apply(target: Project) {
|
|
||||||
with(target) {
|
|
||||||
with(pluginManager) {
|
|
||||||
apply("com.android.application")
|
|
||||||
apply("org.jetbrains.kotlin.android")
|
|
||||||
}
|
|
||||||
|
|
||||||
extensions.configure<ApplicationExtension> {
|
|
||||||
configureKotlinAndroid(this)
|
|
||||||
buildToolsVersion = DefaultConfig.buildTools
|
|
||||||
defaultConfig {
|
|
||||||
targetSdk = DefaultConfig.compileSdk
|
|
||||||
applicationId = DefaultConfig.appId
|
|
||||||
versionCode = DefaultConfig.versionCode
|
|
||||||
versionName = DefaultConfig.versionName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
add("implementation", kotlin2("stdlib", libs))
|
|
||||||
add("implementation", kotlin2("reflect", libs))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,26 +0,0 @@
|
|||||||
import com.android.build.gradle.api.AndroidBasePlugin
|
|
||||||
import com.looker.droidify.getLibrary
|
|
||||||
import com.looker.droidify.libs
|
|
||||||
import org.gradle.api.Plugin
|
|
||||||
import org.gradle.api.Project
|
|
||||||
import org.gradle.kotlin.dsl.dependencies
|
|
||||||
|
|
||||||
class AndroidHiltPlugin : Plugin<Project> {
|
|
||||||
override fun apply(target: Project) {
|
|
||||||
with(target) {
|
|
||||||
pluginManager.apply("com.google.devtools.ksp")
|
|
||||||
dependencies {
|
|
||||||
add("ksp", libs.getLibrary("hilt.compiler"))
|
|
||||||
add("implementation", libs.getLibrary("hilt.core"))
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Add support for Android modules, based on [AndroidBasePlugin] */
|
|
||||||
pluginManager.withPlugin("com.android.base") {
|
|
||||||
pluginManager.apply("dagger.hilt.android.plugin")
|
|
||||||
dependencies {
|
|
||||||
add("implementation", libs.getLibrary("hilt.android"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import com.looker.droidify.getLibrary
|
|
||||||
import com.looker.droidify.libs
|
|
||||||
import org.gradle.api.Plugin
|
|
||||||
import org.gradle.api.Project
|
|
||||||
import org.gradle.kotlin.dsl.dependencies
|
|
||||||
|
|
||||||
class AndroidHiltWorkerPlugin : Plugin<Project> {
|
|
||||||
override fun apply(target: Project) {
|
|
||||||
with(target) {
|
|
||||||
with(pluginManager) {
|
|
||||||
apply("looker.hilt")
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
add("implementation", libs.getLibrary("androidx.work.ktx"))
|
|
||||||
add("implementation", libs.getLibrary("hilt.ext.work"))
|
|
||||||
add("ksp", libs.getLibrary("hilt.ext.compiler"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,48 +0,0 @@
|
|||||||
import com.android.build.api.variant.LibraryAndroidComponentsExtension
|
|
||||||
import com.android.build.gradle.LibraryExtension
|
|
||||||
import com.looker.droidify.configureKotlinAndroid
|
|
||||||
import com.looker.droidify.kotlin2
|
|
||||||
import com.looker.droidify.libs
|
|
||||||
import org.gradle.api.Plugin
|
|
||||||
import org.gradle.api.Project
|
|
||||||
import org.gradle.kotlin.dsl.configure
|
|
||||||
import org.gradle.kotlin.dsl.dependencies
|
|
||||||
|
|
||||||
class AndroidLibraryPlugin : Plugin<Project> {
|
|
||||||
override fun apply(target: Project) {
|
|
||||||
with(target) {
|
|
||||||
with(pluginManager) {
|
|
||||||
apply("com.android.library")
|
|
||||||
apply("org.jetbrains.kotlin.android")
|
|
||||||
}
|
|
||||||
|
|
||||||
extensions.configure<LibraryExtension> {
|
|
||||||
configureKotlinAndroid(this)
|
|
||||||
defaultConfig.targetSdk = DefaultConfig.compileSdk
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
isMinifyEnabled = true
|
|
||||||
proguardFiles(
|
|
||||||
getDefaultProguardFile("proguard-android-optimize.txt"),
|
|
||||||
"${rootDir.path}/app/proguard.pro"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
create("alpha") {
|
|
||||||
initWith(getByName("debug"))
|
|
||||||
isMinifyEnabled = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
extensions.configure<LibraryAndroidComponentsExtension> {
|
|
||||||
beforeVariants {
|
|
||||||
it.enableAndroidTest = it.enableAndroidTest
|
|
||||||
&& project.projectDir.resolve("src/androidTest").exists()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dependencies {
|
|
||||||
add("implementation", kotlin2("stdlib", libs))
|
|
||||||
add("implementation", kotlin2("reflect", libs))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
import org.gradle.api.Plugin
|
|
||||||
import org.gradle.api.Project
|
|
||||||
import org.gradle.kotlin.dsl.configure
|
|
||||||
import org.jlleitschuh.gradle.ktlint.KtlintExtension
|
|
||||||
import org.jlleitschuh.gradle.ktlint.reporter.ReporterType
|
|
||||||
|
|
||||||
class AndroidLintPlugin : Plugin<Project> {
|
|
||||||
override fun apply(target: Project) {
|
|
||||||
with(target) {
|
|
||||||
with(pluginManager) {
|
|
||||||
apply("org.jlleitschuh.gradle.ktlint")
|
|
||||||
}
|
|
||||||
|
|
||||||
extensions.configure<KtlintExtension> {
|
|
||||||
android.set(true)
|
|
||||||
ignoreFailures.set(true)
|
|
||||||
debug.set(true)
|
|
||||||
reporters {
|
|
||||||
reporter(ReporterType.HTML)
|
|
||||||
}
|
|
||||||
filter {
|
|
||||||
exclude("**/generated/**")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,46 +0,0 @@
|
|||||||
import com.google.devtools.ksp.gradle.KspExtension
|
|
||||||
import com.looker.droidify.getLibrary
|
|
||||||
import com.looker.droidify.libs
|
|
||||||
import org.gradle.api.Plugin
|
|
||||||
import org.gradle.api.Project
|
|
||||||
import org.gradle.api.tasks.InputDirectory
|
|
||||||
import org.gradle.api.tasks.PathSensitive
|
|
||||||
import org.gradle.api.tasks.PathSensitivity
|
|
||||||
import org.gradle.kotlin.dsl.configure
|
|
||||||
import org.gradle.kotlin.dsl.dependencies
|
|
||||||
import org.gradle.process.CommandLineArgumentProvider
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
class AndroidRoomPlugin : Plugin<Project> {
|
|
||||||
|
|
||||||
override fun apply(target: Project) {
|
|
||||||
with(target) {
|
|
||||||
pluginManager.apply("com.google.devtools.ksp")
|
|
||||||
|
|
||||||
extensions.configure<KspExtension> {
|
|
||||||
// The schemas directory contains a schema file for each version of the Room database.
|
|
||||||
// This is required to enable Room auto migrations.
|
|
||||||
// See https://developer.android.com/reference/kotlin/androidx/room/AutoMigration.
|
|
||||||
arg(RoomSchemaArgProvider(File(projectDir, "schemas")))
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
add("implementation", libs.getLibrary("room.ktx"))
|
|
||||||
add("implementation", libs.getLibrary("room.runtime"))
|
|
||||||
add("ksp", libs.getLibrary("room.compiler"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* https://issuetracker.google.com/issues/132245929
|
|
||||||
* [Export schemas](https://developer.android.com/training/data-storage/room/migrating-db-versions#export-schemas)
|
|
||||||
*/
|
|
||||||
class RoomSchemaArgProvider(
|
|
||||||
@get:InputDirectory
|
|
||||||
@get:PathSensitive(PathSensitivity.RELATIVE)
|
|
||||||
val schemaDir: File,
|
|
||||||
) : CommandLineArgumentProvider {
|
|
||||||
override fun asArguments() = listOf("room.schemaLocation=${schemaDir.path}")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
import com.looker.droidify.getLibrary
|
|
||||||
import com.looker.droidify.libs
|
|
||||||
import org.gradle.api.Plugin
|
|
||||||
import org.gradle.api.Project
|
|
||||||
import org.gradle.kotlin.dsl.dependencies
|
|
||||||
|
|
||||||
class AndroidSerializationPlugin : Plugin<Project> {
|
|
||||||
override fun apply(target: Project) {
|
|
||||||
with(target) {
|
|
||||||
with(pluginManager) {
|
|
||||||
apply("org.jetbrains.kotlin.plugin.serialization")
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
add("implementation", libs.getLibrary("kotlinx.serialization.json"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
object DefaultConfig {
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
// Update [release_build.yml] along with this
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
import com.looker.droidify.configureKotlinJvm
|
|
||||||
import org.gradle.api.Plugin
|
|
||||||
import org.gradle.api.Project
|
|
||||||
|
|
||||||
class JvmLibraryPlugin : Plugin<Project> {
|
|
||||||
override fun apply(target: Project) {
|
|
||||||
with(target) {
|
|
||||||
with(pluginManager) {
|
|
||||||
apply("org.jetbrains.kotlin.jvm")
|
|
||||||
}
|
|
||||||
configureKotlinJvm()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
import org.gradle.kotlin.dsl.DependencyHandlerScope
|
|
||||||
import org.gradle.kotlin.dsl.project
|
|
||||||
|
|
||||||
object Modules {
|
|
||||||
const val app = ":app"
|
|
||||||
const val coreCommon = ":core:common"
|
|
||||||
const val coreData = ":core:data"
|
|
||||||
const val coreDatabase = ":core:database"
|
|
||||||
const val coreDatastore = ":core:datastore"
|
|
||||||
const val coreDI = ":core:di"
|
|
||||||
const val coreDomain = ":core:domain"
|
|
||||||
const val coreNetwork = ":core:network"
|
|
||||||
const val installer = ":installer"
|
|
||||||
const val sync = ":sync:fdroid"
|
|
||||||
}
|
|
||||||
|
|
||||||
fun DependencyHandlerScope.modules(vararg module: String) {
|
|
||||||
module.forEach {
|
|
||||||
add("implementation", project(it))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,79 +0,0 @@
|
|||||||
package com.looker.droidify
|
|
||||||
|
|
||||||
import DefaultConfig
|
|
||||||
import com.android.build.api.dsl.CommonExtension
|
|
||||||
import org.gradle.api.JavaVersion
|
|
||||||
import org.gradle.api.Project
|
|
||||||
import org.gradle.api.plugins.JavaPluginExtension
|
|
||||||
import org.gradle.kotlin.dsl.assign
|
|
||||||
import org.gradle.kotlin.dsl.configure
|
|
||||||
import org.gradle.kotlin.dsl.dependencies
|
|
||||||
import org.jetbrains.kotlin.gradle.dsl.JvmTarget
|
|
||||||
import org.jetbrains.kotlin.gradle.dsl.KotlinAndroidProjectExtension
|
|
||||||
import org.jetbrains.kotlin.gradle.dsl.KotlinBaseExtension
|
|
||||||
import org.jetbrains.kotlin.gradle.dsl.KotlinJvmProjectExtension
|
|
||||||
|
|
||||||
// Taken from NIA sample app by Google
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure base Kotlin with Android options
|
|
||||||
*/
|
|
||||||
internal fun Project.configureKotlinAndroid(
|
|
||||||
commonExtension: CommonExtension<*, *, *, *, *, *>,
|
|
||||||
) {
|
|
||||||
commonExtension.apply {
|
|
||||||
compileSdk = DefaultConfig.compileSdk
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
minSdk = DefaultConfig.minSdk
|
|
||||||
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
|
|
||||||
}
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
// Up to Java 11 APIs are available through desugaring
|
|
||||||
// https://developer.android.com/studio/write/java11-minimal-support-table
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
|
||||||
isCoreLibraryDesugaringEnabled = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
configureKotlin<KotlinAndroidProjectExtension>()
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
add("coreLibraryDesugaring", libs.getLibrary("android.desugarJdkLibs"))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
internal fun Project.configureKotlinJvm() {
|
|
||||||
extensions.configure<JavaPluginExtension> {
|
|
||||||
// Up to Java 11 APIs are available through desugaring
|
|
||||||
// https://developer.android.com/studio/write/java11-minimal-support-table
|
|
||||||
sourceCompatibility = JavaVersion.VERSION_11
|
|
||||||
targetCompatibility = JavaVersion.VERSION_11
|
|
||||||
}
|
|
||||||
|
|
||||||
configureKotlin<KotlinJvmProjectExtension>()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configure base Kotlin options
|
|
||||||
*/
|
|
||||||
private inline fun <reified T : KotlinBaseExtension> Project.configureKotlin() = configure<T> {
|
|
||||||
when (this) {
|
|
||||||
is KotlinAndroidProjectExtension -> compilerOptions
|
|
||||||
is KotlinJvmProjectExtension -> compilerOptions
|
|
||||||
else -> TODO("Unsupported project extension $this ${T::class}")
|
|
||||||
}.apply {
|
|
||||||
// Use when hilt supports ksp2
|
|
||||||
// apiVersion = KotlinVersion.KOTLIN_2_0
|
|
||||||
jvmTarget = JvmTarget.JVM_11
|
|
||||||
freeCompilerArgs = listOf(
|
|
||||||
"-opt-in=kotlin.RequiresOptIn",
|
|
||||||
// Enable experimental coroutines APIs, including Flow
|
|
||||||
"-opt-in=kotlinx.coroutines.ExperimentalCoroutinesApi",
|
|
||||||
"-opt-in=kotlinx.coroutines.FlowPreview",
|
|
||||||
"-Xcontext-receivers"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,19 +0,0 @@
|
|||||||
package com.looker.droidify
|
|
||||||
|
|
||||||
import org.gradle.api.Project
|
|
||||||
import org.gradle.api.artifacts.MinimalExternalModuleDependency
|
|
||||||
import org.gradle.api.artifacts.VersionCatalog
|
|
||||||
import org.gradle.api.artifacts.VersionCatalogsExtension
|
|
||||||
import org.gradle.api.artifacts.dsl.DependencyHandler
|
|
||||||
import org.gradle.api.provider.Provider
|
|
||||||
import org.gradle.kotlin.dsl.getByType
|
|
||||||
import org.gradle.kotlin.dsl.kotlin
|
|
||||||
|
|
||||||
val Project.libs
|
|
||||||
get(): VersionCatalog = extensions.getByType<VersionCatalogsExtension>().named("libs")
|
|
||||||
|
|
||||||
fun VersionCatalog.getLibrary(alias: String): Provider<MinimalExternalModuleDependency> =
|
|
||||||
findLibrary(alias).get()
|
|
||||||
|
|
||||||
fun DependencyHandler.kotlin2(module: String, catalog: VersionCatalog): Any =
|
|
||||||
kotlin(module, version = catalog.findVersion("kotlin").get().strictVersion)
|
|
||||||
1
core/common/.gitignore
vendored
1
core/common/.gitignore
vendored
@@ -1 +0,0 @@
|
|||||||
/build
|
|
||||||
@@ -1,47 +0,0 @@
|
|||||||
import com.android.build.gradle.internal.tasks.factory.dependsOn
|
|
||||||
|
|
||||||
plugins {
|
|
||||||
alias(libs.plugins.looker.android.library)
|
|
||||||
alias(libs.plugins.looker.lint)
|
|
||||||
}
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace = "com.looker.core.common"
|
|
||||||
defaultConfig {
|
|
||||||
vectorDrawables.useSupportLibrary = true
|
|
||||||
}
|
|
||||||
buildFeatures {
|
|
||||||
buildConfig = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation(libs.kotlinx.coroutines.android)
|
|
||||||
implementation(libs.android.material)
|
|
||||||
implementation(libs.androidx.activity)
|
|
||||||
implementation(libs.androidx.fragment.ktx)
|
|
||||||
implementation(libs.androidx.core.ktx)
|
|
||||||
implementation(libs.androidx.lifecycle.viewModel)
|
|
||||||
implementation(libs.androidx.recyclerview)
|
|
||||||
implementation(libs.coil.kt)
|
|
||||||
implementation(libs.jackson.core)
|
|
||||||
}
|
|
||||||
|
|
||||||
// using a task as a preBuild dependency instead of a function that takes some time insures that it runs
|
|
||||||
task("detectAndroidLocals") {
|
|
||||||
val langsList: MutableSet<String> = HashSet()
|
|
||||||
|
|
||||||
// in /res are (almost) all languages that have a translated string is saved. this is safer and saves some time
|
|
||||||
fileTree("src/main/res").visit {
|
|
||||||
if (this.file.path.endsWith("strings.xml") &&
|
|
||||||
this.file.canonicalFile.readText().contains("<string")
|
|
||||||
) {
|
|
||||||
var languageCode = this.file.parentFile.name.replace("values-", "")
|
|
||||||
languageCode = if (languageCode == "values") "en" else languageCode
|
|
||||||
langsList.add(languageCode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
val langsListString = "{${langsList.sorted().joinToString(",") { "\"${it}\"" }}}"
|
|
||||||
android.defaultConfig.buildConfigField("String[]", "DETECTED_LOCALES", langsListString)
|
|
||||||
}
|
|
||||||
tasks.preBuild.dependsOn("detectAndroidLocals")
|
|
||||||
@@ -1,15 +0,0 @@
|
|||||||
package com.looker.core.common
|
|
||||||
|
|
||||||
object Constants {
|
|
||||||
const val NOTIFICATION_CHANNEL_SYNCING = "syncing"
|
|
||||||
const val NOTIFICATION_CHANNEL_UPDATES = "updates"
|
|
||||||
const val NOTIFICATION_CHANNEL_DOWNLOADING = "downloading"
|
|
||||||
const val NOTIFICATION_CHANNEL_INSTALL = "install"
|
|
||||||
|
|
||||||
const val NOTIFICATION_ID_SYNCING = 1
|
|
||||||
const val NOTIFICATION_ID_UPDATES = 2
|
|
||||||
const val NOTIFICATION_ID_DOWNLOADING = 3
|
|
||||||
const val NOTIFICATION_ID_INSTALL = 4
|
|
||||||
|
|
||||||
const val JOB_ID_SYNC = 1
|
|
||||||
}
|
|
||||||
@@ -1,80 +0,0 @@
|
|||||||
package com.looker.core.common
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import com.looker.core.common.extension.get
|
|
||||||
|
|
||||||
private const val PERSONAL_HOST = "droidify.eu.org"
|
|
||||||
|
|
||||||
private val httpScheme = setOf("http", "https")
|
|
||||||
private val fdroidRepoScheme = setOf("fdroidrepo", "fdroidrepos")
|
|
||||||
|
|
||||||
private val supportedExternalHosts = setOf(
|
|
||||||
"f-droid.org",
|
|
||||||
"www.f-droid.org",
|
|
||||||
"staging.f-droid.org",
|
|
||||||
"apt.izzysoft.de"
|
|
||||||
)
|
|
||||||
|
|
||||||
val Intent.deeplinkType: DeeplinkType?
|
|
||||||
get() = when {
|
|
||||||
data?.scheme == "package" || data?.scheme == "fdroid.app" -> {
|
|
||||||
val packageName = data?.schemeSpecificPart?.nullIfEmpty()
|
|
||||||
?: throw InvalidDeeplink("Invalid packageName: $data")
|
|
||||||
DeeplinkType.AppDetail(packageName)
|
|
||||||
}
|
|
||||||
|
|
||||||
data?.scheme in fdroidRepoScheme -> {
|
|
||||||
val repoAddress =
|
|
||||||
if (data?.scheme.equals("fdroidrepos")) {
|
|
||||||
dataString!!.replaceFirst("fdroidrepos", "https")
|
|
||||||
} else if (data?.scheme.equals("fdroidrepo")) {
|
|
||||||
dataString!!.replaceFirst("fdroidrepo", "https")
|
|
||||||
} else {
|
|
||||||
throw InvalidDeeplink("No repo address: $data")
|
|
||||||
}
|
|
||||||
DeeplinkType.AddRepository(repoAddress)
|
|
||||||
}
|
|
||||||
|
|
||||||
data?.scheme == "market" && data?.host == "details" -> {
|
|
||||||
val packageName =
|
|
||||||
data["id"]?.nullIfEmpty() ?: throw InvalidDeeplink("Invalid packageName: $data")
|
|
||||||
DeeplinkType.AppDetail(packageName)
|
|
||||||
}
|
|
||||||
|
|
||||||
data != null && data?.scheme in httpScheme -> {
|
|
||||||
when (data?.host) {
|
|
||||||
PERSONAL_HOST -> {
|
|
||||||
val repoAddress = data["repo_address"]
|
|
||||||
if (data?.path == "/app/") {
|
|
||||||
val packageName =
|
|
||||||
data["id"] ?: throw InvalidDeeplink("Invalid packageName: $data")
|
|
||||||
DeeplinkType.AppDetail(packageName, repoAddress)
|
|
||||||
} else {
|
|
||||||
throw InvalidDeeplink("Unknown intent path: ${data?.path}, Data: $data")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
in supportedExternalHosts -> {
|
|
||||||
val packageName = data?.lastPathSegment?.nullIfEmpty()
|
|
||||||
?: throw InvalidDeeplink("Invalid packageName: $data")
|
|
||||||
DeeplinkType.AppDetail(packageName)
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
|
|
||||||
val Intent.getInstallPackageName: String?
|
|
||||||
get() = if (data?.scheme == "package") data?.schemeSpecificPart?.nullIfEmpty() else null
|
|
||||||
|
|
||||||
class InvalidDeeplink(override val message: String?) : IllegalStateException(message)
|
|
||||||
|
|
||||||
sealed interface DeeplinkType {
|
|
||||||
|
|
||||||
data class AddRepository(val address: String) : DeeplinkType
|
|
||||||
|
|
||||||
data class AppDetail(val packageName: String, val repoAddress: String? = null) : DeeplinkType
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package com.looker.core.common
|
|
||||||
|
|
||||||
import android.net.Uri
|
|
||||||
|
|
||||||
interface Exporter<T> {
|
|
||||||
|
|
||||||
suspend fun export(item: T, target: Uri)
|
|
||||||
|
|
||||||
suspend fun import(target: Uri): T
|
|
||||||
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package com.looker.core.common
|
|
||||||
|
|
||||||
import android.app.NotificationChannel
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.content.Context
|
|
||||||
import android.os.Build
|
|
||||||
import com.looker.core.common.extension.notificationManager
|
|
||||||
|
|
||||||
fun Context.createNotificationChannel(
|
|
||||||
id: String,
|
|
||||||
name: String,
|
|
||||||
description: String? = null,
|
|
||||||
showBadge: Boolean = false,
|
|
||||||
) {
|
|
||||||
sdkAbove(Build.VERSION_CODES.O) {
|
|
||||||
val channel = NotificationChannel(
|
|
||||||
id,
|
|
||||||
name,
|
|
||||||
NotificationManager.IMPORTANCE_LOW
|
|
||||||
).apply {
|
|
||||||
setDescription(description)
|
|
||||||
setShowBadge(showBadge)
|
|
||||||
setSound(null, null)
|
|
||||||
}
|
|
||||||
notificationManager?.createNotificationChannel(channel)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,52 +0,0 @@
|
|||||||
package com.looker.core.common
|
|
||||||
|
|
||||||
import android.Manifest
|
|
||||||
import android.app.Activity
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.os.Build
|
|
||||||
import android.provider.Settings
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
import com.looker.core.common.extension.intent
|
|
||||||
import com.looker.core.common.extension.powerManager
|
|
||||||
|
|
||||||
fun Context.isIgnoreBatteryEnabled() =
|
|
||||||
powerManager?.isIgnoringBatteryOptimizations(packageName) == true
|
|
||||||
|
|
||||||
fun Context.requestBatteryFreedom() {
|
|
||||||
if (!isIgnoreBatteryEnabled()) {
|
|
||||||
val intent = intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS) {
|
|
||||||
data = "package:$packageName".toUri()
|
|
||||||
}
|
|
||||||
runCatching {
|
|
||||||
startActivity(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Activity.requestNotificationPermission(
|
|
||||||
request: (permission: String) -> Unit,
|
|
||||||
onGranted: () -> Unit = {}
|
|
||||||
) {
|
|
||||||
when {
|
|
||||||
ContextCompat.checkSelfPermission(
|
|
||||||
this,
|
|
||||||
Manifest.permission.POST_NOTIFICATIONS
|
|
||||||
) == PackageManager.PERMISSION_GRANTED -> {
|
|
||||||
onGranted()
|
|
||||||
}
|
|
||||||
|
|
||||||
shouldShowRequestPermissionRationale(Manifest.permission.POST_NOTIFICATIONS) -> {
|
|
||||||
sdkAbove(Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
request(Manifest.permission.POST_NOTIFICATIONS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
else -> {
|
|
||||||
sdkAbove(Build.VERSION_CODES.TIRAMISU) {
|
|
||||||
request(Manifest.permission.POST_NOTIFICATIONS)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package com.looker.core.common
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.util.DisplayMetrics
|
|
||||||
import android.view.View
|
|
||||||
import androidx.recyclerview.widget.LinearSmoothScroller
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import kotlin.math.abs
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A custom LinearSmoothScroller that increases the scrolling speed quadratically
|
|
||||||
* based on the distance already scrolled.
|
|
||||||
*
|
|
||||||
* @param context The context used to access resources.
|
|
||||||
*/
|
|
||||||
class Scroller(context: Context) : LinearSmoothScroller(context) {
|
|
||||||
private var distanceScrolled = 0
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Calculates the speed per pixel based on the display metrics and the distance
|
|
||||||
* already scrolled. The speed increases quadratically over time.
|
|
||||||
*
|
|
||||||
* @param displayMetrics The display metrics used to calculate the speed.
|
|
||||||
* @return The speed per pixel.
|
|
||||||
*/
|
|
||||||
override fun calculateSpeedPerPixel(displayMetrics: DisplayMetrics): Float {
|
|
||||||
return (10f / displayMetrics.densityDpi) / (1 + 0.001f * distanceScrolled * distanceScrolled)
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the target view is found. Resets the distance scrolled.
|
|
||||||
*
|
|
||||||
* @param targetView The target view.
|
|
||||||
* @param state The current state of RecyclerView.
|
|
||||||
* @param action The action to be performed.
|
|
||||||
*/
|
|
||||||
override fun onTargetFound(targetView: View, state: RecyclerView.State, action: Action) {
|
|
||||||
super.onTargetFound(targetView, state, action)
|
|
||||||
distanceScrolled = 0
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when seeking the target step. Accumulates the distance scrolled.
|
|
||||||
*
|
|
||||||
* @param dx The amount of horizontal scroll.
|
|
||||||
* @param dy The amount of vertical scroll.
|
|
||||||
* @param state The current state of RecyclerView.
|
|
||||||
* @param action The action to be performed.
|
|
||||||
*/
|
|
||||||
override fun onSeekTargetStep(dx: Int, dy: Int, state: RecyclerView.State, action: Action) {
|
|
||||||
super.onSeekTargetStep(dx, dy, state, action)
|
|
||||||
distanceScrolled += abs(dy)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,36 +0,0 @@
|
|||||||
package com.looker.core.common
|
|
||||||
|
|
||||||
import android.os.Build
|
|
||||||
import androidx.annotation.ChecksSdkIntAtLeast
|
|
||||||
|
|
||||||
@ChecksSdkIntAtLeast(parameter = 0, lambda = 1)
|
|
||||||
inline fun sdkAbove(sdk: Int, onSuccessful: () -> Unit) {
|
|
||||||
if (Build.VERSION.SDK_INT >= sdk) onSuccessful()
|
|
||||||
}
|
|
||||||
|
|
||||||
object SdkCheck {
|
|
||||||
|
|
||||||
val sdk: Int
|
|
||||||
get() = Build.VERSION.SDK_INT
|
|
||||||
|
|
||||||
// Allows auto install if target sdk of apk is one less then current sdk
|
|
||||||
fun canAutoInstall(targetSdk: Int) = targetSdk >= sdk - 1 && isSnowCake
|
|
||||||
|
|
||||||
@get:ChecksSdkIntAtLeast(api = Build.VERSION_CODES.TIRAMISU)
|
|
||||||
val isTiramisu: Boolean = sdk >= Build.VERSION_CODES.TIRAMISU
|
|
||||||
|
|
||||||
@get:ChecksSdkIntAtLeast(api = Build.VERSION_CODES.R)
|
|
||||||
val isR: Boolean = sdk >= Build.VERSION_CODES.R
|
|
||||||
|
|
||||||
@get:ChecksSdkIntAtLeast(api = Build.VERSION_CODES.P)
|
|
||||||
val isPie: Boolean = sdk >= Build.VERSION_CODES.P
|
|
||||||
|
|
||||||
@get:ChecksSdkIntAtLeast(api = Build.VERSION_CODES.O)
|
|
||||||
val isOreo: Boolean = sdk >= Build.VERSION_CODES.O
|
|
||||||
|
|
||||||
@get:ChecksSdkIntAtLeast(api = Build.VERSION_CODES.S)
|
|
||||||
val isSnowCake: Boolean = sdk >= Build.VERSION_CODES.S
|
|
||||||
|
|
||||||
@get:ChecksSdkIntAtLeast(api = Build.VERSION_CODES.N)
|
|
||||||
val isNougat: Boolean = sdk >= Build.VERSION_CODES.N
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.looker.core.common
|
|
||||||
|
|
||||||
class Singleton<T> {
|
|
||||||
private var value: T? = null
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Updates the [value] if its null else it is returned
|
|
||||||
*/
|
|
||||||
fun getOrUpdate(block: () -> T): T = value ?: kotlin.run {
|
|
||||||
value = block()
|
|
||||||
value!!
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,53 +0,0 @@
|
|||||||
package com.looker.core.common
|
|
||||||
|
|
||||||
import android.util.Log
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
fun <T : CharSequence> T.nullIfEmpty(): T? {
|
|
||||||
return if (isNullOrBlank()) null else this
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Removes the string between the first [prefix] and last [suffix]
|
|
||||||
*
|
|
||||||
* For example: if "xyz_abc_123" is passed with [prefix] = "_"
|
|
||||||
*
|
|
||||||
* @return: "xyz_123"
|
|
||||||
*/
|
|
||||||
fun String.stripBetween(prefix: String, suffix: String = prefix): String {
|
|
||||||
val prefixIndex = indexOf(prefix)
|
|
||||||
val suffixIndex = lastIndexOf(suffix)
|
|
||||||
val isRangeValid = prefixIndex != -1 &&
|
|
||||||
suffixIndex != -1 &&
|
|
||||||
prefixIndex != suffixIndex
|
|
||||||
return if (isRangeValid) {
|
|
||||||
substring(0, prefixIndex + 1) + substring(suffixIndex + 1)
|
|
||||||
} else {
|
|
||||||
this
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val sizeFormats = listOf("%.0f B", "%.0f kB", "%.1f MB", "%.2f GB")
|
|
||||||
|
|
||||||
fun Long.formatSize(): String {
|
|
||||||
val (size, index) = generateSequence(Pair(this.toFloat(), 0)) { (size, index) ->
|
|
||||||
if (size >= 1024f) {
|
|
||||||
Pair(size / 1024f, index + 1)
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}.take(sizeFormats.size).last()
|
|
||||||
return sizeFormats[index].format(Locale.US, size)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ByteArray.hex(): String = joinToString(separator = "") { byte ->
|
|
||||||
"%02x".format(Locale.US, byte.toInt() and 0xff)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Any.log(
|
|
||||||
message: Any?,
|
|
||||||
tag: String = this::class.java.simpleName + ".DEBUG",
|
|
||||||
type: Int = Log.DEBUG
|
|
||||||
) {
|
|
||||||
Log.println(type, tag, message.toString())
|
|
||||||
}
|
|
||||||
@@ -1,257 +0,0 @@
|
|||||||
package com.looker.core.common.cache
|
|
||||||
|
|
||||||
import android.content.ContentProvider
|
|
||||||
import android.content.ContentValues
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.database.Cursor
|
|
||||||
import android.database.MatrixCursor
|
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
|
||||||
import android.os.ParcelFileDescriptor
|
|
||||||
import android.os.storage.StorageManager
|
|
||||||
import android.provider.OpenableColumns
|
|
||||||
import android.system.Os
|
|
||||||
import com.looker.core.common.SdkCheck
|
|
||||||
import com.looker.core.common.sdkAbove
|
|
||||||
import java.io.File
|
|
||||||
import java.util.UUID
|
|
||||||
import kotlin.concurrent.thread
|
|
||||||
import kotlin.math.min
|
|
||||||
import kotlin.time.Duration
|
|
||||||
import kotlin.time.Duration.Companion.days
|
|
||||||
import kotlin.time.Duration.Companion.hours
|
|
||||||
|
|
||||||
object Cache {
|
|
||||||
|
|
||||||
private const val RELEASE_DIR = "releases"
|
|
||||||
private const val PARTIAL_DIR = "partial"
|
|
||||||
private const val IMAGES_DIR = "images"
|
|
||||||
private const val INDEX_DIR = "index"
|
|
||||||
private const val TEMP_DIR = "temporary"
|
|
||||||
|
|
||||||
private fun ensureCacheDir(context: Context, name: String): File {
|
|
||||||
return File(
|
|
||||||
context.cacheDir,
|
|
||||||
name
|
|
||||||
).apply { isDirectory || mkdirs() || throw RuntimeException() }
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun applyOrMode(file: File, mode: Int) {
|
|
||||||
val oldMode = Os.stat(file.path).st_mode and 0b111111111111
|
|
||||||
val newMode = oldMode or mode
|
|
||||||
if (newMode != oldMode) {
|
|
||||||
Os.chmod(file.path, newMode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun subPath(dir: File, file: File): String {
|
|
||||||
val dirPath = "${dir.path}/"
|
|
||||||
val filePath = file.path
|
|
||||||
filePath.startsWith(dirPath) || throw RuntimeException()
|
|
||||||
return filePath.substring(dirPath.length)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getEmptySpace(context: Context): Long {
|
|
||||||
val dir = context.cacheDir
|
|
||||||
return min(dir.usableSpace, dir.freeSpace)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getImagesDir(context: Context): File {
|
|
||||||
return ensureCacheDir(context, IMAGES_DIR)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getIndexFile(context: Context, indexName: String): File {
|
|
||||||
return File(ensureCacheDir(context, INDEX_DIR), indexName)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getPartialReleaseFile(context: Context, cacheFileName: String): File {
|
|
||||||
return File(ensureCacheDir(context, PARTIAL_DIR), cacheFileName)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getReleaseFile(context: Context, cacheFileName: String): File {
|
|
||||||
return File(ensureCacheDir(context, RELEASE_DIR), cacheFileName).apply {
|
|
||||||
sdkAbove(Build.VERSION_CODES.N) {
|
|
||||||
// Make readable for package installer
|
|
||||||
val cacheDir = context.cacheDir.parentFile!!.parentFile!!
|
|
||||||
generateSequence(this) { it.parentFile!! }.takeWhile { it != cacheDir }.forEach {
|
|
||||||
when {
|
|
||||||
it.isDirectory -> applyOrMode(it, 0b001001001)
|
|
||||||
it.isFile -> applyOrMode(it, 0b100100100)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getReleaseUri(context: Context, cacheFileName: String): Uri {
|
|
||||||
val file = getReleaseFile(context, cacheFileName)
|
|
||||||
val packageInfo =
|
|
||||||
try {
|
|
||||||
if (SdkCheck.isTiramisu) {
|
|
||||||
context.packageManager.getPackageInfo(
|
|
||||||
context.packageName,
|
|
||||||
PackageManager.PackageInfoFlags.of(PackageManager.GET_PROVIDERS.toLong())
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
context.packageManager.getPackageInfo(
|
|
||||||
context.packageName,
|
|
||||||
PackageManager.GET_PROVIDERS
|
|
||||||
)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
val authority =
|
|
||||||
packageInfo?.providers?.find { it.name == Provider::class.java.name }!!.authority
|
|
||||||
return Uri.Builder()
|
|
||||||
.scheme("content")
|
|
||||||
.authority(authority)
|
|
||||||
.encodedPath(file.path.drop(context.cacheDir.path.length))
|
|
||||||
.build()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun getTemporaryFile(context: Context): File {
|
|
||||||
return File(ensureCacheDir(context, TEMP_DIR), UUID.randomUUID().toString())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun cleanup(context: Context) {
|
|
||||||
thread {
|
|
||||||
cleanup(
|
|
||||||
context,
|
|
||||||
Pair(IMAGES_DIR, 7.days),
|
|
||||||
Pair(INDEX_DIR, Duration.INFINITE),
|
|
||||||
Pair(PARTIAL_DIR, 1.days),
|
|
||||||
Pair(RELEASE_DIR, 1.days),
|
|
||||||
Pair(TEMP_DIR, 1.hours),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun cleanup(context: Context, vararg dirHours: Pair<String, Duration>) {
|
|
||||||
val knownNames = dirHours.asSequence().map { it.first }.toSet()
|
|
||||||
val files = context.cacheDir.listFiles().orEmpty()
|
|
||||||
files.asSequence().filter { it.name !in knownNames }.forEach {
|
|
||||||
if (it.isDirectory) {
|
|
||||||
cleanupDir(it, Duration.ZERO)
|
|
||||||
it.delete()
|
|
||||||
} else {
|
|
||||||
it.delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
dirHours.forEach { (name, duration) ->
|
|
||||||
val file = File(context.cacheDir, name)
|
|
||||||
if (file.exists()) {
|
|
||||||
if (file.isDirectory) {
|
|
||||||
cleanupDir(file, duration)
|
|
||||||
} else {
|
|
||||||
file.delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun cleanupDir(dir: File, duration: Duration) {
|
|
||||||
dir.listFiles()?.forEach {
|
|
||||||
val older = duration <= Duration.ZERO || run {
|
|
||||||
val olderThan = System.currentTimeMillis() / 1000L - duration.inWholeSeconds
|
|
||||||
try {
|
|
||||||
val stat = Os.lstat(it.path)
|
|
||||||
stat.st_atime < olderThan
|
|
||||||
} catch (e: Exception) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (older) {
|
|
||||||
if (it.isDirectory) {
|
|
||||||
cleanupDir(it, duration)
|
|
||||||
if (it.isDirectory) {
|
|
||||||
it.delete()
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
it.delete()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
class Provider : ContentProvider() {
|
|
||||||
companion object {
|
|
||||||
private val defaultColumns = arrayOf(OpenableColumns.DISPLAY_NAME, OpenableColumns.SIZE)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun getFileAndTypeForUri(uri: Uri): Pair<File, String> {
|
|
||||||
return when (uri.pathSegments?.firstOrNull()) {
|
|
||||||
RELEASE_DIR -> Pair(
|
|
||||||
File(context!!.cacheDir, uri.encodedPath!!),
|
|
||||||
"application/vnd.android.package-archive"
|
|
||||||
)
|
|
||||||
|
|
||||||
else -> throw SecurityException()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(): Boolean = true
|
|
||||||
|
|
||||||
override fun query(
|
|
||||||
uri: Uri,
|
|
||||||
projection: Array<String>?,
|
|
||||||
selection: String?,
|
|
||||||
selectionArgs: Array<out String>?,
|
|
||||||
sortOrder: String?
|
|
||||||
): Cursor {
|
|
||||||
val file = getFileAndTypeForUri(uri).first
|
|
||||||
val columns = (projection ?: defaultColumns).mapNotNull {
|
|
||||||
when (it) {
|
|
||||||
OpenableColumns.DISPLAY_NAME -> Pair(it, file.name)
|
|
||||||
OpenableColumns.SIZE -> Pair(it, file.length())
|
|
||||||
else -> null
|
|
||||||
}
|
|
||||||
}.unzip()
|
|
||||||
return MatrixCursor(columns.first.toTypedArray()).apply {
|
|
||||||
addRow(
|
|
||||||
columns.second.toTypedArray()
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getType(uri: Uri): String = getFileAndTypeForUri(uri).second
|
|
||||||
|
|
||||||
private val unsupported: Nothing
|
|
||||||
get() = throw UnsupportedOperationException()
|
|
||||||
|
|
||||||
override fun insert(uri: Uri, contentValues: ContentValues?): Uri = unsupported
|
|
||||||
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<out String>?): Int =
|
|
||||||
unsupported
|
|
||||||
|
|
||||||
override fun update(
|
|
||||||
uri: Uri,
|
|
||||||
contentValues: ContentValues?,
|
|
||||||
selection: String?,
|
|
||||||
selectionArgs: Array<out String>?
|
|
||||||
): Int = unsupported
|
|
||||||
|
|
||||||
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
|
|
||||||
val openMode = when (mode) {
|
|
||||||
"r" -> ParcelFileDescriptor.MODE_READ_ONLY
|
|
||||||
"w", "wt" ->
|
|
||||||
ParcelFileDescriptor.MODE_WRITE_ONLY or ParcelFileDescriptor.MODE_CREATE or
|
|
||||||
ParcelFileDescriptor.MODE_TRUNCATE
|
|
||||||
|
|
||||||
"wa" ->
|
|
||||||
ParcelFileDescriptor.MODE_WRITE_ONLY or ParcelFileDescriptor.MODE_CREATE or
|
|
||||||
ParcelFileDescriptor.MODE_APPEND
|
|
||||||
|
|
||||||
"rw" -> ParcelFileDescriptor.MODE_READ_WRITE or ParcelFileDescriptor.MODE_CREATE
|
|
||||||
"rwt" ->
|
|
||||||
ParcelFileDescriptor.MODE_READ_WRITE or ParcelFileDescriptor.MODE_CREATE or
|
|
||||||
ParcelFileDescriptor.MODE_TRUNCATE
|
|
||||||
|
|
||||||
else -> throw IllegalArgumentException()
|
|
||||||
}
|
|
||||||
val file = getFileAndTypeForUri(uri).first
|
|
||||||
return ParcelFileDescriptor.open(file, openMode)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
package com.looker.core.common.device
|
|
||||||
|
|
||||||
object Huawei {
|
|
||||||
val isHuaweiEmui: Boolean
|
|
||||||
get() {
|
|
||||||
return try {
|
|
||||||
Class.forName("com.huawei.android.os.BuildEx")
|
|
||||||
true
|
|
||||||
} catch (e: Exception) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,38 +0,0 @@
|
|||||||
package com.looker.core.common.device
|
|
||||||
|
|
||||||
import android.annotation.SuppressLint
|
|
||||||
import android.util.Log
|
|
||||||
|
|
||||||
object Miui {
|
|
||||||
val isMiui by lazy {
|
|
||||||
getSystemProperty("ro.miui.ui.version.name")?.isNotEmpty() ?: false
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("PrivateApi")
|
|
||||||
fun isMiuiOptimizationDisabled(): Boolean {
|
|
||||||
val sysProp = getSystemProperty("persist.sys.miui_optimization")
|
|
||||||
if (sysProp == "0" || sysProp == "false") {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
|
|
||||||
return try {
|
|
||||||
Class.forName("android.miui.AppOpsUtils")
|
|
||||||
.getDeclaredMethod("isXOptMode")
|
|
||||||
.invoke(null) as Boolean
|
|
||||||
} catch (e: Exception) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressLint("PrivateApi")
|
|
||||||
private fun getSystemProperty(key: String?): String? {
|
|
||||||
return try {
|
|
||||||
Class.forName("android.os.SystemProperties")
|
|
||||||
.getDeclaredMethod("get", String::class.java)
|
|
||||||
.invoke(null, key) as String
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Log.e("Miui", "Unable to use SystemProperties.get()", e)
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
package com.looker.core.common.extension
|
|
||||||
|
|
||||||
inline fun <K, E> Map<K, E>.updateAsMutable(block: MutableMap<K, E>.() -> Unit): Map<K, E> {
|
|
||||||
return toMutableMap().apply(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <T> Set<T>.updateAsMutable(block: MutableSet<T>.() -> Unit): Set<T> {
|
|
||||||
return toMutableSet().apply(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun <T> MutableSet<T>.addAndCompute(item: T, block: (isAdded: Boolean) -> Unit): Boolean =
|
|
||||||
add(item).apply { block(this) }
|
|
||||||
|
|
||||||
inline fun <T> List<T>.updateAsMutable(block: MutableList<T>.() -> Unit): List<T> {
|
|
||||||
return toMutableList().apply(block)
|
|
||||||
}
|
|
||||||
@@ -1,88 +0,0 @@
|
|||||||
package com.looker.core.common.extension
|
|
||||||
|
|
||||||
import android.app.NotificationManager
|
|
||||||
import android.app.job.JobScheduler
|
|
||||||
import android.content.ClipData
|
|
||||||
import android.content.ClipboardManager
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.res.ColorStateList
|
|
||||||
import android.graphics.drawable.Drawable
|
|
||||||
import android.net.ConnectivityManager
|
|
||||||
import android.os.PowerManager
|
|
||||||
import android.view.inputmethod.InputMethodManager
|
|
||||||
import androidx.annotation.AttrRes
|
|
||||||
import androidx.annotation.DrawableRes
|
|
||||||
import androidx.appcompat.content.res.AppCompatResources
|
|
||||||
import androidx.core.content.ContextCompat
|
|
||||||
import androidx.core.content.getSystemService
|
|
||||||
import com.looker.core.common.R
|
|
||||||
|
|
||||||
inline val Context.clipboardManager: ClipboardManager?
|
|
||||||
get() = getSystemService()
|
|
||||||
|
|
||||||
inline val Context.connectivityManager: ConnectivityManager?
|
|
||||||
get() = getSystemService()
|
|
||||||
|
|
||||||
inline val Context.inputManager: InputMethodManager?
|
|
||||||
get() = getSystemService()
|
|
||||||
|
|
||||||
inline val Context.jobScheduler: JobScheduler?
|
|
||||||
get() = getSystemService()
|
|
||||||
|
|
||||||
inline val Context.notificationManager: NotificationManager?
|
|
||||||
get() = getSystemService()
|
|
||||||
|
|
||||||
inline val Context.powerManager: PowerManager?
|
|
||||||
get() = getSystemService()
|
|
||||||
|
|
||||||
fun Context.copyToClipboard(clip: String) {
|
|
||||||
clipboardManager?.setPrimaryClip(ClipData.newPlainText(null, clip))
|
|
||||||
}
|
|
||||||
|
|
||||||
val Context.corneredBackground: Drawable
|
|
||||||
get() = getDrawableCompat(R.drawable.background_border)
|
|
||||||
|
|
||||||
val Context.divider: Drawable
|
|
||||||
get() = getDrawableFromAttr(android.R.attr.listDivider)
|
|
||||||
|
|
||||||
val Context.homeAsUp: Drawable
|
|
||||||
get() = getDrawableFromAttr(android.R.attr.homeAsUpIndicator)
|
|
||||||
|
|
||||||
val Context.open: Drawable
|
|
||||||
get() = getDrawableCompat(R.drawable.ic_launch)
|
|
||||||
|
|
||||||
val Context.selectableBackground: Drawable
|
|
||||||
get() = getDrawableFromAttr(android.R.attr.selectableItemBackground)
|
|
||||||
|
|
||||||
val Context.camera: Drawable
|
|
||||||
get() = getDrawableCompat(R.drawable.ic_image)
|
|
||||||
|
|
||||||
val Context.aspectRatio: Float
|
|
||||||
get() = with(resources.displayMetrics) {
|
|
||||||
(heightPixels / widthPixels).toFloat()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.getMutatedIcon(@DrawableRes id: Int): Drawable = getDrawableCompat(id).mutate()
|
|
||||||
|
|
||||||
private fun Context.getDrawableFromAttr(attrResId: Int): Drawable {
|
|
||||||
val typedArray = obtainStyledAttributes(intArrayOf(attrResId))
|
|
||||||
val resId = try {
|
|
||||||
typedArray.getResourceId(0, 0)
|
|
||||||
} finally {
|
|
||||||
typedArray.recycle()
|
|
||||||
}
|
|
||||||
return getDrawableCompat(resId)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Context.getDrawableCompat(@DrawableRes resId: Int = R.drawable.background_border): Drawable =
|
|
||||||
requireNotNull(AppCompatResources.getDrawable(this, resId)) { "Cannot find drawable, ID: $resId" }
|
|
||||||
|
|
||||||
fun Context.getColorFromAttr(@AttrRes attrResId: Int): ColorStateList {
|
|
||||||
val typedArray = obtainStyledAttributes(intArrayOf(attrResId))
|
|
||||||
val (colorStateList, resId) = try {
|
|
||||||
Pair(typedArray.getColorStateList(0), typedArray.getResourceId(0, 0))
|
|
||||||
} finally {
|
|
||||||
typedArray.recycle()
|
|
||||||
}
|
|
||||||
return colorStateList ?: ContextCompat.getColorStateList(this, resId)!!
|
|
||||||
}
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
package com.looker.core.common.extension
|
|
||||||
|
|
||||||
import android.database.Cursor
|
|
||||||
|
|
||||||
fun Cursor.asSequence(): Sequence<Cursor> {
|
|
||||||
return generateSequence { if (moveToNext()) this else null }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Cursor.firstOrNull(): Cursor? {
|
|
||||||
return if (moveToFirst()) this else null
|
|
||||||
}
|
|
||||||
@@ -1,18 +0,0 @@
|
|||||||
package com.looker.core.common.extension
|
|
||||||
|
|
||||||
import java.text.SimpleDateFormat
|
|
||||||
import java.util.Date
|
|
||||||
import java.util.Locale
|
|
||||||
import java.util.TimeZone
|
|
||||||
|
|
||||||
private object DateTime {
|
|
||||||
val HTTP_DATE_FORMAT: SimpleDateFormat
|
|
||||||
get() = SimpleDateFormat("EEE, dd MMM yyyy HH:mm:ss z", Locale.US).apply {
|
|
||||||
timeZone = TimeZone.getTimeZone("GMT")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Date.toFormattedString(): String = DateTime.HTTP_DATE_FORMAT.format(this)
|
|
||||||
|
|
||||||
fun String.toDate(): Date = DateTime.HTTP_DATE_FORMAT.parse(this)
|
|
||||||
?: throw IllegalStateException("Wrong Date Format")
|
|
||||||
@@ -1,8 +0,0 @@
|
|||||||
package com.looker.core.common.extension
|
|
||||||
|
|
||||||
import kotlinx.coroutines.CancellationException
|
|
||||||
|
|
||||||
inline fun Exception.exceptCancellation() {
|
|
||||||
printStackTrace()
|
|
||||||
if (this is CancellationException) throw this
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.looker.core.common.extension
|
|
||||||
|
|
||||||
import java.io.File
|
|
||||||
|
|
||||||
val File.size: Long?
|
|
||||||
get() = if (exists()) length().takeIf { it > 0L } else null
|
|
||||||
|
|
||||||
@@ -1,21 +0,0 @@
|
|||||||
package com.looker.core.common.extension
|
|
||||||
|
|
||||||
import com.looker.core.common.hex
|
|
||||||
import java.security.MessageDigest
|
|
||||||
import java.security.cert.Certificate
|
|
||||||
|
|
||||||
fun Certificate.fingerprint(): String {
|
|
||||||
return runCatching { encoded.fingerprint() }.getOrElse { "" }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ByteArray.fingerprint(): String = if (size >= 256) {
|
|
||||||
try {
|
|
||||||
val fingerprint = MessageDigest.getInstance("sha256").digest(this)
|
|
||||||
fingerprint.hex()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
""
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
""
|
|
||||||
}
|
|
||||||
@@ -1,29 +0,0 @@
|
|||||||
package com.looker.core.common.extension
|
|
||||||
|
|
||||||
import androidx.lifecycle.ViewModel
|
|
||||||
import androidx.lifecycle.viewModelScope
|
|
||||||
import kotlinx.coroutines.CoroutineScope
|
|
||||||
import kotlinx.coroutines.ExperimentalCoroutinesApi
|
|
||||||
import kotlinx.coroutines.channels.*
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
|
|
||||||
context(ViewModel)
|
|
||||||
fun <T> Flow<T>.asStateFlow(
|
|
||||||
initialValue: T,
|
|
||||||
scope: CoroutineScope = viewModelScope,
|
|
||||||
started: SharingStarted = SharingStarted.WhileSubscribed(5_000)
|
|
||||||
): StateFlow<T> = stateIn(
|
|
||||||
scope = scope,
|
|
||||||
started = started,
|
|
||||||
initialValue = initialValue
|
|
||||||
)
|
|
||||||
|
|
||||||
context(CoroutineScope)
|
|
||||||
@OptIn(ExperimentalCoroutinesApi::class)
|
|
||||||
fun <T> ReceiveChannel<T>.filter(
|
|
||||||
block: suspend (T) -> Boolean
|
|
||||||
): ReceiveChannel<T> = produce(capacity = Channel.UNLIMITED) {
|
|
||||||
consumeEach { item ->
|
|
||||||
if (block(item)) send(item)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,83 +0,0 @@
|
|||||||
package com.looker.core.common.extension
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.core.view.ViewCompat
|
|
||||||
import androidx.core.view.WindowInsetsCompat
|
|
||||||
import androidx.core.view.marginLeft
|
|
||||||
import androidx.core.view.marginTop
|
|
||||||
import androidx.core.view.updateLayoutParams
|
|
||||||
import androidx.core.view.updatePadding
|
|
||||||
import androidx.core.widget.NestedScrollView
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import com.looker.core.common.SdkCheck
|
|
||||||
import com.looker.core.common.extension.InsetSides.BOTTOM
|
|
||||||
import com.looker.core.common.extension.InsetSides.LEFT
|
|
||||||
import com.looker.core.common.extension.InsetSides.RIGHT
|
|
||||||
import com.looker.core.common.extension.InsetSides.TOP
|
|
||||||
|
|
||||||
fun View.systemBarsMargin(
|
|
||||||
persistentPadding: Int,
|
|
||||||
allowedSides: List<InsetSides> = listOf(LEFT, RIGHT, BOTTOM)
|
|
||||||
) {
|
|
||||||
if (SdkCheck.isR) {
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets ->
|
|
||||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
|
||||||
view.updateLayoutParams<ViewGroup.MarginLayoutParams> {
|
|
||||||
if (TOP in allowedSides) topMargin = insets.top + marginTop
|
|
||||||
if (LEFT in allowedSides) leftMargin = insets.left + marginLeft
|
|
||||||
if (BOTTOM in allowedSides) bottomMargin = insets.bottom + persistentPadding
|
|
||||||
if (RIGHT in allowedSides) rightMargin = insets.right + persistentPadding
|
|
||||||
}
|
|
||||||
WindowInsetsCompat.CONSUMED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun RecyclerView.systemBarsPadding(
|
|
||||||
allowedSides: List<InsetSides> = listOf(LEFT, RIGHT, BOTTOM),
|
|
||||||
includeFab: Boolean = true
|
|
||||||
) {
|
|
||||||
if (SdkCheck.isR) {
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets ->
|
|
||||||
clipToPadding = false
|
|
||||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
|
||||||
view.updatePadding(
|
|
||||||
if (LEFT in allowedSides) insets.left else 0,
|
|
||||||
if (TOP in allowedSides) insets.top else 0,
|
|
||||||
if (RIGHT in allowedSides) insets.right else 0,
|
|
||||||
if (BOTTOM in allowedSides) {
|
|
||||||
insets.bottom + if (includeFab) 88.dp else 0
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
)
|
|
||||||
WindowInsetsCompat.CONSUMED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun NestedScrollView.systemBarsPadding(
|
|
||||||
allowedSides: List<InsetSides> = listOf(LEFT, RIGHT, BOTTOM)
|
|
||||||
) {
|
|
||||||
if (SdkCheck.isR) {
|
|
||||||
ViewCompat.setOnApplyWindowInsetsListener(this) { view, windowInsets ->
|
|
||||||
clipToPadding = false
|
|
||||||
val insets = windowInsets.getInsets(WindowInsetsCompat.Type.systemBars())
|
|
||||||
view.updatePadding(
|
|
||||||
if (LEFT in allowedSides) insets.left else 0,
|
|
||||||
if (TOP in allowedSides) insets.top else 0,
|
|
||||||
if (RIGHT in allowedSides) insets.right else 0,
|
|
||||||
if (BOTTOM in allowedSides) insets.bottom else 0
|
|
||||||
)
|
|
||||||
WindowInsetsCompat.CONSUMED
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
enum class InsetSides {
|
|
||||||
LEFT,
|
|
||||||
RIGHT,
|
|
||||||
TOP,
|
|
||||||
BOTTOM
|
|
||||||
}
|
|
||||||
@@ -1,27 +0,0 @@
|
|||||||
package com.looker.core.common.extension
|
|
||||||
|
|
||||||
import android.app.PendingIntent
|
|
||||||
import android.content.Context
|
|
||||||
import android.content.Intent
|
|
||||||
import android.net.Uri
|
|
||||||
import androidx.core.app.TaskStackBuilder
|
|
||||||
import com.looker.core.common.SdkCheck
|
|
||||||
|
|
||||||
fun intent(action: String, block: Intent.() -> Unit = {}): Intent {
|
|
||||||
return Intent(action).apply(block)
|
|
||||||
}
|
|
||||||
|
|
||||||
inline val intentFlagCompat
|
|
||||||
get() = if (SdkCheck.isSnowCake) {
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT or PendingIntent.FLAG_MUTABLE
|
|
||||||
} else {
|
|
||||||
PendingIntent.FLAG_UPDATE_CURRENT
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Intent.toPendingIntent(context: Context): PendingIntent? =
|
|
||||||
TaskStackBuilder
|
|
||||||
.create(context)
|
|
||||||
.addNextIntentWithParentStack(this)
|
|
||||||
.getPendingIntent(0, intentFlagCompat)
|
|
||||||
|
|
||||||
operator fun Uri?.get(key: String): String? = this?.getQueryParameter(key)
|
|
||||||
@@ -1,108 +0,0 @@
|
|||||||
package com.looker.core.common.extension
|
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.JsonFactory
|
|
||||||
import com.fasterxml.jackson.core.JsonGenerator
|
|
||||||
import com.fasterxml.jackson.core.JsonParseException
|
|
||||||
import com.fasterxml.jackson.core.JsonParser
|
|
||||||
import com.fasterxml.jackson.core.JsonToken
|
|
||||||
|
|
||||||
object Json {
|
|
||||||
val factory = JsonFactory()
|
|
||||||
}
|
|
||||||
|
|
||||||
interface KeyToken {
|
|
||||||
val key: String
|
|
||||||
val token: JsonToken
|
|
||||||
|
|
||||||
fun number(key: String): Boolean = this.key == key && this.token.isNumeric
|
|
||||||
fun string(key: String): Boolean = this.key == key && this.token == JsonToken.VALUE_STRING
|
|
||||||
fun boolean(key: String): Boolean = this.key == key && this.token.isBoolean
|
|
||||||
fun dictionary(key: String): Boolean = this.key == key && this.token == JsonToken.START_OBJECT
|
|
||||||
fun array(key: String): Boolean = this.key == key && this.token == JsonToken.START_ARRAY
|
|
||||||
}
|
|
||||||
|
|
||||||
fun JsonParser.illegal(): Nothing {
|
|
||||||
throw JsonParseException(this, "Illegal state")
|
|
||||||
}
|
|
||||||
|
|
||||||
fun JsonParser.forEachKey(callback: JsonParser.(KeyToken) -> Unit) {
|
|
||||||
var passKey = ""
|
|
||||||
var passToken = JsonToken.NOT_AVAILABLE
|
|
||||||
val keyToken = object : KeyToken {
|
|
||||||
override val key: String
|
|
||||||
get() = passKey
|
|
||||||
override val token: JsonToken
|
|
||||||
get() = passToken
|
|
||||||
}
|
|
||||||
while (true) {
|
|
||||||
val token = nextToken()
|
|
||||||
if (token == JsonToken.FIELD_NAME) {
|
|
||||||
passKey = currentName
|
|
||||||
passToken = nextToken()
|
|
||||||
callback(keyToken)
|
|
||||||
} else if (token == JsonToken.END_OBJECT) {
|
|
||||||
break
|
|
||||||
} else {
|
|
||||||
illegal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun JsonParser.forEach(requiredToken: JsonToken, callback: JsonParser.() -> Unit) {
|
|
||||||
while (true) {
|
|
||||||
val token = nextToken()
|
|
||||||
if (token == JsonToken.END_ARRAY) {
|
|
||||||
break
|
|
||||||
} else if (token == requiredToken) {
|
|
||||||
callback()
|
|
||||||
} else if (token.isStructStart) {
|
|
||||||
skipChildren()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> JsonParser.collectNotNull(
|
|
||||||
requiredToken: JsonToken,
|
|
||||||
callback: JsonParser.() -> T?
|
|
||||||
): List<T> {
|
|
||||||
val list = mutableListOf<T>()
|
|
||||||
forEach(requiredToken) {
|
|
||||||
val result = callback()
|
|
||||||
if (result != null) {
|
|
||||||
list += result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return list
|
|
||||||
}
|
|
||||||
|
|
||||||
fun JsonParser.collectNotNullStrings(): List<String> {
|
|
||||||
return collectNotNull(JsonToken.VALUE_STRING) { valueAsString }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun JsonParser.collectDistinctNotEmptyStrings(): List<String> {
|
|
||||||
return collectNotNullStrings().asSequence().filter { it.isNotEmpty() }.distinct().toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun <T> JsonParser.parseDictionary(callback: JsonParser.() -> T): T {
|
|
||||||
if (nextToken() == JsonToken.START_OBJECT) {
|
|
||||||
val result = callback()
|
|
||||||
if (nextToken() != null) {
|
|
||||||
illegal()
|
|
||||||
}
|
|
||||||
return result
|
|
||||||
} else {
|
|
||||||
illegal()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun JsonGenerator.writeDictionary(callback: JsonGenerator.() -> Unit) {
|
|
||||||
writeStartObject()
|
|
||||||
callback()
|
|
||||||
writeEndObject()
|
|
||||||
}
|
|
||||||
|
|
||||||
inline fun JsonGenerator.writeArray(fieldName: String, callback: JsonGenerator.() -> Unit) {
|
|
||||||
writeArrayFieldStart(fieldName)
|
|
||||||
callback()
|
|
||||||
writeEndArray()
|
|
||||||
}
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
package com.looker.core.common.extension
|
|
||||||
|
|
||||||
import java.util.Locale
|
|
||||||
|
|
||||||
fun String.toLocale(): Locale = when {
|
|
||||||
contains("-r") -> Locale(
|
|
||||||
substring(0, 2),
|
|
||||||
substring(4)
|
|
||||||
)
|
|
||||||
|
|
||||||
contains("_") -> Locale(
|
|
||||||
substring(0, 2),
|
|
||||||
substring(3)
|
|
||||||
)
|
|
||||||
|
|
||||||
else -> Locale(this)
|
|
||||||
}
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
package com.looker.core.common.extension
|
|
||||||
|
|
||||||
import androidx.core.net.toUri
|
|
||||||
|
|
||||||
val String.isOnion: Boolean
|
|
||||||
get() = toUri().host?.endsWith(".onion") == true
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
package com.looker.core.common.extension
|
|
||||||
|
|
||||||
import android.content.res.Resources
|
|
||||||
import android.util.TypedValue
|
|
||||||
import android.view.View
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
|
|
||||||
infix fun Long.percentBy(denominator: Long?): Int {
|
|
||||||
if (denominator == null || denominator < 1) return -1
|
|
||||||
return (this * 100 / denominator).toInt()
|
|
||||||
}
|
|
||||||
|
|
||||||
val Number.dpToPx
|
|
||||||
get() = TypedValue.applyDimension(
|
|
||||||
TypedValue.COMPLEX_UNIT_DIP,
|
|
||||||
this.toFloat(),
|
|
||||||
Resources.getSystem().displayMetrics
|
|
||||||
)
|
|
||||||
|
|
||||||
context(View)
|
|
||||||
val Int.dp: Int
|
|
||||||
get() = (this * resources.displayMetrics.density).roundToInt()
|
|
||||||
@@ -1,152 +0,0 @@
|
|||||||
package com.looker.core.common.extension
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.content.pm.ApplicationInfo
|
|
||||||
import android.content.pm.PackageInfo
|
|
||||||
import android.content.pm.PackageManager
|
|
||||||
import android.content.pm.Signature
|
|
||||||
import com.looker.core.common.SdkCheck
|
|
||||||
import com.looker.core.common.hex
|
|
||||||
import java.security.MessageDigest
|
|
||||||
|
|
||||||
val PackageInfo.singleSignature: Signature?
|
|
||||||
get() = if (SdkCheck.isPie) {
|
|
||||||
val signingInfo = signingInfo
|
|
||||||
if (signingInfo?.hasMultipleSigners() == false) {
|
|
||||||
signingInfo.apkContentsSigners
|
|
||||||
?.let { if (it.size == 1) it[0] else null }
|
|
||||||
} else {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
signatures?.let { if (it.size == 1) it[0] else null }
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Signature.calculateHash() = MessageDigest.getInstance("MD5")
|
|
||||||
.digest(toCharsString().toByteArray())
|
|
||||||
.hex()
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
val PackageInfo.versionCodeCompat: Long
|
|
||||||
get() = if (SdkCheck.isPie) longVersionCode else versionCode.toLong()
|
|
||||||
|
|
||||||
fun PackageManager.isSystemApplication(packageName: String): Boolean = try {
|
|
||||||
(
|
|
||||||
(
|
|
||||||
this.getApplicationInfoCompat(packageName)
|
|
||||||
.flags
|
|
||||||
) and ApplicationInfo.FLAG_SYSTEM
|
|
||||||
) != 0
|
|
||||||
} catch (e: Exception) {
|
|
||||||
false
|
|
||||||
}
|
|
||||||
|
|
||||||
fun PackageManager.getLauncherActivities(packageName: String): List<Pair<String, String>> {
|
|
||||||
return queryIntentActivities(
|
|
||||||
Intent(Intent.ACTION_MAIN).addCategory(
|
|
||||||
Intent.CATEGORY_LAUNCHER
|
|
||||||
),
|
|
||||||
0
|
|
||||||
)
|
|
||||||
.asSequence()
|
|
||||||
.mapNotNull { resolveInfo -> resolveInfo.activityInfo }
|
|
||||||
.filter { activityInfo -> activityInfo.packageName == packageName }
|
|
||||||
.mapNotNull { activityInfo ->
|
|
||||||
val label = try {
|
|
||||||
activityInfo.loadLabel(this).toString()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.printStackTrace()
|
|
||||||
null
|
|
||||||
}
|
|
||||||
label?.let { labelName ->
|
|
||||||
activityInfo.name to labelName
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.toList()
|
|
||||||
}
|
|
||||||
|
|
||||||
fun PackageManager.getApplicationInfoCompat(
|
|
||||||
filePath: String
|
|
||||||
): ApplicationInfo = if (SdkCheck.isTiramisu) {
|
|
||||||
getApplicationInfo(
|
|
||||||
filePath,
|
|
||||||
PackageManager.ApplicationInfoFlags.of(0L)
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
getApplicationInfo(filePath, 0)
|
|
||||||
}
|
|
||||||
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
private val signaturesFlagCompat: Int
|
|
||||||
get() = (
|
|
||||||
if (SdkCheck.isPie) {
|
|
||||||
PackageManager.GET_SIGNING_CERTIFICATES
|
|
||||||
} else {
|
|
||||||
0
|
|
||||||
}
|
|
||||||
) or PackageManager.GET_SIGNATURES
|
|
||||||
|
|
||||||
fun PackageManager.getPackageInfoCompat(
|
|
||||||
packageName: String,
|
|
||||||
signatureFlag: Int = signaturesFlagCompat
|
|
||||||
): PackageInfo? = try {
|
|
||||||
if (SdkCheck.isTiramisu) {
|
|
||||||
getPackageInfo(
|
|
||||||
packageName,
|
|
||||||
PackageManager.PackageInfoFlags.of(signatureFlag.toLong())
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
getPackageInfo(packageName, signatureFlag)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun PackageManager.getPackageName(
|
|
||||||
packageName: String?,
|
|
||||||
): CharSequence? {
|
|
||||||
if (packageName == null) return null
|
|
||||||
return try {
|
|
||||||
getApplicationLabel(
|
|
||||||
getApplicationInfo(
|
|
||||||
packageName,
|
|
||||||
PackageManager.GET_META_DATA
|
|
||||||
)
|
|
||||||
)
|
|
||||||
} catch (e: PackageManager.NameNotFoundException) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun PackageManager.getPackageArchiveInfoCompat(
|
|
||||||
filePath: String,
|
|
||||||
signatureFlag: Int = signaturesFlagCompat
|
|
||||||
): PackageInfo? = try {
|
|
||||||
if (SdkCheck.isTiramisu) {
|
|
||||||
getPackageArchiveInfo(
|
|
||||||
filePath,
|
|
||||||
PackageManager.PackageInfoFlags.of(signatureFlag.toLong())
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
getPackageArchiveInfo(filePath, signatureFlag)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
|
|
||||||
fun PackageManager.getInstalledPackagesCompat(
|
|
||||||
signatureFlag: Int = signaturesFlagCompat
|
|
||||||
): List<PackageInfo>? = try {
|
|
||||||
if (SdkCheck.isTiramisu) {
|
|
||||||
getInstalledPackages(PackageManager.PackageInfoFlags.of(signatureFlag.toLong()))
|
|
||||||
} else {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
getInstalledPackages(signatureFlag)
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
null
|
|
||||||
}
|
|
||||||
@@ -1,7 +0,0 @@
|
|||||||
package com.looker.core.common.extension
|
|
||||||
|
|
||||||
import android.database.sqlite.SQLiteDatabase
|
|
||||||
|
|
||||||
fun SQLiteDatabase.execWithResult(sql: String) {
|
|
||||||
rawQuery(sql, null).use { it.count }
|
|
||||||
}
|
|
||||||
@@ -1,30 +0,0 @@
|
|||||||
package com.looker.core.common.extension
|
|
||||||
|
|
||||||
import android.app.Service
|
|
||||||
import android.content.Intent
|
|
||||||
import com.looker.core.common.SdkCheck
|
|
||||||
|
|
||||||
fun Service.startSelf() {
|
|
||||||
val intent = Intent(this, this::class.java)
|
|
||||||
if (SdkCheck.isOreo) {
|
|
||||||
startForegroundService(intent)
|
|
||||||
} else {
|
|
||||||
startService(intent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun Service.stopForegroundCompat(removeNotification: Boolean = true) {
|
|
||||||
@Suppress("DEPRECATION")
|
|
||||||
if (SdkCheck.isNougat) {
|
|
||||||
stopForeground(
|
|
||||||
if (removeNotification) {
|
|
||||||
Service.STOP_FOREGROUND_REMOVE
|
|
||||||
} else {
|
|
||||||
Service.STOP_FOREGROUND_DETACH
|
|
||||||
}
|
|
||||||
)
|
|
||||||
} else {
|
|
||||||
stopForeground(removeNotification)
|
|
||||||
}
|
|
||||||
stopSelf()
|
|
||||||
}
|
|
||||||
@@ -1,54 +0,0 @@
|
|||||||
package com.looker.core.common.extension
|
|
||||||
|
|
||||||
import android.util.TypedValue
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.View
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import android.widget.TextView
|
|
||||||
import androidx.recyclerview.widget.LinearLayoutManager
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import coil.request.ImageRequest
|
|
||||||
import kotlin.math.min
|
|
||||||
import kotlin.math.roundToInt
|
|
||||||
import kotlinx.coroutines.channels.awaitClose
|
|
||||||
import kotlinx.coroutines.flow.*
|
|
||||||
|
|
||||||
fun ImageRequest.Builder.authentication(base64: String) {
|
|
||||||
addHeader("Authorization", base64)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun TextView.setTextSizeScaled(size: Int) {
|
|
||||||
val realSize = (size * resources.displayMetrics.scaledDensity).roundToInt()
|
|
||||||
setTextSize(TypedValue.COMPLEX_UNIT_PX, realSize.toFloat())
|
|
||||||
}
|
|
||||||
|
|
||||||
fun ViewGroup.inflate(layoutResId: Int): View {
|
|
||||||
return LayoutInflater.from(context).inflate(layoutResId, this, false)
|
|
||||||
}
|
|
||||||
|
|
||||||
val RecyclerView.firstItemPosition: Flow<Int>
|
|
||||||
get() = callbackFlow {
|
|
||||||
val listener = object : RecyclerView.OnScrollListener() {
|
|
||||||
override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) {
|
|
||||||
val position = (recyclerView.layoutManager as LinearLayoutManager)
|
|
||||||
.findFirstVisibleItemPosition()
|
|
||||||
trySend(position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
addOnScrollListener(listener)
|
|
||||||
awaitClose { removeOnScrollListener(listener) }
|
|
||||||
}.distinctUntilChanged().conflate()
|
|
||||||
|
|
||||||
val RecyclerView.isFirstItemVisible: Flow<Boolean>
|
|
||||||
get() = firstItemPosition.map { it == 0 }.distinctUntilChanged()
|
|
||||||
|
|
||||||
val View.minDimension: Int
|
|
||||||
get() = (
|
|
||||||
min(
|
|
||||||
layoutParams.width,
|
|
||||||
layoutParams.height
|
|
||||||
) / resources.displayMetrics.density
|
|
||||||
).roundToInt()
|
|
||||||
|
|
||||||
val View.dpi: Int
|
|
||||||
get() = (context.resources.displayMetrics.densityDpi * minDimension) / 48
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
package com.looker.core.common.result
|
|
||||||
|
|
||||||
sealed interface Result<out T> {
|
|
||||||
data class Success<T>(val data: T) : Result<T>
|
|
||||||
|
|
||||||
data class Error<T>(
|
|
||||||
val exception: Throwable? = null,
|
|
||||||
val data: T? = null
|
|
||||||
) : Result<T>
|
|
||||||
}
|
|
||||||
@@ -1,70 +0,0 @@
|
|||||||
package com.looker.core.common.signature
|
|
||||||
|
|
||||||
import com.looker.core.common.extension.exceptCancellation
|
|
||||||
import com.looker.core.common.hex
|
|
||||||
import java.io.File
|
|
||||||
import java.security.MessageDigest
|
|
||||||
import kotlinx.coroutines.*
|
|
||||||
|
|
||||||
suspend fun File.verifyHash(hash: Hash): Boolean {
|
|
||||||
return try {
|
|
||||||
if (!hash.isValid() || !exists()) return false
|
|
||||||
calculateHash(hash.type)
|
|
||||||
?.equals(hash.hash, true)
|
|
||||||
?: false
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.exceptCancellation()
|
|
||||||
false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun File.calculateHash(hashType: String): String? {
|
|
||||||
return try {
|
|
||||||
if (hashType.isBlank() || !exists()) return null
|
|
||||||
MessageDigest
|
|
||||||
.getInstance(hashType)
|
|
||||||
.readBytesFrom(this)
|
|
||||||
?.hex()
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.exceptCancellation()
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private suspend fun MessageDigest.readBytesFrom(
|
|
||||||
file: File
|
|
||||||
): ByteArray? = withContext(Dispatchers.IO) {
|
|
||||||
try {
|
|
||||||
if (file.length() < DIRECT_READ_LIMIT) return@withContext digest(file.readBytes())
|
|
||||||
val buffer = ByteArray(DEFAULT_BUFFER_SIZE)
|
|
||||||
file.inputStream().use { input ->
|
|
||||||
var bytesRead = input.read(buffer)
|
|
||||||
while (bytesRead >= 0) {
|
|
||||||
ensureActive()
|
|
||||||
update(buffer, 0, bytesRead)
|
|
||||||
bytesRead = input.read(buffer)
|
|
||||||
}
|
|
||||||
digest()
|
|
||||||
}
|
|
||||||
} catch (e: Exception) {
|
|
||||||
e.exceptCancellation()
|
|
||||||
null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// 25 MB
|
|
||||||
private const val DIRECT_READ_LIMIT = 25 * 1024 * 1024
|
|
||||||
|
|
||||||
@Suppress("FunctionName")
|
|
||||||
data class Hash(
|
|
||||||
val type: String,
|
|
||||||
val hash: String
|
|
||||||
) {
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
fun SHA256(hash: String) = Hash(type = "sha256", hash)
|
|
||||||
fun MD5(hash: String) = Hash(type = "md5", hash)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun isValid(): Boolean = type.isNotBlank() && hash.isNotBlank()
|
|
||||||
}
|
|
||||||
@@ -1,5 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:color="?attr/colorError" android:state_checked="true" />
|
|
||||||
<item android:color="?attr/colorOutline" />
|
|
||||||
</selector>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:color="?attr/colorOnSurfaceInverse" android:state_enabled="false" />
|
|
||||||
<item android:color="?attr/colorPrimary" android:state_checked="true" />
|
|
||||||
<item android:color="?attr/colorOutline" />
|
|
||||||
</selector>
|
|
||||||
@@ -1,6 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<item android:alpha="@dimen/material_emphasis_disabled_background" android:color="?attr/colorOnSurface" android:state_enabled="false" />
|
|
||||||
<item android:color="?attr/colorPrimaryContainer" android:state_checked="true" />
|
|
||||||
<item android:color="?attr/colorSurfaceVariant" />
|
|
||||||
</selector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M440,313L244,509Q232,521 216,520.5Q200,520 188,508Q177,496 176.5,480Q176,464 188,452L452,188Q458,182 465,179.5Q472,177 480,177Q488,177 495,179.5Q502,182 508,188L772,452Q783,463 783,479.5Q783,496 772,508Q760,520 743.5,520Q727,520 715,508L520,313L520,760Q520,777 508.5,788.5Q497,800 480,800Q463,800 451.5,788.5Q440,777 440,760L440,313Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,4 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<shape xmlns:android="http://schemas.android.com/apk/res/android">
|
|
||||||
<corners android:radius="@dimen/shape_large_corner" />
|
|
||||||
</shape>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorOnSurface"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M18,13h-5v5c0,0.55 -0.45,1 -1,1s-1,-0.45 -1,-1v-5H6c-0.55,0 -1,-0.45 -1,-1s0.45,-1 1,-1h5V6c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v5h5c0.55,0 1,0.45 1,1s-0.45,1 -1,1z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M175.48,851.92Q147.1,851.92 127.19,832.01Q107.27,812.09 107.27,783.74L107.27,176.26Q107.27,147.91 127.19,127.99Q147.1,108.08 175.54,108.08L490.81,108.08L692.73,310L692.73,501.42L636.77,501.42L636.77,338.38L462.42,338.38L462.42,164.04L175.54,164.04Q170.92,164.04 167.08,167.89Q163.23,171.73 163.23,176.35L163.23,783.65Q163.23,788.27 167.08,792.11Q170.92,795.96 175.54,795.96L577.23,795.96L577.23,851.92L175.48,851.92ZM163.23,795.96L163.23,501.42L163.23,501.42L163.23,338.38L163.23,164.04L163.23,164.04Q163.23,164.04 163.23,167.89Q163.23,171.73 163.23,176.35L163.23,783.65Q163.23,788.27 163.23,792.11Q163.23,795.96 163.23,795.96L163.23,795.96ZM222.46,740.58Q226.08,697 249.02,660.69Q271.96,624.38 309.62,603L275.89,542.66Q275.89,542.54 279.25,528.87Q282.92,527 287.29,527.69Q291.65,528.39 293.65,532.31L327.94,593.77Q345.58,586.54 363.35,583.02Q381.13,579.5 399.88,579.5Q418.64,579.5 436.73,583.02Q454.81,586.54 472.12,593.77L506.54,532.31Q506.73,531.92 519.95,528.69Q523.73,530.63 524.92,534.9Q526.11,539.16 524.11,542.65L490.77,603Q528.42,624.38 551.21,660.68Q574,696.98 577.54,740.58L222.46,740.58ZM320.46,687.15Q327.69,687.15 332.77,681.84Q337.85,676.53 337.85,669.44Q337.85,662.35 332.71,657.08Q327.57,651.81 320.46,651.81Q313.23,651.81 307.81,657.11Q302.39,662.41 302.39,669.48Q302.39,676.55 307.81,681.85Q313.23,687.15 320.46,687.15ZM479.92,687.15Q487.15,687.15 492.58,681.85Q498,676.55 498,669.48Q498,662.41 492.58,657.11Q487.15,651.81 479.92,651.81Q472.69,651.81 467.52,657.11Q462.35,662.41 462.35,669.48Q462.35,676.55 467.62,681.85Q472.89,687.15 479.92,687.15ZM761.08,851.92L618.23,708.5L657.08,668.65L733.19,744.46L733.19,561L789.15,561L789.15,743.96L865.08,669.04L904.11,708.5L761.08,851.92Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorOnSurface"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M15.88,9.29L12,13.17 8.12,9.29c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41l4.59,4.59c0.39,0.39 1.02,0.39 1.41,0l4.59,-4.59c0.39,-0.39 0.39,-1.02 0,-1.41 -0.39,-0.38 -1.03,-0.39 -1.42,0z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M480.03,812.73Q422,812.73 372.87,783.98Q323.73,755.23 294.6,705.15L211.23,705.15Q199.77,705.15 191.96,696.9Q184.15,688.65 184.15,677.17Q184.15,665.7 192.37,657.45Q200.58,649.19 212.23,649.19L273.54,649.19Q267.46,624.27 266.56,598.64Q265.65,573.01 265.65,547.58L211.23,547.58Q199.77,547.58 191.96,539.32Q184.15,531.07 184.15,519.6Q184.15,508.12 192.37,499.87Q200.58,491.62 212.23,491.62L265.67,491.62Q265.67,465.42 266.02,439.67Q266.39,413.92 273.54,389.19L211.23,389.19Q199.77,389.19 191.96,380.94Q184.15,372.68 184.15,361.21Q184.15,349.74 192.37,341.48Q200.58,333.23 212.23,333.23L294.5,333.23Q308.84,308.69 328.9,288.75Q348.96,268.81 374.27,255.27L325,205.69Q317,197.58 317.35,186.5Q317.69,175.42 325.5,167.42Q333.31,159.31 344.58,159.31Q355.85,159.31 364.15,167.42L429,232.46Q453.83,224.62 479.79,224.62Q505.74,224.62 530.58,232.46L597.5,166.42Q605.63,158.62 616.54,159.12Q627.46,159.62 635.27,167.42Q643.08,175.73 643.08,187Q643.08,198.27 634.96,206.38L586.08,255.27Q611.38,268.81 631.23,288.6Q651.08,308.39 665.46,333.23L748.96,333.23Q760.23,333.23 768.04,341.48Q775.84,349.74 775.84,361.21Q775.84,372.68 767.63,380.94Q759.42,389.19 747.96,389.19L686.46,389.19Q693.31,413.92 693.83,439.67Q694.34,465.42 694.34,491.62L748.96,491.62Q760.23,491.62 768.04,499.87Q775.84,508.12 775.84,519.6Q775.84,531.07 767.63,539.32Q759.42,547.58 747.96,547.58L694.34,547.58Q694.34,573.08 693.44,598.67Q692.54,624.27 686.46,649.19L748.96,649.19Q760.23,649.19 768.04,657.45Q775.84,665.7 775.84,677.17Q775.84,688.65 767.63,696.9Q759.42,705.15 747.96,705.15L665.46,705.15Q636.27,755.23 587.16,783.98Q538.06,812.73 480.03,812.73ZM480.02,756.77Q545.69,756.77 592.04,710.38Q638.39,663.99 638.39,598.29L638.39,440.81Q638.39,375.11 592.02,328.77Q545.65,282.42 479.98,282.42Q414.31,282.42 367.96,328.81Q321.61,375.2 321.61,440.9L321.61,598.38Q321.61,664.08 367.98,710.42Q414.36,756.77 480.02,756.77ZM435.85,626.77L525.35,626.77Q536.61,626.77 544.42,618.75Q552.23,610.73 552.23,598.73Q552.23,587.23 544.01,579.02Q535.78,570.81 524.35,570.81L434.85,570.81Q423.39,570.81 415.58,578.87Q407.77,586.92 407.77,598.69Q407.77,610.35 415.98,618.56Q424.19,626.77 435.85,626.77ZM435.85,468.38L525.35,468.38Q536.61,468.38 544.42,460.36Q552.23,452.34 552.23,440.34Q552.23,428.85 544.01,420.64Q535.78,412.42 524.35,412.42L434.85,412.42Q423.39,412.42 415.58,420.48Q407.77,428.54 407.77,440.31Q407.77,451.96 415.98,460.17Q424.19,468.38 435.85,468.38ZM480,519.5Q480,519.5 480,519.5Q480,519.5 480,519.5L480,519.5Q480,519.5 480,519.5Q480,519.5 480,519.5Q480,519.5 480,519.5Q480,519.5 480,519.5L480,519.5Q480,519.5 480,519.5Q480,519.5 480,519.5Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorOnSurface"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M18.3,5.71c-0.39,-0.39 -1.02,-0.39 -1.41,0L12,10.59 7.11,5.7c-0.39,-0.39 -1.02,-0.39 -1.41,0 -0.39,0.39 -0.39,1.02 0,1.41L10.59,12 5.7,16.89c-0.39,0.39 -0.39,1.02 0,1.41 0.39,0.39 1.02,0.39 1.41,0L12,13.41l4.89,4.89c0.39,0.39 1.02,0.39 1.41,0 0.39,-0.39 0.39,-1.02 0,-1.41L13.41,12l4.89,-4.89c0.38,-0.38 0.38,-1.02 0,-1.4z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,13 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="108dp"
|
|
||||||
android:height="108dp"
|
|
||||||
android:viewportWidth="108"
|
|
||||||
android:viewportHeight="108">
|
|
||||||
<path
|
|
||||||
android:fillColor="#FFB4AB"
|
|
||||||
android:pathData="M0,0 108,0 108,108 L0,108 z" />
|
|
||||||
<path
|
|
||||||
android:fillColor="#690005"
|
|
||||||
android:pathData="M65.3,45.828l3.8,-6.6c0.2,-0.4 0.1,-0.9 -0.3,-1.1c-0.4,-0.2 -0.9,-0.1 -1.1,0.3l-3.9,6.7c-6.3,-2.8 -13.4,-2.8 -19.7,0l-3.9,-6.7c-0.2,-0.4 -0.7,-0.5 -1.1,-0.3C38.8,38.328 38.7,38.828 38.9,39.228l3.8,6.6C36.2,49.428 31.7,56.028 31,63.928h46C76.3,56.028 71.8,49.428 65.3,45.828zM43.4,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2c-0.3,-0.7 -0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C45.3,56.528 44.5,57.328 43.4,57.328L43.4,57.328zM64.6,57.328c-0.8,0 -1.5,-0.5 -1.8,-1.2s-0.1,-1.5 0.4,-2.1c0.5,-0.5 1.4,-0.7 2.1,-0.4c0.7,0.3 1.2,1 1.2,1.8C66.5,56.528 65.6,57.328 64.6,57.328L64.6,57.328z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24"
|
|
||||||
android:tint="?attr/colorOnPrimaryContainer">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M9,16.2l-3.5,-3.5c-0.39,-0.39 -1.01,-0.39 -1.4,0 -0.39,0.39 -0.39,1.01 0,1.4l4.19,4.19c0.39,0.39 1.02,0.39 1.41,0L20.3,7.7c0.39,-0.39 0.39,-1.01 0,-1.4 -0.39,-0.39 -1.01,-0.39 -1.4,0L9,16.2z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M180.38,480.61L341.87,642.1Q349.69,649.92 350.04,661.39Q350.38,672.85 341.88,681.44Q333.38,690.04 322.27,690.04Q311.15,690.04 302.65,681.35L124.81,503.69Q119.69,498.52 117.14,492.46Q114.58,486.41 114.58,479.6Q114.58,472.78 117.14,466.73Q119.69,460.68 124.81,455.5L301.8,278.51Q310.27,270.04 321.83,269.94Q333.38,269.85 342.19,278.46Q350.81,287.27 350.81,298.73Q350.81,310.19 342.19,318.81L180.38,480.61ZM779.62,479.39L618.13,317.9Q610.31,310.08 609.96,298.61Q609.62,287.15 618.12,278.56Q626.62,269.96 637.73,269.96Q648.85,269.96 657.54,278.46L835.18,456.29Q840.31,461.42 842.86,467.48Q845.42,473.55 845.42,480.37Q845.42,487.19 842.86,493.25Q840.31,499.32 835.19,504.5L658.35,681.35Q649.73,689.96 638.48,689.75Q627.23,689.54 618.62,680.73Q609.81,672.11 609.81,660.65Q609.81,649.19 618.63,640.37L779.62,479.39Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M480.09,851.92Q402.94,851.92 335.03,822.6Q267.11,793.27 216.87,743Q166.64,692.73 137.36,624.95Q108.08,557.16 108.08,480.09Q108.08,402.94 137.4,335.03Q166.73,267.11 217,216.87Q267.27,166.64 335.05,137.36Q402.84,108.08 479.91,108.08Q557.06,108.08 624.97,137.4Q692.89,166.73 743.13,217Q793.36,267.27 822.64,335.05Q851.92,402.84 851.92,479.91Q851.92,557.06 822.6,624.97Q793.27,692.89 743,743.13Q692.73,793.36 624.95,822.64Q557.16,851.92 480.09,851.92ZM479.99,795.96Q611.89,795.96 703.92,703.94Q795.96,611.91 795.96,480.01Q795.96,348.11 703.94,256.08Q611.91,164.04 480.01,164.04Q348.11,164.04 256.08,256.06Q164.04,348.09 164.04,479.99Q164.04,611.89 256.06,703.92Q348.09,795.96 479.99,795.96ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM400.81,626.77L559.19,626.77Q571.19,626.77 578.98,618.63Q586.77,610.49 586.77,598.45L586.77,558.64Q586.77,547.25 578.53,539.03Q570.29,530.81 558.87,530.81Q547.23,530.81 539.02,539.04Q530.81,547.28 530.81,558.69L530.81,570.81L429.19,570.81L429.19,389.19L530.81,389.19L530.81,401.11Q530.81,412.75 539.03,420.97Q547.26,429.19 558.8,429.19Q570.35,429.19 578.56,420.97Q586.77,412.75 586.77,401.11L586.77,361.61Q586.77,349.55 578.98,341.39Q571.19,333.23 559.19,333.23L400.81,333.23Q388.81,333.23 381.02,341.39Q373.23,349.55 373.23,361.61L373.23,598.39Q373.23,610.45 381.02,618.61Q388.81,626.77 400.81,626.77Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M283,830Q252.06,830 230.03,807.97Q208,785.94 208,755L208,243L206.5,243Q191,243 180,232Q169,221 169,205.5Q169,190 180,179Q191,168 206.5,168L362,168L362,167.5Q362,152 373,141Q384,130 399.5,130L561.5,130Q577,130 588,141Q599,152 599,167.5L599,168L754.5,168Q770,168 781,179Q792,190 792,205.5Q792,221 781,232Q770,243 754.5,243L753,243L753,755Q753,785.94 730.97,807.97Q708.94,830 678,830L283,830ZM678,243L283,243L283,755Q283,755 283,755Q283,755 283,755L678,755Q678,755 678,755Q678,755 678,755L678,243ZM365,676.5L440,676.5L440,321.5L365,321.5L365,676.5ZM521,676.5L596,676.5L596,321.5L521,321.5L521,676.5ZM283,243L283,243L283,755Q283,755 283,755Q283,755 283,755L283,755Q283,755 283,755Q283,755 283,755L283,243Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M471.92,638.08L497.08,638.08L497.08,606.46Q525.08,602.54 544.58,583.93Q564.08,565.31 564.08,535.92Q564.08,510.45 544.08,493.3Q524.08,476.15 498.08,465.15L498.08,387.69Q508.85,390.31 516.31,397.5Q523.77,404.69 527.15,414.31L559.31,400.85Q551.92,381.39 535.28,369.38Q518.64,357.38 498.08,353.54L498.08,321.92L471.92,321.92L471.92,352.92Q443.92,355.84 424.42,372.84Q404.92,389.84 404.92,418Q404.92,444.23 425.42,462.08Q445.92,479.94 471.92,490.85L471.92,572.54Q454.39,568.31 442.81,556.5Q431.23,544.69 426.85,528.69L395.31,542.15Q403.31,568.81 423.31,585.94Q443.31,603.08 471.92,607.08L471.92,638.08ZM498.08,571.92L498.08,500.92Q510.59,506.63 520.26,514.62Q529.92,522.61 529.92,537.15Q529.92,553.77 520.96,561.04Q512,568.31 498.08,571.92ZM471.92,454.08Q459.77,448.31 449.42,440.35Q439.08,432.39 439.08,418Q439.08,403 449.23,396.04Q459.39,389.08 471.92,387.08L471.92,454.08ZM320,737.88Q212.24,737.88 137.18,662.83Q62.12,587.77 62.12,480.02Q62.12,372.27 137.18,297.1Q212.24,221.92 320,221.92L640,221.92Q747.84,221.92 822.96,297.02Q898.08,372.12 898.08,479.92Q898.08,587.73 822.96,662.81Q747.84,737.88 640,737.88L320,737.88ZM320,681.92L640,681.92Q723.88,681.92 783,622.86Q842.11,563.79 842.11,479.99Q842.11,396.19 783,337.04Q723.88,277.89 640,277.89L320,277.89Q236.2,277.89 177.14,336.99Q118.08,396.09 118.08,479.95Q118.08,563.81 177.14,622.86Q236.2,681.92 320,681.92ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960"
|
|
||||||
android:tint="?attr/colorControlNormal">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M378.27,812.73L378.27,737.88L263.42,737.88L263.42,681.92L338.27,681.92L338.27,277.89L263.42,277.89L263.42,221.83L378.27,221.83L378.27,147.27L434.23,147.27L434.23,221.92L516.77,221.92L516.77,147.27L572.73,147.27L572.73,224.83L572.73,224.83Q625.11,232.5 658.92,271.59Q692.73,310.67 692.73,364.97Q692.73,395.54 681.33,421.87Q669.92,448.19 650.54,466.39Q688.96,483.54 710.84,517.96Q732.73,552.38 732.73,595.24Q732.73,655.5 691.46,696.69Q650.2,737.88 589.85,737.88L572.73,737.88L572.73,812.73L516.77,812.73L516.77,737.88L434.23,737.88L434.23,812.73L378.27,812.73ZM394.23,451.92L549.85,451.92Q586.57,451.92 611.67,426.89Q636.77,401.86 636.77,364.99Q636.77,328.12 611.67,303Q586.57,277.89 549.85,277.89L394.23,277.89L394.23,451.92ZM394.23,681.92L589.85,681.92Q626.57,681.92 651.67,656.89Q676.77,631.86 676.77,594.99Q676.77,558.11 651.67,533Q626.57,507.88 589.85,507.88L394.23,507.88L394.23,681.92Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorOnSurface"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M9.461 3C5.183 3 3 5.464 3 10.064v3.213 6.438L7.19 15.52v-4.902c0-1.906 .505-3.118 2.199-3.391
|
|
||||||
.592-.116 1.824-.075 2.607-.075v2.911c0 .027 .004 .074 .01 .098 .033 .118 .139 .204 .266 .204 .071 0 .138-.037
|
|
||||||
.207-.105l7.262-7.259-4.875-.001zM21 4.285L16.81 8.48v4.902c0 1.906-.505 3.118-2.199 3.391-.592 .116-1.824 .075
|
|
||||||
-2.607 .075v-2.911c0-.026-.004-.074-.01-.098-.033-.118-.139-.204-.266-.204-.071 0-.138 .037-.207 .105L4.258 20.999
|
|
||||||
9.133 21H14.539C18.817 21 21 18.536 21 13.936v-3.214z" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
@@ -1,22 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorOnSurface"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M11.393 1.347L8.018 1.869 5.253 13.43c-.16 .682-.244 1.325-.251 1.927-.007 .604 .116 1.136 .37 1.6
|
|
||||||
.254 .465 .675 .831 1.262 1.1 .588 .268 1.397 .402 2.428 .402v-.002L9.716 15.781C9.339 15.752 9.045 15.687 8.834
|
|
||||||
15.585 8.624 15.484 8.475 15.35 8.388 15.183 8.301 15.016 8.261 14.823 8.269 14.605c.007-.217 .04-.457 .098-.718zm
|
|
||||||
5.201 5.184c-.871 0-1.68 .069-2.428 .207-.747 .138-1.412 .294-1.992 .468L8.559 22.27H11.782l.98-3.941c.493 .087
|
|
||||||
.987 .13 1.48 .13 1.015 0 1.956-.178 2.82-.534 .864-.355 1.603-.851 2.22-1.491 .617-.639 1.099-1.397 1.448-2.276
|
|
||||||
.348-.878 .522-1.846 .523-2.905 0 0 0-.001 0-.001 0 0 0-.001 0-.001C21.252 10.601 21.162 9.987 20.98 9.415 20.799
|
|
||||||
8.842 20.519 8.342 20.142 7.913 19.765 7.485 19.283 7.147 18.695 6.901 18.107 6.654 17.407 6.53 16.594 6.53Zm-.414
|
|
||||||
2.722c.682 0 1.161 .218 1.437 .653 .275 .436 .413 .966 .413 1.59 0 .639-.091 1.223-.272 1.752-.182 .53-.436 .984
|
|
||||||
-.762 1.361-.327 .378-.723 .671-1.187 .881-.465 .211-.98 .316-1.546 .316-.363 0-.667-.029-.914-.087l1.523-6.335c
|
|
||||||
.406-.087 .842-.131 1.307-.131z" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
@@ -1,14 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorOnSurface"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="m11.065 16.274l1.039-3.913 2.46-.899 .612-2.3-.021-.057-2.422 .885 1.745-6.571H9.53L7.248 11.994
|
|
||||||
5.342 12.69 4.713 15.061 6.617 14.366 5.272 19.419H18.443l.844-3.145h-8.222" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
@@ -1,16 +0,0 @@
|
|||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorOnSurface"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M12 2A10 10 0 0 0 2 12A10 10 0 0 0 12 22A10 10 0 0 0 18.043 19.951L15.469 17.377A6.4 6.4 0 0 1 12
|
|
||||||
18.4A6.4 6.4 0 0 1 5.6 12A6.4 6.4 0 0 1 12 5.6A6.4 6.4 0 0 1 15.465 6.627L18.047 4.045A10 10 0 0 0 12 2zM19.951
|
|
||||||
5.957L17.377 8.531A6.4 6.4 0 0 1 18.4 12A6.4 6.4 0 0 1 17.373 15.465L19.955 18.047A10 10 0 0 0 22 12A10 10 0 0 0
|
|
||||||
19.951 5.957z" />
|
|
||||||
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M480,614.48Q471.54,614.48 463.6,611.13Q455.65,607.78 449.43,601.57L306.85,458.98Q294.41,446.54 294.54,429.15Q294.67,411.76 306.85,399.33Q319.52,386.65 336.92,386.52Q354.33,386.39 367,399.07L437,469.07L437,204.87Q437,187.22 449.67,174.54Q462.35,161.87 480,161.87Q497.65,161.87 510.33,174.54Q523,187.22 523,204.87L523,469.07L593,399.07Q605.43,386.39 622.84,386.4Q640.24,386.41 652.91,399.09Q665.33,411.76 665.46,429.03Q665.59,446.3 652.91,458.98L510.57,601.57Q504.35,607.78 496.4,611.13Q488.46,614.48 480,614.48ZM247.87,798.13Q212.09,798.13 186.98,773.02Q161.87,747.91 161.87,712.13L161.87,637.63Q161.87,619.98 174.54,607.3Q187.22,594.63 204.87,594.63Q222.52,594.63 235.2,607.3Q247.87,619.98 247.87,637.63L247.87,712.13Q247.87,712.13 247.87,712.13Q247.87,712.13 247.87,712.13L712.13,712.13Q712.13,712.13 712.13,712.13Q712.13,712.13 712.13,712.13L712.13,637.63Q712.13,619.98 724.8,607.3Q737.48,594.63 755.13,594.63Q772.78,594.63 785.46,607.3Q798.13,619.98 798.13,637.63L798.13,712.13Q798.13,747.91 773.02,773.02Q747.91,798.13 712.13,798.13L247.87,798.13Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M176.26,772.73Q147.91,772.73 127.99,752.81Q108.08,732.9 108.08,704.54L108.08,255.46Q108.08,227.1 127.99,207.19Q147.91,187.27 176.26,187.27L783.74,187.27Q812.09,187.27 832.01,207.19Q851.92,227.1 851.92,255.46L851.92,704.54Q851.92,732.9 832.01,752.81Q812.09,772.73 783.74,772.73L176.26,772.73ZM795.96,296.88L498.35,487.96Q493.73,490.27 489.27,491.77Q484.81,493.27 480,493.27Q475.19,493.27 470.73,491.77Q466.27,490.27 461.85,487.96L164.04,296.88L164.04,704.46Q164.04,709.85 167.5,713.31Q170.96,716.77 176.35,716.77L783.65,716.77Q789.04,716.77 792.5,713.31Q795.96,709.85 795.96,704.46L795.96,296.88ZM480,440.81L789,243.23L171,243.23L480,440.81ZM164.04,296.88L164.04,305.61Q164.04,301.33 164.04,295.14Q164.04,288.96 164.04,282.15Q164.04,269.59 164.04,263Q164.04,256.42 164.04,263.96L164.04,243.23L164.04,243.23L164.04,264Q164.04,257.19 164.04,262.73Q164.04,268.27 164.04,281.69Q164.04,289.17 164.04,295.58Q164.04,302 164.04,305.61L164.04,296.88L164.04,704.46Q164.04,709.85 164.04,713.31Q164.04,716.77 164.04,716.77L164.04,716.77Q164.04,716.77 164.04,713.31Q164.04,709.85 164.04,704.46L164.04,296.88Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorError"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M19.66,3.99c-2.64,-1.8 -5.9,-0.96 -7.66,1.1 -1.76,-2.06 -5.02,-2.91 -7.66,-1.1 -1.4,0.96 -2.28,2.58 -2.34,4.29 -0.14,3.88 3.3,6.99 8.55,11.76l0.1,0.09c0.76,0.69 1.93,0.69 2.69,-0.01l0.11,-0.1c5.25,-4.76 8.68,-7.87 8.55,-11.75 -0.06,-1.7 -0.94,-3.32 -2.34,-4.28zM12.1,18.55l-0.1,0.1 -0.1,-0.1C7.14,14.24 4,11.39 4,8.5 4,6.5 5.5,5 7.5,5c1.54,0 3.04,0.99 3.57,2.36h1.87C13.46,5.99 14.96,5 16.5,5c2,0 3.5,1.5 3.5,3.5 0,2.89 -3.14,5.74 -7.9,10.05z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorError"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M13.35,20.13c-0.76,0.69 -1.93,0.69 -2.69,-0.01l-0.11,-0.1C5.3,15.27 1.87,12.16 2,8.28c0.06,-1.7 0.93,-3.33 2.34,-4.29 2.64,-1.8 5.9,-0.96 7.66,1.1 1.76,-2.06 5.02,-2.91 7.66,-1.1 1.41,0.96 2.28,2.59 2.34,4.29 0.14,3.88 -3.3,6.99 -8.55,11.76l-0.1,0.09z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="512"
|
|
||||||
android:viewportHeight="512">
|
|
||||||
<path
|
|
||||||
android:fillColor="?android:textColor"
|
|
||||||
android:pathData="M494.07,281.6l-25.18,-78.08a11,11 0,0 0,-0.61 -2.1L417.78,44.48a20.08,20.08 0,0 0,-19.17 -13.82A19.77,19.77 0,0 0,379.66 44.6L331.52,194.15h-152L131.34,44.59a19.76,19.76 0,0 0,-18.86 -13.94h-0.11a20.15,20.15 0,0 0,-19.12 14L42.7,201.73c0,0.14 -0.11,0.26 -0.16,0.4L16.91,281.61a29.15,29.15 0,0 0,10.44 32.46L248.79,476.48a11.25,11.25 0,0 0,13.38 -0.07L483.65,314.07a29.13,29.13 0,0 0,10.42 -32.47m-331,-64.51L224.8,408.85 76.63,217.09m209.64,191.8 l59.19,-183.84 2.55,-8h86.52L300.47,390.44M398.8,59.31l43.37,134.83H355.35M324.16,217l-43,133.58L255.5,430.14 186.94,217M112.27,59.31l43.46,134.83H69M40.68,295.58a6.19,6.19 0,0 1,-2.21 -6.9l19,-59L197.08,410.27M470.34,295.58 L313.92,410.22l0.52,-0.69L453.5,229.64l19,59a6.2,6.2 0,0 1,-2.19 6.92" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960"
|
|
||||||
android:tint="?attr/colorControlNormal">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M478.92,812.73Q361.13,812.73 271.1,740.48Q181.08,668.23 155.15,555.95Q151.92,544.46 158.64,534.62Q165.36,524.78 177.58,523.15Q189.04,521.73 198.21,527.79Q207.38,533.85 210.73,545.41Q233.08,637.77 307.69,697.27Q382.31,756.77 478.97,756.77Q594.27,756.77 674.98,676.07Q755.69,595.37 755.69,480.09Q755.69,364.31 674.77,283.77Q593.86,203.23 478.11,203.23Q414.16,203.23 357.98,231.69Q301.81,260.15 261.42,309.23L335.54,309.23Q347.17,309.23 355.39,317.47Q363.62,325.71 363.62,337.13Q363.62,348.77 355.39,356.98Q347.17,365.19 335.54,365.19L203.46,365.19Q189.21,365.19 179.32,355.2Q169.43,345.21 169.43,330.96L169.43,198.81Q169.43,187.43 177.55,179.21Q185.68,171 197.32,171Q208.96,171 217.18,179.24Q225.39,187.47 225.39,198.88L225.39,264.96Q273.27,209.12 338.91,178.19Q404.55,147.27 478.03,147.27Q547.3,147.27 608.07,173.48Q668.84,199.69 713.94,244.79Q759.04,289.89 785.34,350.32Q811.65,410.75 811.65,479.95Q811.65,549.15 785.34,609.63Q759.04,670.11 713.94,715.21Q668.84,760.31 608.38,786.52Q547.91,812.73 478.92,812.73ZM509.27,468.61L617.96,577.35Q626.08,585.66 626.27,597.12Q626.46,608.58 617.96,617.17Q609.46,625.77 598.1,625.77Q586.73,625.77 578.23,617.08L463.37,501.39Q458.23,496.27 455.77,490.21Q453.31,484.15 453.31,477.38L453.31,311.92Q453.31,300.51 461.44,292.27Q469.57,284.04 481.21,284.04Q492.85,284.04 501.06,292.27Q509.27,300.51 509.27,311.92L509.27,468.61Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M204,831Q173.06,831 151.03,808.97Q129,786.94 129,756L129,204Q129,173.06 151.03,151.03Q173.06,129 204,129L756,129Q786.94,129 808.97,151.03Q831,173.06 831,204L831,756Q831,786.94 808.97,808.97Q786.94,831 756,831L204,831ZM204,756L756,756Q756,756 756,756Q756,756 756,756L756,204Q756,204 756,204Q756,204 756,204L204,204Q204,204 204,204Q204,204 204,204L204,756Q204,756 204,756Q204,756 204,756ZM204,756Q204,756 204,756Q204,756 204,756L204,204Q204,204 204,204Q204,204 204,204L204,204Q204,204 204,204Q204,204 204,204L204,756Q204,756 204,756Q204,756 204,756L204,756ZM285.5,677L675.31,677Q687,677 692.5,666.75Q698,656.5 690.5,647L583,504Q577.35,496.5 567.92,496.5Q558.5,496.5 553,504L450,641L377,544.5Q371.35,537 361.92,537Q352.5,537 347,544.5L270.64,646.93Q263,656.5 268.63,666.75Q274.25,677 285.5,677Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,9 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="?android:textColor"
|
|
||||||
android:pathData="M13.8521,0.7714C12.3162,0.2228 11.0572,0.3718 9.89,0.7524L9.9406,16.1462c0.8343,0.7743 3.0648,0.8543 3.8984,0.0768V9.216l5.2569,7.0811c0.9244,0.5188 4.1768,0.2319 4.162,-1.317L17.873,8.196 23.352,1.146C22.8842,0.3544 19.6824,0.1619 18.7887,1.003L13.84,7.22ZM4.834,4.005C4.7874,4.0099 4.744,4.0307 4.711,4.064L3.145,5.63C3.0793,5.696 3.0669,5.7982 3.115,5.878L4.949,8.9C4.6205,9.4525 4.3613,10.0432 4.177,10.659l-3.367,0.7c-0.0944,0.0195 -0.1621,0.1026 -0.162,0.199v2.215c0,0.093 0.064,0.174 0.155,0.196l3.268,0.8c0.1709,0.7107 0.4405,1.394 0.801,2.03L2.98,19.683c-0.0521,0.0804 -0.0408,0.1863 0.027,0.254l1.566,1.567c0.0663,0.0659 0.1689,0.0782 0.249,0.03l2.964,-1.8c0.582,0.336 1.21,0.6 1.874,0.78l0.692,3.325C10.372,23.933 10.454,24 10.55,24h2.215c0.0937,0.0003 0.1752,-0.0639 0.197,-0.155l0.815,-3.332c0.6758,-0.1827 1.3239,-0.4555 1.927,-0.811l2.922,1.915c0.08,0.053 0.186,0.042 0.254,-0.026l1.567,-1.566c0.0661,-0.0658 0.0784,-0.1683 0.03,-0.248L19.41,18.019C18.9297,17.2055 18.038,15.026 17.371,15.8 15.3819,19.6985 10.1369,20.4613 7.1197,17.291 4.1025,14.1206 5.1238,8.9198 9.116,7.126 9.5319,6.8182 7.957,5.999 7.957,5.999L7.956,5.997 4.966,4.037C4.9271,4.0111 4.8805,3.9995 4.834,4.004Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960"
|
|
||||||
android:tint="?attr/colorControlNormal">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M479.76,851.92Q403.15,851.92 335.38,822.71Q267.6,793.49 217.05,742.95Q166.51,692.4 137.29,624.63Q108.08,556.85 108.08,480.24Q108.08,402.85 137.29,335.35Q166.51,267.85 217.05,217.23Q267.6,166.6 335.38,137.34Q403.15,108.08 479.76,108.08Q557.15,108.08 624.65,137.35Q692.15,166.62 742.77,217.26Q793.4,267.89 822.66,335.41Q851.92,402.92 851.92,480Q851.92,556.85 822.66,624.63Q793.4,692.4 742.77,742.95Q692.15,793.49 624.65,822.71Q557.15,851.92 479.76,851.92ZM480,797.54Q511.23,756.52 532.4,714.35Q553.58,672.19 566.92,622.88L393.27,622.88Q407,673.92 427.98,715.89Q448.97,757.86 480,797.54ZM407.39,785.73Q383.58,751.42 364.71,709.23Q345.85,667.04 335.6,622.88L199.19,622.88Q230.88,686.5 284.9,728.64Q338.92,770.77 407.39,785.73ZM552.61,785.73Q620.88,770.96 675,728.73Q729.12,686.5 761,622.88L624.59,622.88Q612.23,667.42 593.36,709.61Q574.5,751.81 552.61,785.73ZM176.46,566.92L323.85,566.92Q320.08,544.31 318.39,522.79Q316.69,501.27 316.69,480Q316.69,458.73 318.39,437.21Q320.08,415.69 323.85,393.08L176.46,393.08Q170.19,413.96 167.12,435.8Q164.04,457.64 164.04,480Q164.04,502.36 167.12,524.2Q170.19,546.04 176.46,566.92ZM380.31,566.92L579.89,566.92Q583.77,544.31 585.56,523.34Q587.35,502.36 587.35,480Q587.35,457.64 585.56,436.66Q583.77,415.69 579.89,393.08L380.31,393.08Q376.23,415.69 374.44,436.66Q372.65,457.64 372.65,480Q372.65,502.36 374.44,523.34Q376.23,544.31 380.31,566.92ZM636.15,566.92L783.73,566.92Q789.81,546.04 792.89,524.2Q795.96,502.36 795.96,480Q795.96,457.64 792.89,435.8Q789.81,413.96 783.73,393.08L636.15,393.08Q639.92,415.69 641.61,437.21Q643.31,458.73 643.31,480Q643.31,501.27 641.61,522.79Q639.92,544.31 636.15,566.92ZM624.59,337.12L761,337.12Q728.92,272.92 675.77,231.36Q622.61,189.81 552.61,173.88Q576.42,210.11 595,251.73Q613.58,293.35 624.59,337.12ZM393.27,337.12L566.92,337.12Q553,286.65 531.44,243.63Q509.88,200.6 480,162.46Q450.12,200.6 428.56,243.63Q407,286.65 393.27,337.12ZM199.19,337.12L335.6,337.12Q346.42,293.35 365,251.73Q383.58,210.11 407.39,173.88Q337.19,190 284.14,231.56Q231.08,273.11 199.19,337.12Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M204,831Q173.06,831 151.03,808.97Q129,786.94 129,756L129,204Q129,173.06 151.03,151.03Q173.06,129 204,129L439.5,129Q455,129 466,140Q477,151 477,166.5Q477,182 466,193Q455,204 439.5,204L204,204Q204,204 204,204Q204,204 204,204L204,756Q204,756 204,756Q204,756 204,756L756,756Q756,756 756,756Q756,756 756,756L756,520.5Q756,505 767,494Q778,483 793.5,483Q809,483 820,494Q831,505 831,520.5L831,756Q831,786.94 808.97,808.97Q786.94,831 756,831L204,831ZM756,256.5L415.5,597Q405,607.5 389.75,607.5Q374.5,607.5 363.5,596.5Q352.5,585.5 352.5,570.25Q352.5,555 363.38,544.12L703.5,204L589.5,204Q574,204 563,193Q552,182 552,166.5Q552,151 563,140Q574,129 589.5,129L831,129L831,370.5Q831,386 820,397Q809,408 793.5,408Q778,408 767,397Q756,386 756,370.5L756,256.5Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorOnSurface"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M22.42,11.34l-1.86,-2.12 0.26,-2.81c0.05,-0.5 -0.29,-0.96 -0.77,-1.07l-2.76,-0.63 -1.44,-2.43c-0.26,-0.43 -0.79,-0.61 -1.25,-0.41L12,3 9.41,1.89c-0.46,-0.2 -1,-0.02 -1.25,0.41L6.71,4.72l-2.75,0.62c-0.49,0.11 -0.83,0.56 -0.78,1.07l0.26,2.8 -1.86,2.13c-0.33,0.38 -0.33,0.94 0,1.32l1.86,2.12 -0.26,2.82c-0.05,0.5 0.29,0.96 0.77,1.07l2.76,0.63 1.44,2.42c0.26,0.43 0.79,0.61 1.26,0.41L12,21l2.59,1.11c0.46,0.2 1,0.02 1.25,-0.41l1.44,-2.43 2.76,-0.63c0.49,-0.11 0.82,-0.57 0.77,-1.07l-0.26,-2.81 1.86,-2.12c0.34,-0.36 0.34,-0.92 0.01,-1.3zM13,17h-2v-2h2v2zM12,13c-0.55,0 -1,-0.45 -1,-1L11,8c0,-0.55 0.45,-1 1,-1s1,0.45 1,1v4c0,0.55 -0.45,1 -1,1z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960"
|
|
||||||
android:tint="?attr/colorControlNormal">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M452.12,643.58L452.12,435.19L508.08,435.19L508.08,643.58L452.12,643.58ZM480.11,365.5Q467.08,365.5 458.19,356.72Q449.31,347.94 449.31,334.91Q449.31,321.89 458.09,313Q466.87,304.12 479.89,304.12Q492.92,304.12 501.81,312.9Q510.69,321.67 510.69,334.7Q510.69,347.73 501.91,356.62Q493.13,365.5 480.11,365.5ZM294.65,891.92Q266.29,891.92 246.38,872.01Q226.46,852.09 226.46,823.75L226.46,136.25Q226.46,107.91 246.38,87.99Q266.29,68.08 294.65,68.08L665.35,68.08Q693.71,68.08 713.62,87.99Q733.54,107.91 733.54,136.25L733.54,823.75Q733.54,852.09 713.62,872.01Q693.71,891.92 665.35,891.92L294.65,891.92ZM282.42,784.35L282.42,823.65Q282.42,828.27 286.27,832.11Q290.12,835.96 294.73,835.96L665.27,835.96Q669.88,835.96 673.73,832.11Q677.58,828.27 677.58,823.65L677.58,784.35L282.42,784.35ZM282.42,728.39L677.58,728.39L677.58,231.61L282.42,231.61L282.42,728.39ZM282.42,175.65L677.58,175.65L677.58,136.35Q677.58,131.73 673.73,127.89Q669.88,124.04 665.27,124.04L294.73,124.04Q290.12,124.04 286.27,127.89Q282.42,131.73 282.42,136.35L282.42,175.65ZM282.42,175.65L282.42,136.35Q282.42,131.73 282.42,127.89Q282.42,124.04 282.42,124.04L282.42,124.04Q282.42,124.04 282.42,127.89Q282.42,131.73 282.42,136.35L282.42,175.65L282.42,175.65ZM282.42,784.35L282.42,784.35L282.42,823.65Q282.42,828.27 282.42,832.11Q282.42,835.96 282.42,835.96L282.42,835.96Q282.42,835.96 282.42,832.11Q282.42,828.27 282.42,823.65L282.42,784.35Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M480,468.5Q423.56,468.5 383.8,428.74Q344.04,388.98 344.04,332.54Q344.04,276.1 383.8,236.34Q423.56,196.58 480,196.58Q536.44,196.58 576.2,236.34Q615.96,276.1 615.96,332.54Q615.96,388.98 576.2,428.74Q536.44,468.5 480,468.5ZM187.27,705.08L187.27,677.68Q187.27,649.09 202.57,625.11Q217.88,601.13 244.25,588.06Q302.86,559.79 361.8,545.39Q420.73,531 480.01,531Q539.28,531 598.26,545.39Q657.23,559.77 715.8,588.05Q742.15,601.12 757.44,625.1Q772.73,649.09 772.73,677.67L772.73,705.08Q772.73,728.72 756.18,745.26Q739.64,761.81 716,761.81L244,761.81Q220.36,761.81 203.82,745.26Q187.27,728.72 187.27,705.08ZM243.23,705.85L716.77,705.85L716.77,677.78Q716.77,665.39 709.73,654.98Q702.69,644.58 690.31,637.85Q638.87,612.69 586.23,599.83Q533.58,586.96 479.98,586.96Q426.02,586.96 373.55,599.83Q321.08,612.69 269.89,637.85Q257.31,644.58 250.27,655.02Q243.23,665.46 243.23,677.73L243.23,705.85ZM480,412.54Q513,412.54 536.5,389.04Q560,365.54 560,332.54Q560,299.54 536.5,276.04Q513,252.54 480,252.54Q447,252.54 423.5,276.04Q400,299.54 400,332.54Q400,365.54 423.5,389.04Q447,412.54 480,412.54ZM480,332.54Q480,332.54 480,332.54Q480,332.54 480,332.54Q480,332.54 480,332.54Q480,332.54 480,332.54Q480,332.54 480,332.54Q480,332.54 480,332.54Q480,332.54 480,332.54Q480,332.54 480,332.54ZM480,705.85L480,705.85Q480,705.85 480,705.85Q480,705.85 480,705.85Q480,705.85 480,705.85Q480,705.85 480,705.85Q480,705.85 480,705.85Q480,705.85 480,705.85Q480,705.85 480,705.85Q480,705.85 480,705.85L480,705.85L480,705.85Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960"
|
|
||||||
android:tint="?attr/colorControlNormal">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M282.33,694.34Q193.1,694.34 130.59,631.87Q68.08,569.39 68.08,480.14Q68.08,390.89 130.6,328.27Q193.11,265.66 282.42,265.66Q343.55,265.66 395.78,298.41Q448,331.16 474.08,384.85L823.65,384.85Q851.81,384.85 871.87,404.9Q891.92,424.96 891.92,453.12L891.92,506.88Q891.92,535.05 871.87,555.1Q851.81,575.15 823.65,575.15L812.73,575.15L812.73,626.08Q812.73,654.24 792.67,674.29Q772.62,694.34 744.46,694.34L689.89,694.34Q661.72,694.34 641.67,674.29Q621.62,654.24 621.62,626.08L621.62,575.15L474.08,575.15Q448,628.84 395.75,661.59Q343.5,694.34 282.33,694.34ZM282.48,638.39Q348.16,638.39 387.59,598.14Q427.02,557.89 434.9,519.19L677.8,519.19L677.8,626.08Q677.8,631.46 681.27,634.92Q684.73,638.39 690.11,638.39L744.46,638.39Q749.85,638.39 753.31,634.92Q756.77,631.46 756.77,626.08L756.77,519.19L823.65,519.19Q829.04,519.19 832.5,515.73Q835.96,512.27 835.96,506.88L835.96,453.12Q835.96,447.73 832.5,444.27Q829.04,440.81 823.65,440.81L434.81,440.81Q426.92,402.07 387.5,361.84Q348.08,321.61 282.4,321.61Q216.73,321.61 170.38,367.98Q124.04,414.35 124.04,480.02Q124.04,545.69 170.42,592.04Q216.8,638.39 282.48,638.39ZM282.53,541.38Q307.77,541.38 325.79,523.26Q343.81,505.13 343.81,479.89Q343.81,454.65 325.68,436.64Q307.56,418.62 282.32,418.62Q257.08,418.62 239.06,436.74Q221.04,454.87 221.04,480.11Q221.04,505.35 239.16,523.36Q257.29,541.38 282.53,541.38ZM282.42,480Q282.42,480 282.42,480Q282.42,480 282.42,480Q282.42,480 282.42,480Q282.42,480 282.42,480Q282.42,480 282.42,480Q282.42,480 282.42,480L282.42,480L282.42,480L282.42,480L282.42,480L282.42,480L282.42,480L282.42,480Q282.42,480 282.42,480Q282.42,480 282.42,480Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M480.09,851.92Q402.94,851.92 335.03,822.6Q267.11,793.27 216.87,743Q166.64,692.73 137.36,624.95Q108.08,557.16 108.08,480.09Q108.08,402.94 137.4,335.03Q166.73,267.11 217,216.87Q267.27,166.64 335.05,137.36Q402.84,108.08 479.91,108.08Q557.06,108.08 624.97,137.4Q692.89,166.73 743.13,217Q793.36,267.27 822.64,335.05Q851.92,402.84 851.92,479.91Q851.92,557.06 822.6,624.97Q793.27,692.89 743,743.13Q692.73,793.36 624.95,822.64Q557.16,851.92 480.09,851.92ZM480,795.96Q611.9,795.96 703.93,703.94Q795.96,611.92 795.96,479.9Q795.96,473 795.81,466.25Q795.65,459.5 794.96,452.92Q791.24,479.86 771.41,497.33Q751.58,514.81 723.72,514.81L634.55,514.81Q604.54,514.81 583.29,493.77Q562.04,472.73 562.04,442.75L562.04,406.41L417.58,406.41L417.58,333.85Q417.58,303.92 438.63,282.78Q459.69,261.63 489.63,261.63L525.92,261.63L525.92,246.24Q525.92,220.54 541.69,203.67Q557.46,186.81 579.19,180.35Q555.73,172.35 530.91,168.19Q506.1,164.04 480,164.04Q348.1,164.04 256.07,256.07Q164.04,348.1 164.04,480Q164.04,483.08 164.04,485.77Q164.04,488.46 164.42,491.54L335.96,491.54Q396.14,491.54 438.43,533.82Q480.73,576.09 480.73,636.07L480.73,672.42L372.38,672.42L372.38,776.38Q397.61,785.73 424.54,790.85Q451.47,795.96 480,795.96Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960"
|
|
||||||
android:tint="?attr/colorControlNormal">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M202.87,848.13Q165.09,848.13 138.48,821.52Q111.87,794.91 111.87,757.13L111.87,202.87Q111.87,165.09 138.48,138.48Q165.09,111.87 202.87,111.87L645.8,111.87Q664.02,111.87 680.52,118.71Q697.02,125.54 709.7,138.22L821.78,250.3Q834.46,262.98 841.29,279.48Q848.13,295.98 848.13,314.2L848.13,757.13Q848.13,794.91 821.52,821.52Q794.91,848.13 757.13,848.13L202.87,848.13ZM757.13,315.2L644.8,202.87L202.87,202.87Q202.87,202.87 202.87,202.87Q202.87,202.87 202.87,202.87L202.87,757.13Q202.87,757.13 202.87,757.13Q202.87,757.13 202.87,757.13L757.13,757.13Q757.13,757.13 757.13,757.13Q757.13,757.13 757.13,757.13L757.13,315.2ZM480,717.13Q530,717.13 565,682.13Q600,647.13 600,597.13Q600,547.13 565,512.13Q530,477.13 480,477.13Q430,477.13 395,512.13Q360,547.13 360,597.13Q360,647.13 395,682.13Q430,717.13 480,717.13ZM288.37,402.87L557.37,402.87Q576.52,402.87 589.7,389.7Q602.87,376.52 602.87,357.37L602.87,288.37Q602.87,269.22 589.7,256.04Q576.52,242.87 557.37,242.87L288.37,242.87Q269.22,242.87 256.04,256.04Q242.87,269.22 242.87,288.37L242.87,357.37Q242.87,376.52 256.04,389.7Q269.22,402.87 288.37,402.87ZM202.87,315.2L202.87,757.13Q202.87,757.13 202.87,757.13Q202.87,757.13 202.87,757.13L202.87,757.13Q202.87,757.13 202.87,757.13Q202.87,757.13 202.87,757.13L202.87,202.87Q202.87,202.87 202.87,202.87Q202.87,202.87 202.87,202.87L202.87,202.87L202.87,315.2Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M379.74,639.5Q271.46,639.5 196.27,564.37Q121.09,489.24 121.09,381.05Q121.09,272.85 196.21,197.67Q271.34,122.5 379.54,122.5Q487.74,122.5 562.91,197.69Q638.09,272.87 638.09,381.16Q638.09,424.48 625.07,462.87Q612.04,501.26 589.5,530.28L809.2,749.75Q821.76,762.52 821.76,780.05Q821.76,797.59 808.85,810.26Q796.17,822.93 778.52,822.93Q760.87,822.93 748.32,810.39L529.01,590.91Q499.59,613.96 461.21,626.73Q422.83,639.5 379.74,639.5ZM379.59,553.5Q451.93,553.5 502.01,503.42Q552.09,453.35 552.09,381Q552.09,308.65 502.01,258.58Q451.93,208.5 379.59,208.5Q307.24,208.5 257.16,258.58Q207.09,308.65 207.09,381Q207.09,453.35 257.16,503.42Q307.24,553.5 379.59,553.5Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960"
|
|
||||||
android:tint="?attr/colorControlNormal">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M716.88,871Q668.5,871 634.75,837.17Q601,803.33 601,755Q601,748.04 602,740.58Q603,733.11 605,727.15L324,564Q307.5,579.5 287.05,587.75Q266.6,596 244.2,596Q195.5,596 161.75,562.02Q128,528.04 128,479.5Q128,431.38 161.83,397.69Q195.67,364 244,364Q266.65,364 287.32,372Q308,380 324,395L605,231.85Q603,225.89 602,218.42Q601,210.96 601,204Q601,155.67 634.87,121.83Q668.73,88 717.12,88Q765.5,88 799.25,121.69Q833,155.38 833,203.5Q833,252.04 799.17,286.02Q765.33,320 717,320Q694.53,320 674.02,311.75Q653.5,303.5 637,288L356,451Q358,457.29 359,465.14Q360,473 360,479.96Q360,486.93 359,494.39Q358,501.85 356,507.82L637,671Q653.5,655.5 673.95,647.25Q694.4,639 716.8,639Q765.5,639 799.25,672.98Q833,706.96 833,755.5Q833,803.63 799.13,837.31Q765.27,871 716.88,871ZM717,245Q734,245 746,233Q758,221 758,204Q758,187 746,175Q734,163 717,163Q700,163 688,175Q676,187 676,204Q676,221 688,233Q700,245 717,245ZM244,521Q261,521 273,509Q285,497 285,480Q285,463 273,451Q261,439 244,439Q227,439 215,451Q203,463 203,480Q203,497 215,509Q227,521 244,521ZM717,796Q734,796 746,784Q758,772 758,755Q758,738 746,726Q734,714 717,714Q700,714 688,726Q676,738 676,755Q676,772 688,784Q700,796 717,796ZM717,204Q717,204 717,204Q717,204 717,204Q717,204 717,204Q717,204 717,204Q717,204 717,204Q717,204 717,204Q717,204 717,204Q717,204 717,204ZM244,480Q244,480 244,480Q244,480 244,480Q244,480 244,480Q244,480 244,480Q244,480 244,480Q244,480 244,480Q244,480 244,480Q244,480 244,480ZM717,755Q717,755 717,755Q717,755 717,755Q717,755 717,755Q717,755 717,755Q717,755 717,755Q717,755 717,755Q717,755 717,755Q717,755 717,755Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,11 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:autoMirrored="true"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M164.63,726.26Q146.89,726.26 134.38,713.59Q121.87,700.91 121.87,683.2Q121.87,665.49 134.38,653Q146.89,640.5 164.63,640.5L322.8,640.5Q340.54,640.5 353.05,653.17Q365.57,665.85 365.57,683.56Q365.57,701.27 353.05,713.77Q340.54,726.26 322.8,726.26L164.63,726.26ZM164.63,528Q146.89,528 134.38,515.33Q121.87,502.65 121.87,484.94Q121.87,467.23 134.38,454.73Q146.89,442.24 164.63,442.24L559.22,442.24Q576.95,442.24 589.47,454.91Q601.98,467.59 601.98,485.3Q601.98,503.01 589.47,515.5Q576.95,528 559.22,528L164.63,528ZM164.63,329.74Q146.89,329.74 134.38,317.07Q121.87,304.39 121.87,286.68Q121.87,268.97 134.54,256.47Q147.22,243.98 164.87,243.98L795.37,243.98Q813.11,243.98 825.62,256.65Q838.13,269.33 838.13,287.04Q838.13,304.75 825.46,317.24Q812.78,329.74 795.13,329.74L164.63,329.74Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorOnSurface"
|
|
||||||
android:viewportWidth="24"
|
|
||||||
android:viewportHeight="24">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M8.0855,18.6109L14.0817,4.6064C14.2991,4.0987 14.8868,3.8633 15.3946,4.0807C15.866,4.2826 16.1026,4.8038 15.96,5.2837L15.9202,5.3936L9.9241,19.3981C9.7067,19.9058 9.1189,20.1412 8.6112,19.9238C8.1398,19.722 7.9032,19.2007 8.0458,18.7208L8.0855,18.6109L14.0817,4.6064L8.0855,18.6109ZM2.2929,11.2929L6.2929,7.2929C6.6834,6.9024 7.3166,6.9024 7.7071,7.2929C8.0676,7.6534 8.0953,8.2206 7.7903,8.6129L7.7071,8.7071L4.4142,12L7.7071,15.2929C8.0976,15.6834 8.0976,16.3166 7.7071,16.7071C7.3466,17.0676 6.7794,17.0953 6.3871,16.7903L6.2929,16.7071L2.2929,12.7071C1.9324,12.3466 1.9047,11.7794 2.2097,11.3871L2.2929,11.2929L6.2929,7.2929L2.2929,11.2929ZM16.2921,7.2917C16.6526,6.9312 17.2198,6.9035 17.6121,7.2085L17.7063,7.2917L21.7071,11.2929C22.0678,11.6536 22.0953,12.2211 21.7899,12.6134L21.7066,12.7076L17.7058,16.7031C17.315,17.0934 16.6819,17.0929 16.2916,16.7022C15.9313,16.3414 15.904,15.7742 16.2093,15.3821L16.2925,15.288L19.5854,11.9995L16.292,8.7059C15.9015,8.3153 15.9015,7.6822 16.2921,7.2917Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M246.52,481.5Q246.52,524.57 262.91,565.99Q279.3,607.41 314.35,642.93L325.43,654.02L325.43,600.17Q325.43,583.96 337.03,572.48Q348.63,561 364.85,561Q381.07,561 392.54,572.48Q404.02,583.96 404.02,600.17L404.02,754.78Q404.02,772.43 391.35,785.11Q378.67,797.78 361.02,797.78L206.65,797.78Q190.43,797.78 178.96,786.18Q167.48,774.59 167.48,758.37Q167.48,742.15 179.08,730.67Q190.67,719.2 206.89,719.2L269.59,719.2L254.7,704.59Q204.96,656.65 182.74,598.68Q160.52,540.72 160.52,481.5Q160.52,387.61 209.55,310.08Q258.59,232.54 340.96,192.8Q356.13,185.33 371.21,193.38Q386.28,201.43 391.76,218.59Q397,235.24 390.67,250.78Q384.35,266.33 368.93,274.78Q312.61,305.57 279.57,360.62Q246.52,415.67 246.52,481.5ZM713.48,478.5Q713.48,435.43 697.09,394.01Q680.7,352.59 645.65,317.07L634.57,305.98L634.57,359.83Q634.57,376.04 623.09,387.52Q611.61,399 595.39,399Q579.17,399 567.58,387.4Q555.98,375.8 555.98,359.59L555.98,205.22Q555.98,187.57 568.65,174.89Q581.33,162.22 598.98,162.22L753.35,162.22Q769.57,162.22 781.04,173.7Q792.52,185.17 792.52,201.39Q792.52,217.61 781.04,229.21Q769.57,240.8 753.35,240.8L690.41,240.8L705.3,255.41Q754.02,304.37 776.75,361.83Q799.48,419.28 799.48,478.5Q799.48,572.39 750.45,649.42Q701.41,726.46 619.54,766.7Q604.37,774.17 589.04,766.37Q573.72,758.57 568.24,741.41Q563,724.76 569.33,709.22Q575.65,693.67 591.07,685.22Q647.39,654.43 680.43,599.38Q713.48,544.33 713.48,478.5Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M231.31,482.27Q231.31,528.84 248.8,573.11Q266.3,617.38 303.88,654.89L331.69,682.69L331.69,600.23Q331.69,588.79 339.92,580.57Q348.14,572.35 359.78,572.35Q371.23,572.35 379.44,580.57Q387.65,588.79 387.65,600.23L387.65,747.04Q387.65,761.29 377.66,771.28Q367.67,781.27 353.61,781.27L206.81,781.27Q195.17,781.27 186.95,773.04Q178.73,764.82 178.73,753.18Q178.73,741.73 186.95,733.52Q195.17,725.31 206.81,725.31L295.65,725.31L264.96,695.04Q217.77,649.11 196.56,594Q175.35,538.88 175.35,482.27Q175.35,393.85 221,320.37Q266.65,246.89 343.65,207.58Q354.08,202.31 364.9,206.69Q375.73,211.08 379.77,222.11Q383.42,233.08 379.36,243.53Q375.29,253.98 364.81,259.81Q303.15,292.46 267.23,351.81Q231.31,411.16 231.31,482.27ZM728.89,477.54Q728.89,431.16 711.29,386.89Q693.7,342.62 656.12,305.11L628.31,277.31L628.31,359.58Q628.31,371.21 620.08,379.43Q611.86,387.65 600.41,387.65Q588.77,387.65 580.56,379.43Q572.35,371.21 572.35,359.58L572.35,212.77Q572.35,198.71 582.34,188.72Q592.33,178.73 606.58,178.73L753.38,178.73Q764.83,178.73 773.05,186.96Q781.27,195.18 781.27,206.63Q781.27,218.27 773.05,226.48Q764.83,234.69 753.38,234.69L664.34,234.69L695.04,264.96Q741.43,311.35 763.14,366.16Q784.85,420.96 784.85,477.6Q784.85,566.15 739.38,639.13Q693.92,712.11 617.23,751.54Q606.61,756.81 595.64,752.86Q584.65,748.92 580.81,737.7Q576.77,726.92 580.96,716.44Q585.15,705.96 595.39,700.19Q656.85,667.54 692.87,608.19Q728.89,548.84 728.89,477.54Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M479.23,851.92Q402.77,851.92 335.09,822.71Q267.4,793.49 216.96,742.95Q166.51,692.4 137.29,624.63Q108.08,556.85 108.08,479.99Q108.08,402.15 138.06,334.42Q168.04,266.69 219.71,216.34Q271.39,166 340.81,137.04Q410.23,108.08 488.57,108.08Q562.06,108.08 627.64,133.25Q693.23,158.42 743.02,202.57Q792.81,246.71 822.37,307.49Q851.92,368.27 851.92,438.96Q851.92,540.44 791.96,597.39Q732,654.34 637.61,654.34L566.73,654.34Q548.35,654.34 537.15,666.08Q525.96,677.81 525.96,694.33Q525.96,713.41 540.96,734.9Q555.96,756.39 555.96,782.81Q555.96,816.27 536.46,834.09Q516.96,851.92 479.23,851.92ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480L480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM262.38,507.58Q282.7,507.58 296.35,493.97Q310,480.36 310,460.04Q310,439.72 296.39,426.07Q282.78,412.42 262.46,412.42Q242.15,412.42 228.5,426.03Q214.85,439.64 214.85,459.96Q214.85,480.28 228.46,493.93Q242.07,507.58 262.38,507.58ZM381.58,349.19Q401.89,349.19 415.54,335.58Q429.19,321.97 429.19,301.66Q429.19,281.34 415.58,267.69Q401.97,254.04 381.66,254.04Q361.34,254.04 347.69,267.65Q334.04,281.26 334.04,301.58Q334.04,321.89 347.65,335.54Q361.26,349.19 381.58,349.19ZM579.15,349.19Q599.47,349.19 613.12,335.58Q626.77,321.97 626.77,301.66Q626.77,281.34 613.16,267.69Q599.55,254.04 579.23,254.04Q558.92,254.04 545.27,267.65Q531.62,281.26 531.62,301.58Q531.62,321.89 545.23,335.54Q558.84,349.19 579.15,349.19ZM697.54,507.58Q717.85,507.58 731.5,493.97Q745.15,480.36 745.15,460.04Q745.15,439.72 731.54,426.07Q717.93,412.42 697.62,412.42Q677.3,412.42 663.65,426.03Q650,439.64 650,459.96Q650,480.28 663.61,493.93Q677.22,507.58 697.54,507.58ZM479.31,795.96Q488.96,795.96 494.48,791.15Q500,786.35 500,777.96Q500,763.96 485.4,746.1Q470.81,728.23 470.81,691.81Q470.81,649.54 499.19,623.96Q527.57,598.39 569.26,598.39L637.58,598.39Q708.19,598.39 752.08,557.56Q795.96,516.73 795.96,438.92Q795.96,319.46 703.94,241.75Q611.92,164.04 488.7,164.04Q353.54,164.04 258.79,255.86Q164.04,347.69 164.04,480Q164.04,611.08 256.48,703.52Q348.92,795.96 479.31,795.96Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M508.38,468.65L508.38,310.31Q508.38,298.89 500.14,290.66Q491.9,282.42 480.49,282.42Q468.85,282.42 460.64,290.66Q452.42,298.89 452.42,310.31L452.42,477.38Q452.42,483.96 454.89,490.12Q457.35,496.29 462.48,501.38L598.46,637.92Q606.46,646.19 617.92,646.38Q629.39,646.58 638.08,637.89Q646.58,629.39 646.58,618.12Q646.58,606.85 638.12,598.2L508.38,468.65ZM480.09,851.92Q402.94,851.92 335.03,822.6Q267.11,793.27 216.87,743Q166.64,692.73 137.36,624.95Q108.08,557.16 108.08,480.09Q108.08,402.94 137.4,335.03Q166.73,267.11 217,216.87Q267.27,166.64 335.05,137.36Q402.84,108.08 479.91,108.08Q557.06,108.08 624.97,137.4Q692.89,166.73 743.13,217Q793.36,267.27 822.64,335.05Q851.92,402.84 851.92,479.91Q851.92,557.06 822.6,624.97Q793.27,692.89 743,743.13Q692.73,793.36 624.95,822.64Q557.16,851.92 480.09,851.92ZM480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480Q480,480 480,480ZM480,795.96Q611.08,795.96 703.52,703.52Q795.96,611.08 795.96,480Q795.96,348.92 703.52,256.48Q611.08,164.04 480,164.04Q348.92,164.04 256.48,256.48Q164.04,348.92 164.04,480Q164.04,611.08 256.48,703.52Q348.92,795.96 480,795.96Z" />
|
|
||||||
</vector>
|
|
||||||
@@ -1,10 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="24dp"
|
|
||||||
android:height="24dp"
|
|
||||||
android:tint="?attr/colorControlNormal"
|
|
||||||
android:viewportWidth="960"
|
|
||||||
android:viewportHeight="960">
|
|
||||||
<path
|
|
||||||
android:fillColor="@android:color/white"
|
|
||||||
android:pathData="M703.5,806Q642,806 599,763Q556,720 556,658.5Q556,597 599,554Q642,511 703.5,511Q765,511 808,554Q851,597 851,658.5Q851,720 808,763Q765,806 703.5,806ZM703.47,731Q733.5,731 754.75,709.78Q776,688.55 776,658.53Q776,628.5 754.78,607.25Q733.55,586 703.53,586Q673.5,586 652.25,607.22Q631,628.45 631,658.47Q631,688.5 652.22,709.75Q673.45,731 703.47,731ZM443.5,696L186.5,696Q171,696 160,685Q149,674 149,658.5Q149,643 160,632Q171,621 186.5,621L443.5,621Q459,621 470,632Q481,643 481,658.5Q481,674 470,685Q459,696 443.5,696ZM256.5,449Q195,449 152,406Q109,363 109,301.5Q109,240 152,197Q195,154 256.5,154Q318,154 361,197Q404,240 404,301.5Q404,363 361,406Q318,449 256.5,449ZM256.48,374Q286.5,374 307.75,352.78Q329,331.55 329,301.53Q329,271.5 307.77,250.25Q286.55,229 256.52,229Q226.5,229 205.25,250.22Q184,271.45 184,301.47Q184,331.5 205.23,352.75Q226.45,374 256.48,374ZM773.5,339L516.5,339Q501,339 490,328Q479,317 479,301.5Q479,286 490,275Q501,264 516.5,264L773.5,264Q789,264 800,275Q811,286 811,301.5Q811,317 800,328Q789,339 773.5,339ZM703.5,658.5Q703.5,658.5 703.5,658.5Q703.5,658.5 703.5,658.5Q703.5,658.5 703.5,658.5Q703.5,658.5 703.5,658.5Q703.5,658.5 703.5,658.5Q703.5,658.5 703.5,658.5Q703.5,658.5 703.5,658.5Q703.5,658.5 703.5,658.5ZM256.5,301.5Q256.5,301.5 256.5,301.5Q256.5,301.5 256.5,301.5Q256.5,301.5 256.5,301.5Q256.5,301.5 256.5,301.5Q256.5,301.5 256.5,301.5Q256.5,301.5 256.5,301.5Q256.5,301.5 256.5,301.5Q256.5,301.5 256.5,301.5Z" />
|
|
||||||
</vector>
|
|
||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user