Merge remote-tracking branch 'origin/master'

This commit is contained in:
Iamlooker 2022-04-11 09:08:36 +05:30
commit 5543c78987
22 changed files with 204 additions and 144 deletions

View File

@ -17,8 +17,8 @@ android {
applicationId = 'com.looker.droidify' applicationId = 'com.looker.droidify'
minSdk = 23 minSdk = 23
targetSdk = 32 targetSdk = 32
versionCode = 43 versionCode = 903
versionName = "0.4.3" versionName = "0.9.0"
vectorDrawables.useSupportLibrary = true vectorDrawables.useSupportLibrary = true
javaCompileOptions { javaCompileOptions {
@ -58,7 +58,7 @@ android {
minifyEnabled = false minifyEnabled = false
shrinkResources = false shrinkResources = false
applicationIdSuffix = ".debug" applicationIdSuffix = ".debug"
versionNameSuffix = "-alpha2" versionNameSuffix = "-alpha3"
resValue "string", "application_name", "Neo Store-Debug" resValue "string", "application_name", "Neo Store-Debug"
manifestPlaceholders = [ manifestPlaceholders = [
appIcon : "@mipmap/ic_launcher_debug", appIcon : "@mipmap/ic_launcher_debug",
@ -69,7 +69,7 @@ android {
minifyEnabled = false minifyEnabled = false
shrinkResources = false shrinkResources = false
applicationIdSuffix = ".neo" applicationIdSuffix = ".neo"
versionNameSuffix = "-alpha2" versionNameSuffix = "-alpha3"
resValue "string", "application_name", "Neo Store-alpha" resValue "string", "application_name", "Neo Store-alpha"
manifestPlaceholders = [ manifestPlaceholders = [
appIcon : "@mipmap/ic_launcher_debug", appIcon : "@mipmap/ic_launcher_debug",

View File

@ -9,6 +9,7 @@
<uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_INSTALL_PACKAGES" />
<uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" /> <uses-permission android:name="android.permission.REQUEST_DELETE_PACKAGES" />
<uses-permission android:name="android.permission.QUERY_ALL_PACKAGES" <uses-permission android:name="android.permission.QUERY_ALL_PACKAGES"
<uses-permission android:name="android.permission.REQUEST_IGNORE_BATTERY_OPTIMIZATIONS" />
tools:ignore="QueryAllPackagesPermission" /> tools:ignore="QueryAllPackagesPermission" />
<uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" /> <uses-permission android:name="android.permission.UPDATE_PACKAGES_WITHOUT_USER_ACTION" />

View File

@ -15,8 +15,8 @@ const val TABLE_CATEGORY_NAME = "category"
const val TABLE_CATEGORY_TEMP_NAME = "temporary_category" const val TABLE_CATEGORY_TEMP_NAME = "temporary_category"
const val TABLE_INSTALLED = "installed" const val TABLE_INSTALLED = "installed"
const val TABLE_INSTALLED_NAME = "memory_installed" const val TABLE_INSTALLED_NAME = "memory_installed"
const val TABLE_LOCK = "lock" const val TABLE_IGNORED = "lock"
const val TABLE_LOCK_NAME = "memory_lock" const val TABLE_IGNORED_NAME = "memory_lock"
const val TABLE_PRODUCT = "product" const val TABLE_PRODUCT = "product"
const val TABLE_PRODUCT_NAME = "product" const val TABLE_PRODUCT_NAME = "product"
const val TABLE_PRODUCT_TEMP_NAME = "temporary_product" const val TABLE_PRODUCT_TEMP_NAME = "temporary_product"
@ -43,7 +43,6 @@ const val ROW_ANTIFEATURES = "antiFeatures"
const val ROW_LICENSES = "licenses" const val ROW_LICENSES = "licenses"
const val ROW_DONATES = "donates" const val ROW_DONATES = "donates"
const val ROW_SCREENSHOTS = "screenshots" const val ROW_SCREENSHOTS = "screenshots"
const val ROW_VERSION = "version"
const val ROW_SIGNATURE = "signature" const val ROW_SIGNATURE = "signature"
const val ROW_ID = "_id" const val ROW_ID = "_id"
const val ROW_ENABLED = "enabled" const val ROW_ENABLED = "enabled"

View File

@ -37,7 +37,8 @@ object Preferences {
Key.Theme, Key.Theme,
Key.DefaultTab, Key.DefaultTab,
Key.UpdateNotify, Key.UpdateNotify,
Key.UpdateUnstable Key.UpdateUnstable,
Key.IgnoreIgnoreBatteryOptimization
).map { Pair(it.name, it) }.toMap() ).map { Pair(it.name, it) }.toMap()
fun init(context: Context) { fun init(context: Context) {
@ -178,6 +179,9 @@ object Preferences {
object UpdateNotify : Key<Boolean>("update_notify", Value.BooleanValue(true)) object UpdateNotify : Key<Boolean>("update_notify", Value.BooleanValue(true))
object UpdateUnstable : Key<Boolean>("update_unstable", Value.BooleanValue(false)) object UpdateUnstable : Key<Boolean>("update_unstable", Value.BooleanValue(false))
object IgnoreIgnoreBatteryOptimization :
Key<Boolean>("ignore_ignore_battery_optimization", Value.BooleanValue(false))
} }
sealed class AutoSync(override val valueString: String) : Enumeration<AutoSync> { sealed class AutoSync(override val valueString: String) : Enumeration<AutoSync> {

View File

@ -3,7 +3,7 @@ package com.looker.droidify.content
import android.content.Context import android.content.Context
import android.content.SharedPreferences import android.content.SharedPreferences
import com.looker.droidify.database.DatabaseX import com.looker.droidify.database.DatabaseX
import com.looker.droidify.database.entity.Lock import com.looker.droidify.database.entity.Ignored
import com.looker.droidify.entity.ProductPreference import com.looker.droidify.entity.ProductPreference
import kotlinx.coroutines.CoroutineScope import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.Dispatchers
@ -27,13 +27,13 @@ object ProductPreferences {
db.lockDao.insert(*preferences.all.keys db.lockDao.insert(*preferences.all.keys
.mapNotNull { pName -> .mapNotNull { pName ->
this@ProductPreferences[pName].databaseVersionCode?.let { this@ProductPreferences[pName].databaseVersionCode?.let {
Lock(pName, it) Ignored(pName, it)
} }
} }
.toTypedArray() .toTypedArray()
) )
subject.collect { (packageName, versionCode) -> subject.collect { (packageName, versionCode) ->
if (versionCode != null) db.lockDao.insert(Lock(packageName, versionCode)) if (versionCode != null) db.lockDao.insert(Ignored(packageName, versionCode))
else db.lockDao.delete(packageName) else db.lockDao.delete(packageName)
} }
} }

View File

@ -144,7 +144,7 @@ interface ProductDao : BaseDao<Product> {
builder += """SELECT $TABLE_PRODUCT.rowid AS $ROW_ID, $TABLE_PRODUCT.$ROW_REPOSITORY_ID, builder += """SELECT $TABLE_PRODUCT.rowid AS $ROW_ID, $TABLE_PRODUCT.$ROW_REPOSITORY_ID,
$TABLE_PRODUCT.$ROW_PACKAGE_NAME, $TABLE_PRODUCT.$ROW_LABEL, $TABLE_PRODUCT.$ROW_PACKAGE_NAME, $TABLE_PRODUCT.$ROW_LABEL,
$TABLE_PRODUCT.$ROW_SUMMARY, $TABLE_PRODUCT.$ROW_DESCRIPTION, $TABLE_PRODUCT.$ROW_SUMMARY, $TABLE_PRODUCT.$ROW_DESCRIPTION,
(COALESCE($TABLE_LOCK.$ROW_VERSION_CODE, -1) NOT IN (0, $TABLE_PRODUCT.$ROW_VERSION_CODE) AND (COALESCE($TABLE_IGNORED.$ROW_VERSION_CODE, -1) NOT IN (0, $TABLE_PRODUCT.$ROW_VERSION_CODE) AND
$TABLE_PRODUCT.$ROW_COMPATIBLE != 0 AND $TABLE_PRODUCT.$ROW_COMPATIBLE != 0 AND
$TABLE_PRODUCT.$ROW_VERSION_CODE > COALESCE($TABLE_INSTALLED.$ROW_VERSION_CODE, 0xffffffff) AND $TABLE_PRODUCT.$ROW_VERSION_CODE > COALESCE($TABLE_INSTALLED.$ROW_VERSION_CODE, 0xffffffff) AND
$signatureMatches) AS $ROW_CAN_UPDATE, $TABLE_PRODUCT.$ROW_ICON, $signatureMatches) AS $ROW_CAN_UPDATE, $TABLE_PRODUCT.$ROW_ICON,
@ -173,8 +173,8 @@ interface ProductDao : BaseDao<Product> {
ON $TABLE_PRODUCT.$ROW_REPOSITORY_ID = $TABLE_REPOSITORY.$ROW_ID""" ON $TABLE_PRODUCT.$ROW_REPOSITORY_ID = $TABLE_REPOSITORY.$ROW_ID"""
// Merge the matching locks // Merge the matching locks
builder += """LEFT JOIN $TABLE_LOCK_NAME AS $TABLE_LOCK builder += """LEFT JOIN $TABLE_IGNORED_NAME AS $TABLE_IGNORED
ON $TABLE_PRODUCT.$ROW_PACKAGE_NAME = $TABLE_LOCK.$ROW_PACKAGE_NAME""" ON $TABLE_PRODUCT.$ROW_PACKAGE_NAME = $TABLE_IGNORED.$ROW_PACKAGE_NAME"""
// Merge the matching installed // Merge the matching installed
if (!installed && !updates) builder += "LEFT" if (!installed && !updates) builder += "LEFT"
@ -291,7 +291,7 @@ interface InstalledDao : BaseDao<Installed> {
} }
@Dao @Dao
interface LockDao : BaseDao<Lock> { interface LockDao : BaseDao<Ignored> {
@Query("DELETE FROM memory_lock WHERE packageName = :packageName") @Query("DELETE FROM memory_lock WHERE packageName = :packageName")
fun delete(packageName: String) fun delete(packageName: String)
} }

View File

@ -20,7 +20,7 @@ import kotlinx.coroutines.launch
Category::class, Category::class,
CategoryTemp::class, CategoryTemp::class,
Installed::class, Installed::class,
Lock::class Ignored::class
], version = 6 ], version = 6
) )
@TypeConverters(Converters::class) @TypeConverters(Converters::class)

View File

@ -2,10 +2,11 @@ package com.looker.droidify.database.entity
import androidx.room.Entity import androidx.room.Entity
import androidx.room.PrimaryKey import androidx.room.PrimaryKey
import com.looker.droidify.TABLE_LOCK_NAME import com.looker.droidify.TABLE_IGNORED_NAME
@Entity(tableName = TABLE_LOCK_NAME) // TODO complete renaming to Ignored
data class Lock( @Entity(tableName = TABLE_IGNORED_NAME)
data class Ignored(
@PrimaryKey @PrimaryKey
var packageName: String = "", var packageName: String = "",
var versionCode: Long = 0L var versionCode: Long = 0L

View File

@ -37,6 +37,7 @@ import java.io.File
import java.security.MessageDigest import java.security.MessageDigest
import kotlin.math.roundToInt import kotlin.math.roundToInt
// TODO maybe replace by using WorkManager instead?
class DownloadService : ConnectionService<DownloadService.Binder>() { class DownloadService : ConnectionService<DownloadService.Binder>() {
companion object { companion object {
private const val ACTION_CANCEL = "${BuildConfig.APPLICATION_ID}.intent.action.CANCEL" private const val ACTION_CANCEL = "${BuildConfig.APPLICATION_ID}.intent.action.CANCEL"

View File

@ -3,6 +3,7 @@ package com.looker.droidify.ui.activities
import android.content.Context import android.content.Context
import android.content.Intent import android.content.Intent
import android.os.Bundle import android.os.Bundle
import android.os.PowerManager
import android.view.Menu import android.view.Menu
import android.view.MenuItem import android.view.MenuItem
import android.view.inputmethod.InputMethodManager import android.view.inputmethod.InputMethodManager
@ -31,6 +32,7 @@ import com.looker.droidify.ui.fragments.Source
import com.looker.droidify.ui.viewmodels.MainActivityViewModelX import com.looker.droidify.ui.viewmodels.MainActivityViewModelX
import com.looker.droidify.utility.extension.android.Android import com.looker.droidify.utility.extension.android.Android
import com.looker.droidify.utility.extension.text.nullIfEmpty import com.looker.droidify.utility.extension.text.nullIfEmpty
import com.looker.droidify.utility.showBatteryOptimizationDialog
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
import kotlin.properties.Delegates import kotlin.properties.Delegates
@ -52,6 +54,7 @@ class MainActivityX : AppCompatActivity() {
lateinit var appBarConfiguration: AppBarConfiguration lateinit var appBarConfiguration: AppBarConfiguration
private lateinit var navController: NavController private lateinit var navController: NavController
private val viewModel: MainActivityViewModelX by viewModels() private val viewModel: MainActivityViewModelX by viewModels()
private lateinit var powerManager: PowerManager
val menuSetup = MutableLiveData<Boolean>() val menuSetup = MutableLiveData<Boolean>()
val syncConnection = Connection(SyncService::class.java, onBind = { _, _ -> val syncConnection = Connection(SyncService::class.java, onBind = { _, _ ->
@ -95,6 +98,7 @@ class MainActivityX : AppCompatActivity() {
setupActionBarWithNavController(navController, appBarConfiguration) setupActionBarWithNavController(navController, appBarConfiguration)
binding.bottomNavigation.selectedItemId = currentTab binding.bottomNavigation.selectedItemId = currentTab
powerManager = getSystemService(Context.POWER_SERVICE) as PowerManager
if (savedInstanceState == null && (intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) { if (savedInstanceState == null && (intent.flags and Intent.FLAG_ACTIVITY_LAUNCHED_FROM_HISTORY) == 0) {
handleIntent(intent) handleIntent(intent)
} }
@ -117,6 +121,8 @@ class MainActivityX : AppCompatActivity() {
super.onResume() super.onResume()
if (currentTheme != Preferences[Preferences.Key.Theme].getResId(resources.configuration)) if (currentTheme != Preferences[Preferences.Key.Theme].getResId(resources.configuration))
recreate() recreate()
if (!powerManager.isIgnoringBatteryOptimizations(this.packageName) && !Preferences[Preferences.Key.IgnoreIgnoreBatteryOptimization])
showBatteryOptimizationDialog()
} }
override fun onSupportNavigateUp(): Boolean { override fun onSupportNavigateUp(): Boolean {

View File

@ -30,11 +30,12 @@ import com.looker.droidify.ui.compose.components.RepositoryItem
fun ProductsVerticalRecycler( fun ProductsVerticalRecycler(
productsList: List<Product>?, productsList: List<Product>?,
repositories: Map<Long, Repository>, repositories: Map<Long, Repository>,
modifier: Modifier = Modifier.fillMaxSize(),
onUserClick: (ProductItem) -> Unit = {}, onUserClick: (ProductItem) -> Unit = {},
onFavouriteClick: (ProductItem) -> Unit = {}, onFavouriteClick: (ProductItem) -> Unit = {},
onInstallClick: (ProductItem) -> Unit = {} onInstallClick: (ProductItem) -> Unit = {}
) { ) {
VerticalItemList(list = productsList) { VerticalItemList(list = productsList, modifier = modifier) {
it.toItem().let { item -> it.toItem().let { item ->
ProductsListItem( ProductsListItem(
item, item,
@ -84,7 +85,7 @@ fun RepositoriesRecycler(
@Composable @Composable
fun <T> VerticalItemList( fun <T> VerticalItemList(
modifier: Modifier = Modifier, modifier: Modifier = Modifier.fillMaxSize(),
backgroundColor: Color = MaterialTheme.colorScheme.background, backgroundColor: Color = MaterialTheme.colorScheme.background,
list: List<T>?, list: List<T>?,
itemKey: ((T) -> Any)? = null, itemKey: ((T) -> Any)? = null,
@ -92,7 +93,6 @@ fun <T> VerticalItemList(
) { ) {
Box( Box(
modifier = modifier modifier = modifier
.fillMaxSize()
.background(backgroundColor), .background(backgroundColor),
contentAlignment = if (list.isNullOrEmpty()) Alignment.Center else Alignment.TopStart contentAlignment = if (list.isNullOrEmpty()) Alignment.Center else Alignment.TopStart
) { ) {

View File

@ -28,12 +28,13 @@ fun ProductCard(
onUserClick: (ProductItem) -> Unit = {} onUserClick: (ProductItem) -> Unit = {}
) { ) {
val imageData by remember(item, repo) { val product by remember(item) { mutableStateOf(item) }
val imageData by remember(product, repo) {
mutableStateOf( mutableStateOf(
CoilDownloader.createIconUri( CoilDownloader.createIconUri(
item.packageName, product.packageName,
item.icon, product.icon,
item.metadataIcon, product.metadataIcon,
repo?.address, repo?.address,
repo?.authentication repo?.authentication
).toString() ).toString()
@ -46,7 +47,7 @@ fun ProductCard(
.requiredSize(80.dp, 116.dp) .requiredSize(80.dp, 116.dp)
.clip(shape = RoundedCornerShape(8.dp)) .clip(shape = RoundedCornerShape(8.dp))
.background(color = MaterialTheme.colorScheme.surface) .background(color = MaterialTheme.colorScheme.surface)
.clickable(onClick = { onUserClick(item) }), .clickable(onClick = { onUserClick(product) }),
horizontalAlignment = Alignment.CenterHorizontally, horizontalAlignment = Alignment.CenterHorizontally,
verticalArrangement = Arrangement.Center verticalArrangement = Arrangement.Center
) { ) {
@ -57,7 +58,7 @@ fun ProductCard(
Text( Text(
modifier = Modifier.padding(4.dp, 2.dp), modifier = Modifier.padding(4.dp, 2.dp),
text = item.name, text = product.name,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
maxLines = 1, maxLines = 1,
@ -65,7 +66,7 @@ fun ProductCard(
) )
Text( Text(
modifier = Modifier.padding(4.dp, 1.dp), modifier = Modifier.padding(4.dp, 1.dp),
text = item.version, text = product.version,
style = MaterialTheme.typography.labelSmall, style = MaterialTheme.typography.labelSmall,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
maxLines = 1, maxLines = 1,

View File

@ -32,12 +32,13 @@ fun ProductsListItem(
onFavouriteClick: (ProductItem) -> Unit = {}, onFavouriteClick: (ProductItem) -> Unit = {},
onInstallClick: (ProductItem) -> Unit = {} onInstallClick: (ProductItem) -> Unit = {}
) { ) {
val imageData by remember(item, repo) { val product by remember(item) { mutableStateOf(item) }
val imageData by remember(product, repo) {
mutableStateOf( mutableStateOf(
CoilDownloader.createIconUri( CoilDownloader.createIconUri(
item.packageName, product.packageName,
item.icon, product.icon,
item.metadataIcon, product.metadataIcon,
repo?.address, repo?.address,
repo?.authentication repo?.authentication
).toString() ).toString()
@ -46,10 +47,10 @@ fun ProductsListItem(
ExpandableCard( ExpandableCard(
modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp), modifier = Modifier.padding(horizontal = 8.dp, vertical = 8.dp),
onClick = { onUserClick(item) }, onClick = { onUserClick(product) },
expandedContent = { expandedContent = {
ExpandedItemContent( ExpandedItemContent(
item = item, item = product,
onFavourite = onFavouriteClick, onFavourite = onFavouriteClick,
onInstallClicked = onInstallClick onInstallClicked = onInstallClick
) )
@ -74,7 +75,7 @@ fun ProductsListItem(
.fillMaxHeight(0.4f), .fillMaxHeight(0.4f),
) { ) {
Text( Text(
text = item.name, text = product.name,
modifier = Modifier modifier = Modifier
.align(Alignment.CenterVertically) .align(Alignment.CenterVertically)
.weight(1f), .weight(1f),
@ -84,8 +85,8 @@ fun ProductsListItem(
style = MaterialTheme.typography.titleMedium style = MaterialTheme.typography.titleMedium
) )
Text( Text(
text = item.version,
modifier = Modifier.align(Alignment.CenterVertically), modifier = Modifier.align(Alignment.CenterVertically),
text = product.version,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
maxLines = 1, maxLines = 1,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
@ -95,7 +96,7 @@ fun ProductsListItem(
modifier = Modifier modifier = Modifier
.fillMaxHeight() .fillMaxHeight()
.fillMaxWidth(), .fillMaxWidth(),
text = item.summary, text = product.summary,
style = MaterialTheme.typography.bodySmall, style = MaterialTheme.typography.bodySmall,
overflow = TextOverflow.Ellipsis, overflow = TextOverflow.Ellipsis,
maxLines = 2, maxLines = 2,

View File

@ -1,16 +1,14 @@
package com.looker.droidify.ui.compose.utils package com.looker.droidify.ui.compose.utils
import androidx.compose.foundation.layout.Arrangement import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.PaddingValues
import androidx.compose.foundation.lazy.LazyRow import androidx.compose.foundation.lazy.LazyRow
import androidx.compose.foundation.lazy.items import androidx.compose.foundation.lazy.items
import androidx.compose.foundation.shape.RoundedCornerShape import androidx.compose.foundation.shape.RoundedCornerShape
import androidx.compose.material.Chip import androidx.compose.material.*
import androidx.compose.material.ChipColors
import androidx.compose.material.ChipDefaults
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material3.MaterialTheme import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Text import androidx.compose.material3.Text
import androidx.compose.runtime.Composable import androidx.compose.runtime.*
import androidx.compose.ui.Modifier import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Shape import androidx.compose.ui.graphics.Shape
import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.dp
@ -29,7 +27,8 @@ fun ChipRow(
) { ) {
LazyRow( LazyRow(
modifier = modifier, modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(8.dp) horizontalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(horizontal = 8.dp)
) { ) {
items(list) { items(list) {
Chip( Chip(
@ -40,7 +39,47 @@ fun ChipRow(
Text( Text(
text = it, text = it,
style = MaterialTheme.typography.labelLarge, style = MaterialTheme.typography.labelLarge,
color = MaterialTheme.colorScheme.primary.copy(alpha = ChipDefaults.ContentOpacity) color = chipColors.contentColor(enabled = true).value
)
}
}
}
}
@OptIn(ExperimentalMaterialApi::class)
@Composable
fun SelectableChipRow(
modifier: Modifier = Modifier,
list: List<String>,
chipColors: SelectableChipColors = ChipDefaults.filterChipColors(
backgroundColor = MaterialTheme.colorScheme.surface,
contentColor = MaterialTheme.colorScheme.onSurface,
selectedBackgroundColor = MaterialTheme.colorScheme.primary,
selectedContentColor = MaterialTheme.colorScheme.onPrimary
),
shapes: Shape = RoundedCornerShape(50),
onClick: (String) -> Unit
) {
var selected by remember { mutableStateOf(list[0]) }
LazyRow(
modifier = modifier,
horizontalArrangement = Arrangement.spacedBy(8.dp),
contentPadding = PaddingValues(horizontal = 8.dp)
) {
items(list) {
FilterChip(
shape = shapes,
colors = chipColors,
selected = it == selected,
onClick = {
onClick(it)
selected = it
}
) {
Text(
text = it,
color = chipColors.contentColor(enabled = true, selected = it == selected).value
) )
} }
} }

View File

@ -7,10 +7,8 @@ import androidx.fragment.app.Fragment
abstract class BaseNavFragment : Fragment() { abstract class BaseNavFragment : Fragment() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) { override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState) super.onViewCreated(view, savedInstanceState)
setupAdapters()
setupLayout() setupLayout()
} }
abstract fun setupAdapters()
abstract fun setupLayout() abstract fun setupLayout()
} }

View File

@ -5,18 +5,26 @@ import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import androidx.appcompat.widget.SearchView import androidx.appcompat.widget.SearchView
import androidx.compose.foundation.background
import androidx.compose.foundation.isSystemInDarkTheme import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.material.Scaffold import androidx.compose.foundation.layout.Column
import androidx.core.view.children import androidx.compose.foundation.layout.fillMaxSize
import com.google.android.material.chip.Chip import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.material.ExperimentalMaterialApi
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.Scaffold
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import com.looker.droidify.R import com.looker.droidify.R
import com.looker.droidify.content.Preferences import com.looker.droidify.content.Preferences
import com.looker.droidify.database.entity.Category import com.looker.droidify.database.entity.Product
import com.looker.droidify.database.entity.Repository import com.looker.droidify.database.entity.Repository
import com.looker.droidify.databinding.FragmentExploreXBinding import com.looker.droidify.databinding.FragmentExploreXBinding
import com.looker.droidify.entity.Section import com.looker.droidify.entity.Section
import com.looker.droidify.ui.compose.ProductsVerticalRecycler import com.looker.droidify.ui.compose.ProductsVerticalRecycler
import com.looker.droidify.ui.compose.theme.AppTheme import com.looker.droidify.ui.compose.theme.AppTheme
import com.looker.droidify.ui.compose.utils.SelectableChipRow
import com.looker.droidify.utility.isDarkTheme import com.looker.droidify.utility.isDarkTheme
import com.looker.droidify.widget.FocusSearchView import com.looker.droidify.widget.FocusSearchView
@ -40,67 +48,15 @@ class ExploreFragment : MainNavFragmentX() {
return binding.root return binding.root
} }
override fun setupAdapters() {
}
override fun setupLayout() { override fun setupLayout() {
viewModel.repositories.observe(viewLifecycleOwner) { viewModel.repositories.observe(viewLifecycleOwner) {
repositories = it.associateBy { repo -> repo.id } repositories = it.associateBy { repo -> repo.id }
} }
viewModel.primaryProducts.observe(viewLifecycleOwner) { viewModel.primaryProducts.observe(viewLifecycleOwner) {
binding.primaryComposeRecycler.setContent { redrawPage(it, viewModel.categories.value ?: emptyList())
AppTheme(
darkTheme = when (Preferences[Preferences.Key.Theme]) {
is Preferences.Theme.System -> isSystemInDarkTheme()
is Preferences.Theme.AmoledSystem -> isSystemInDarkTheme()
else -> isDarkTheme
}
) {
Scaffold { _ ->
ProductsVerticalRecycler(it, repositories,
onUserClick = { item ->
AppSheetX(item.packageName)
.showNow(parentFragmentManager, "Product ${item.packageName}")
},
onFavouriteClick = {},
onInstallClick = {
mainActivityX.syncConnection.binder?.installApps(listOf(it))
}
)
}
}
}
} }
viewModel.categories.observe(viewLifecycleOwner) { viewModel.categories.observe(viewLifecycleOwner) {
binding.categories.apply { redrawPage(viewModel.primaryProducts.value, it)
removeAllViews()
addView(Chip(requireContext(), null, R.attr.categoryChipStyle).apply {
setText(R.string.all_applications)
id = R.id.SHOW_ALL
})
it.sorted().forEach {
addView(Chip(requireContext(), null, R.attr.categoryChipStyle).apply {
text = it
})
}
val selectedSection = viewModel.sections.value
check(
children.filterNotNull()
.find { child ->
child is Chip && selectedSection is Category && child.text == selectedSection.label
}?.id ?: R.id.SHOW_ALL
)
}
}
binding.categories.setOnCheckedStateChangeListener { group, checkedIds ->
group.findViewById<Chip>(checkedIds[0]).let {
viewModel.setSection(
if (it.text.equals(getString(R.string.all_applications)))
Section.All
else
Section.Category(it.text.toString())
)
}
} }
mainActivityX.menuSetup.observe(viewLifecycleOwner) { mainActivityX.menuSetup.observe(viewLifecycleOwner) {
if (it != null) { if (it != null) {
@ -122,4 +78,53 @@ class ExploreFragment : MainNavFragmentX() {
} }
} }
} }
@OptIn(ExperimentalMaterialApi::class, ExperimentalMaterial3Api::class)
private fun redrawPage(products: List<Product>?, categories: List<String> = emptyList()) {
binding.primaryComposeRecycler.setContent {
AppTheme(
darkTheme = when (Preferences[Preferences.Key.Theme]) {
is Preferences.Theme.System -> isSystemInDarkTheme()
is Preferences.Theme.AmoledSystem -> isSystemInDarkTheme()
else -> isDarkTheme
}
) {
Scaffold { _ ->
Column(
Modifier
.background(MaterialTheme.colorScheme.background)
.fillMaxSize()
) {
SelectableChipRow(
list = listOf(
stringResource(id = R.string.all_applications),
*categories.sorted().toTypedArray()
)
) {
viewModel.setSection(
when (it) {
getString(R.string.all_applications) -> Section.All
else -> Section.Category(it)
}
)
}
ProductsVerticalRecycler(products,
repositories,
Modifier
.fillMaxWidth()
.weight(1f),
onUserClick = { item ->
AppSheetX(item.packageName)
.showNow(parentFragmentManager, "Product ${item.packageName}")
},
onFavouriteClick = {},
onInstallClick = {
mainActivityX.syncConnection.binder?.installApps(listOf(it))
}
)
}
}
}
}
}
} }

View File

@ -39,9 +39,6 @@ class InstalledFragment : MainNavFragmentX() {
return binding.root return binding.root
} }
override fun setupAdapters() {
}
override fun setupLayout() { override fun setupLayout() {
viewModel.repositories.observe(viewLifecycleOwner) { viewModel.repositories.observe(viewLifecycleOwner) {
repositories = it.associateBy { repo -> repo.id } repositories = it.associateBy { repo -> repo.id }

View File

@ -39,9 +39,6 @@ class LatestFragment : MainNavFragmentX() {
return binding.root return binding.root
} }
override fun setupAdapters() {
}
override fun setupLayout() { override fun setupLayout() {
viewModel.repositories.observe(viewLifecycleOwner) { viewModel.repositories.observe(viewLifecycleOwner) {
repositories = it.associateBy { repo -> repo.id } repositories = it.associateBy { repo -> repo.id }

View File

@ -43,12 +43,9 @@ class PrefsRepositoriesFragment : BaseNavFragment() {
return binding.root return binding.root
} }
override fun setupAdapters() { override fun setupLayout() {
syncConnection.bind(requireContext()) syncConnection.bind(requireContext())
binding.addRepository.setOnClickListener { viewModel.addRepository() } binding.addRepository.setOnClickListener { viewModel.addRepository() }
}
override fun setupLayout() {
viewModel.repositories.observe(requireActivity()) { viewModel.repositories.observe(requireActivity()) {
binding.reposRecycler.setContent { binding.reposRecycler.setContent {
AppTheme( AppTheme(

View File

@ -3,11 +3,19 @@ package com.looker.droidify.utility
import android.app.ActivityManager import android.app.ActivityManager
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_FOREGROUND
import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE import android.app.ActivityManager.RunningAppProcessInfo.IMPORTANCE_VISIBLE
import android.content.ActivityNotFoundException
import android.content.Context import android.content.Context
import android.content.DialogInterface
import android.content.Intent
import android.content.pm.PackageInfo import android.content.pm.PackageInfo
import android.content.pm.Signature import android.content.pm.Signature
import android.content.res.Configuration import android.content.res.Configuration
import android.graphics.drawable.Drawable import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.PowerManager
import android.provider.Settings
import android.widget.Toast
import androidx.appcompat.app.AlertDialog
import com.looker.droidify.BuildConfig import com.looker.droidify.BuildConfig
import com.looker.droidify.PREFS_LANGUAGE_DEFAULT import com.looker.droidify.PREFS_LANGUAGE_DEFAULT
import com.looker.droidify.R import com.looker.droidify.R
@ -194,3 +202,27 @@ val isBlackTheme: Boolean
is Preferences.Theme.AmoledSystem -> true is Preferences.Theme.AmoledSystem -> true
else -> false else -> false
} }
fun Context.showBatteryOptimizationDialog() {
AlertDialog.Builder(this)
.setTitle(R.string.ignore_battery_optimization_title)
.setMessage(R.string.ignore_battery_optimization_message)
.setPositiveButton(R.string.dialog_approve) { dialog: DialogInterface?, _: Int ->
val intent = Intent(Settings.ACTION_REQUEST_IGNORE_BATTERY_OPTIMIZATIONS)
intent.data = Uri.parse("package:" + this.packageName)
try {
startActivity(intent)
} catch (e: ActivityNotFoundException) {
Toast.makeText(
this,
R.string.ignore_battery_optimization_not_supported,
Toast.LENGTH_LONG
).show()
Preferences[Preferences.Key.IgnoreIgnoreBatteryOptimization] = true
}
}
.setNeutralButton(R.string.dialog_refuse) { _: DialogInterface?, _: Int ->
Preferences[Preferences.Key.IgnoreIgnoreBatteryOptimization] = true
}
.show()
}

View File

@ -15,8 +15,7 @@
~ You should have received a copy of the GNU Affero General Public License ~ You should have received a copy of the GNU Affero General Public License
~ along with this program. If not, see <https://www.gnu.org/licenses/>. ~ along with this program. If not, see <https://www.gnu.org/licenses/>.
--> -->
<layout xmlns:android="http://schemas.android.com/apk/res/android" <layout xmlns:android="http://schemas.android.com/apk/res/android">
xmlns:app="http://schemas.android.com/apk/res-auto">
<data> <data>
@ -26,33 +25,10 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent">
<LinearLayout <androidx.compose.ui.platform.ComposeView
android:id="@+id/primaryComposeRecycler"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent" />
android:nestedScrollingEnabled="true"
android:orientation="vertical">
<HorizontalScrollView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none">
<com.google.android.material.chip.ChipGroup
android:id="@+id/categories"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:nestedScrollingEnabled="true"
android:paddingHorizontal="@dimen/shape_margin_medium"
app:selectionRequired="true"
app:singleSelection="true" />
</HorizontalScrollView>
<androidx.compose.ui.platform.ComposeView
android:id="@+id/primaryComposeRecycler"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1" />
</LinearLayout>
</com.google.android.material.circularreveal.CircularRevealFrameLayout> </com.google.android.material.circularreveal.CircularRevealFrameLayout>
</layout> </layout>

View File

@ -189,4 +189,9 @@
<string name="default_tab">Default Tab</string> <string name="default_tab">Default Tab</string>
<string name="pending">Pending</string> <string name="pending">Pending</string>
<string name="installing">Installing</string> <string name="installing">Installing</string>
<string name="ignore_battery_optimization_title">Ignore Battery Optimization</string>
<string name="ignore_battery_optimization_message">Starting Android 12 there\'s restrictions on running foreground services causing the app to crash on downloads. To prevent this, you should turn off the battery optimization</string>
<string name="ignore_battery_optimization_not_supported">The device doesn\'t support ignoring battery optimizations!</string>
<string name="dialog_refuse">Don\'t need it</string>
<string name="dialog_approve">Let\'s do it!</string>
</resources> </resources>