Refactor badges and notifications, migrate badge settings
This commit is contained in:
+86
-58
@@ -21,6 +21,7 @@ import android.net.Uri
|
||||
import android.os.Build
|
||||
import android.os.Handler
|
||||
import android.os.Process
|
||||
import android.service.notification.StatusBarNotification
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.app.NotificationCompat
|
||||
@@ -32,15 +33,13 @@ import androidx.lifecycle.*
|
||||
import androidx.transition.Scene
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import de.mm20.launcher2.badges.BadgeProvider
|
||||
import de.mm20.launcher2.badges.BadgeRepository
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.ktx.castToOrNull
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.ktx.getBadgeIcon
|
||||
import de.mm20.launcher2.ktx.lifecycleScope
|
||||
import de.mm20.launcher2.ktx.*
|
||||
import de.mm20.launcher2.legacy.helper.ActivityStarter
|
||||
import de.mm20.launcher2.notifications.NotificationRepository
|
||||
import de.mm20.launcher2.notifications.NotificationService
|
||||
import de.mm20.launcher2.search.data.AppInstallation
|
||||
import de.mm20.launcher2.search.data.Application
|
||||
@@ -50,8 +49,9 @@ import de.mm20.launcher2.transition.ChangingLayoutTransition
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||
import de.mm20.launcher2.ui.legacy.view.*
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.util.concurrent.Executors
|
||||
@@ -60,7 +60,10 @@ import kotlin.math.roundToInt
|
||||
class ApplicationDetailRepresentation : Representation, KoinComponent {
|
||||
|
||||
private val iconRepository: IconRepository by inject()
|
||||
private val badgeProvider: BadgeProvider by inject()
|
||||
private val badgeRepository: BadgeRepository by inject()
|
||||
private val notificationRepository: NotificationRepository by inject()
|
||||
|
||||
private var job: Job? = null
|
||||
|
||||
override fun getScene(
|
||||
rootView: SearchableView,
|
||||
@@ -75,15 +78,35 @@ class ApplicationDetailRepresentation : Representation, KoinComponent {
|
||||
setOnClickListener(null)
|
||||
setOnLongClickListener(null)
|
||||
findViewById<TextView>(R.id.appName).text = application.label
|
||||
findViewById<LauncherIconView>(R.id.icon).apply {
|
||||
badge = badgeProvider.getLiveBadge(application.badgeKey)
|
||||
val iconView = findViewById<LauncherIconView>(R.id.icon).apply {
|
||||
shape = LauncherIconView.getDefaultShape(context)
|
||||
icon = iconRepository.getIconIfCached(application)
|
||||
lifecycleScope.launch {
|
||||
iconRepository.getIcon(application, (84 * rootView.dp).toInt())
|
||||
.collectLatest {
|
||||
icon = it
|
||||
}
|
||||
|
||||
val notificationView = findViewById<ChipGroup>(R.id.notifications)
|
||||
notificationView.layoutTransition = ChangingLayoutTransition()
|
||||
|
||||
job = rootView.scope.launch {
|
||||
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
iconRepository.getIcon(application, (84 * rootView.dp).toInt())
|
||||
.collectLatest {
|
||||
iconView.icon = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
badgeRepository.getBadge(application.badgeKey).collectLatest {
|
||||
iconView.badge = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
notificationRepository
|
||||
.notifications
|
||||
.map { it.filter { it.packageName == application.`package` } }
|
||||
.collectLatest {
|
||||
updateNotifications(notificationView, it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
findViewById<SwipeCardView>(R.id.appCard).also {
|
||||
@@ -142,9 +165,59 @@ class ApplicationDetailRepresentation : Representation, KoinComponent {
|
||||
}
|
||||
}
|
||||
|
||||
scene.setExitAction {
|
||||
job?.cancel()
|
||||
}
|
||||
|
||||
return scene
|
||||
}
|
||||
|
||||
private fun updateNotifications(chipGroup: ChipGroup, notifications: List<StatusBarNotification>) {
|
||||
val context = chipGroup.context
|
||||
chipGroup.removeAllViews()
|
||||
notifications.forEach {
|
||||
var title = it.notification.tickerText
|
||||
if (title.isNullOrBlank()) {
|
||||
title = it.notification.extras.getCharSequence(Notification.EXTRA_TITLE)
|
||||
}
|
||||
if (title.isNullOrBlank()) {
|
||||
title = it.notification.extras.getCharSequence(Notification.EXTRA_TEXT)
|
||||
}
|
||||
if (title == null) title = ""
|
||||
if (!NotificationCompat.isGroupSummary(it.notification)) {
|
||||
val view = Chip(context)
|
||||
view.text = title
|
||||
view.chipIcon =
|
||||
createShortcutDrawable(getNotificationChipIcon(context, it.notification))
|
||||
view.chipStrokeWidth = 1 * context.dp
|
||||
view.chipStrokeColor = ContextCompat.getColorStateList(context, R.color.chip_stroke)
|
||||
view.chipBackgroundColor =
|
||||
ContextCompat.getColorStateList(context, R.color.chip_background)
|
||||
view.setTextAppearanceResource(R.style.ChipTextAppearance)
|
||||
view.closeIconTint = ColorStateList.valueOf(
|
||||
ContextCompat.getColor(
|
||||
context,
|
||||
R.color.text_color_secondary
|
||||
)
|
||||
)
|
||||
|
||||
view.isCloseIconVisible = it.isClearable
|
||||
|
||||
view.setOnClickListener { _ ->
|
||||
try {
|
||||
it.notification.contentIntent?.send()
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
}
|
||||
}
|
||||
view.setOnCloseIconClickListener { _ ->
|
||||
notificationRepository.cancelNotification(it)
|
||||
}
|
||||
chipGroup.addView(view)
|
||||
}
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupToolbar(rootView: SearchableView, toolbar: ToolbarView, app: Application) {
|
||||
val context = rootView.context
|
||||
toolbar.clear()
|
||||
@@ -222,51 +295,6 @@ class ApplicationDetailRepresentation : Representation, KoinComponent {
|
||||
|
||||
private fun setupShortcuts(appShortcuts: ChipGroup, app: Application) {
|
||||
val context = appShortcuts.context
|
||||
appShortcuts.removeAllViews()
|
||||
val ns = NotificationService.getInstance()
|
||||
val notifications = ns?.getNotifications(app.`package`)
|
||||
notifications?.forEach {
|
||||
var title = it.notification.tickerText
|
||||
if (title.isNullOrBlank()) {
|
||||
title = it.notification.extras.getCharSequence(Notification.EXTRA_TITLE)
|
||||
}
|
||||
if (title.isNullOrBlank()) {
|
||||
title = it.notification.extras.getCharSequence(Notification.EXTRA_TEXT)
|
||||
}
|
||||
if (title == null) title = ""
|
||||
if (!NotificationCompat.isGroupSummary(it.notification)) {
|
||||
val view = Chip(context)
|
||||
view.text = title
|
||||
view.chipIcon =
|
||||
createShortcutDrawable(getNotificationChipIcon(context, it.notification))
|
||||
view.chipStrokeWidth = 1 * context.dp
|
||||
view.chipStrokeColor = ContextCompat.getColorStateList(context, R.color.chip_stroke)
|
||||
view.chipBackgroundColor =
|
||||
ContextCompat.getColorStateList(context, R.color.chip_background)
|
||||
view.setTextAppearanceResource(R.style.ChipTextAppearance)
|
||||
view.closeIconTint = ColorStateList.valueOf(
|
||||
ContextCompat.getColor(
|
||||
context,
|
||||
R.color.text_color_secondary
|
||||
)
|
||||
)
|
||||
|
||||
view.isCloseIconVisible = it.isClearable
|
||||
|
||||
view.setOnClickListener { _ ->
|
||||
try {
|
||||
it.notification.contentIntent?.send()
|
||||
} catch (e: PendingIntent.CanceledException) {
|
||||
}
|
||||
}
|
||||
view.setOnCloseIconClickListener { _ ->
|
||||
ns.cancelNotification(it.key)
|
||||
appShortcuts.removeView(view)
|
||||
}
|
||||
appShortcuts.addView(view)
|
||||
}
|
||||
|
||||
}
|
||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) {
|
||||
val launcherApps =
|
||||
context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
|
||||
|
||||
@@ -2,16 +2,20 @@ package de.mm20.launcher2.ui.legacy.search
|
||||
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.transition.Scene
|
||||
import de.mm20.launcher2.badges.BadgeProvider
|
||||
import de.mm20.launcher2.badges.BadgeRepository
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.ktx.lifecycleOwner
|
||||
import de.mm20.launcher2.ktx.lifecycleScope
|
||||
import de.mm20.launcher2.legacy.helper.ActivityStarter
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||
import de.mm20.launcher2.ui.legacy.view.LauncherIconView
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
@@ -20,7 +24,9 @@ import org.koin.core.component.inject
|
||||
class BasicGridRepresentation : Representation, KoinComponent {
|
||||
|
||||
private val iconRepository: IconRepository by inject()
|
||||
private val badgeProvider: BadgeProvider by inject()
|
||||
private val badgeRepository: BadgeRepository by inject()
|
||||
|
||||
private var job: Job? = null
|
||||
|
||||
override fun getScene(
|
||||
rootView: SearchableView,
|
||||
@@ -40,7 +46,6 @@ class BasicGridRepresentation : Representation, KoinComponent {
|
||||
.alpha(1f)
|
||||
.start()*/
|
||||
findViewById<LauncherIconView>(R.id.icon).apply {
|
||||
badge = badgeProvider.getLiveBadge(searchable.badgeKey)
|
||||
shape = LauncherIconView.getDefaultShape(context)
|
||||
setOnClickListener {
|
||||
if (!ActivityStarter.start(
|
||||
@@ -53,9 +58,20 @@ class BasicGridRepresentation : Representation, KoinComponent {
|
||||
}
|
||||
}
|
||||
icon = iconRepository.getIconIfCached(searchable)
|
||||
lifecycleScope.launch {
|
||||
iconRepository.getIcon(searchable, (84 * rootView.dp).toInt()).collectLatest {
|
||||
icon = it
|
||||
|
||||
job = rootView.scope.launch {
|
||||
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
iconRepository.getIcon(searchable, (84 * rootView.dp).toInt())
|
||||
.collectLatest {
|
||||
icon = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
badgeRepository.getBadge(searchable.badgeKey).collectLatest {
|
||||
badge = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setOnLongClickListener {
|
||||
@@ -65,6 +81,9 @@ class BasicGridRepresentation : Representation, KoinComponent {
|
||||
}
|
||||
}
|
||||
}
|
||||
scene.setExitAction {
|
||||
job?.cancel()
|
||||
}
|
||||
|
||||
return scene
|
||||
|
||||
|
||||
@@ -13,10 +13,13 @@ import android.widget.TextView
|
||||
import android.widget.Toast
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.transition.Scene
|
||||
import de.mm20.launcher2.badges.BadgeProvider
|
||||
import de.mm20.launcher2.badges.BadgeRepository
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.ktx.lifecycleOwner
|
||||
import de.mm20.launcher2.ktx.lifecycleScope
|
||||
import de.mm20.launcher2.ktx.setStartCompoundDrawable
|
||||
import de.mm20.launcher2.legacy.helper.ActivityStarter
|
||||
@@ -25,6 +28,7 @@ import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||
import de.mm20.launcher2.ui.legacy.view.*
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
@@ -33,8 +37,10 @@ import java.net.URLEncoder
|
||||
|
||||
class ContactDetailRepresentation : Representation, KoinComponent {
|
||||
|
||||
val iconRepository: IconRepository by inject()
|
||||
val badgeProvider: BadgeProvider by inject()
|
||||
private val iconRepository: IconRepository by inject()
|
||||
private val badgeRepository: BadgeRepository by inject()
|
||||
|
||||
private var job: Job? = null
|
||||
|
||||
override fun getScene(
|
||||
rootView: SearchableView,
|
||||
@@ -48,12 +54,21 @@ class ContactDetailRepresentation : Representation, KoinComponent {
|
||||
scene.setEnterAction {
|
||||
with(rootView) {
|
||||
findViewById<LauncherIconView>(R.id.icon).apply {
|
||||
badge = badgeProvider.getLiveBadge(contact.badgeKey)
|
||||
shape = LauncherIconView.getDefaultShape(context)
|
||||
icon = iconRepository.getIconIfCached(contact)
|
||||
lifecycleScope.launch {
|
||||
iconRepository.getIcon(contact, (84 * rootView.dp).toInt()).collectLatest {
|
||||
icon = it
|
||||
job = rootView.scope.launch {
|
||||
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
iconRepository.getIcon(contact, (84 * rootView.dp).toInt())
|
||||
.collectLatest {
|
||||
icon = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
badgeRepository.getBadge(contact.badgeKey).collectLatest {
|
||||
badge = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -67,6 +82,9 @@ class ContactDetailRepresentation : Representation, KoinComponent {
|
||||
addShortcuts(rootView, contact)
|
||||
}
|
||||
}
|
||||
scene.setExitAction {
|
||||
job?.cancel()
|
||||
}
|
||||
return scene
|
||||
}
|
||||
|
||||
|
||||
@@ -2,11 +2,14 @@ package de.mm20.launcher2.ui.legacy.search
|
||||
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.transition.Scene
|
||||
import de.mm20.launcher2.badges.BadgeRepository
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.badges.BadgeProvider
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.ktx.lifecycleOwner
|
||||
import de.mm20.launcher2.ktx.lifecycleScope
|
||||
import de.mm20.launcher2.search.data.Contact
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
@@ -15,6 +18,7 @@ import de.mm20.launcher2.ui.legacy.view.FavoriteSwipeAction
|
||||
import de.mm20.launcher2.ui.legacy.view.HideSwipeAction
|
||||
import de.mm20.launcher2.ui.legacy.view.LauncherIconView
|
||||
import de.mm20.launcher2.ui.legacy.view.SwipeCardView
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
@@ -22,8 +26,10 @@ import org.koin.core.component.inject
|
||||
|
||||
class ContactListRepresentation : Representation, KoinComponent {
|
||||
|
||||
val iconRepository: IconRepository by inject()
|
||||
val badgeProvider: BadgeProvider by inject()
|
||||
private val iconRepository: IconRepository by inject()
|
||||
private val badgeRepository: BadgeRepository by inject()
|
||||
|
||||
private var job: Job? = null
|
||||
|
||||
override fun getScene(rootView: SearchableView, searchable: Searchable, previousRepresentation: Int?): Scene {
|
||||
val contact = searchable as Contact
|
||||
@@ -32,12 +38,21 @@ class ContactListRepresentation : Representation, KoinComponent {
|
||||
scene.setEnterAction {
|
||||
with(rootView) {
|
||||
findViewById<LauncherIconView>(R.id.icon).apply {
|
||||
badge = badgeProvider.getLiveBadge(contact.badgeKey)
|
||||
shape = LauncherIconView.getDefaultShape(context)
|
||||
icon = iconRepository.getIconIfCached(contact)
|
||||
lifecycleScope.launch {
|
||||
iconRepository.getIcon(contact, (84 * rootView.dp).toInt()).collectLatest {
|
||||
icon = it
|
||||
job = rootView.scope.launch {
|
||||
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
iconRepository.getIcon(searchable, (84 * rootView.dp).toInt())
|
||||
.collectLatest {
|
||||
icon = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
badgeRepository.getBadge(searchable.badgeKey).collectLatest {
|
||||
badge = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -60,6 +75,9 @@ class ContactListRepresentation : Representation, KoinComponent {
|
||||
|
||||
}
|
||||
}
|
||||
scene.setExitAction {
|
||||
job?.cancel()
|
||||
}
|
||||
return scene
|
||||
}
|
||||
|
||||
|
||||
@@ -5,19 +5,22 @@ import android.content.Intent
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.transition.Scene
|
||||
import com.google.android.material.dialog.MaterialAlertDialogBuilder
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.badges.BadgeProvider
|
||||
import de.mm20.launcher2.badges.BadgeRepository
|
||||
import de.mm20.launcher2.files.FilesViewModel
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.ktx.lifecycleScope
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.ktx.lifecycleOwner
|
||||
import de.mm20.launcher2.search.data.File
|
||||
import de.mm20.launcher2.search.data.GDriveFile
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||
import de.mm20.launcher2.ui.legacy.view.*
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
@@ -27,10 +30,16 @@ import java.text.DecimalFormat
|
||||
|
||||
class FileDetailRepresentation : Representation, KoinComponent {
|
||||
|
||||
val iconRepository: IconRepository by inject()
|
||||
val badgeProvider: BadgeProvider by inject()
|
||||
private val iconRepository: IconRepository by inject()
|
||||
private val badgeRepository: BadgeRepository by inject()
|
||||
|
||||
override fun getScene(rootView: SearchableView, searchable: Searchable, previousRepresentation: Int?): Scene {
|
||||
private var job: Job? = null
|
||||
|
||||
override fun getScene(
|
||||
rootView: SearchableView,
|
||||
searchable: Searchable,
|
||||
previousRepresentation: Int?
|
||||
): Scene {
|
||||
val file = searchable as File
|
||||
val context = rootView.context as AppCompatActivity
|
||||
val scene = Scene.getSceneForLayout(rootView, R.layout.view_file_detail, rootView.context)
|
||||
@@ -39,12 +48,21 @@ class FileDetailRepresentation : Representation, KoinComponent {
|
||||
findViewById<TextView>(R.id.fileLabel).text = file.label
|
||||
findViewById<TextView>(R.id.fileInfo).text = getInfo(context, file)
|
||||
findViewById<LauncherIconView>(R.id.icon).apply {
|
||||
badge = badgeProvider.getLiveBadge(file.badgeKey)
|
||||
shape = LauncherIconView.getDefaultShape(context)
|
||||
icon = iconRepository.getIconIfCached(file)
|
||||
lifecycleScope.launch {
|
||||
iconRepository.getIcon(file, (84 * rootView.dp).toInt()).collectLatest {
|
||||
icon = it
|
||||
job = rootView.scope.launch {
|
||||
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
iconRepository.getIcon(searchable, (84 * rootView.dp).toInt())
|
||||
.collectLatest {
|
||||
icon = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
badgeRepository.getBadge(searchable.badgeKey).collectLatest {
|
||||
badge = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -55,6 +73,9 @@ class FileDetailRepresentation : Representation, KoinComponent {
|
||||
setupMenu(rootView, findViewById(R.id.fileToolbar), file)
|
||||
}
|
||||
}
|
||||
scene.setExitAction {
|
||||
job?.cancel()
|
||||
}
|
||||
return scene
|
||||
}
|
||||
|
||||
@@ -62,7 +83,8 @@ class FileDetailRepresentation : Representation, KoinComponent {
|
||||
val context = toolbar.context
|
||||
toolbar.clear()
|
||||
|
||||
val backAction = ToolbarAction(R.drawable.ic_arrow_back, context.getString(R.string.menu_back))
|
||||
val backAction =
|
||||
ToolbarAction(R.drawable.ic_arrow_back, context.getString(R.string.menu_back))
|
||||
backAction.clickAction = {
|
||||
rootView.back()
|
||||
}
|
||||
@@ -72,7 +94,8 @@ class FileDetailRepresentation : Representation, KoinComponent {
|
||||
toolbar.addAction(favAction, ToolbarView.PLACEMENT_END)
|
||||
|
||||
if (file.isDeletable) {
|
||||
val deleteAction = ToolbarAction(R.drawable.ic_delete, context.getString(R.string.menu_delete))
|
||||
val deleteAction =
|
||||
ToolbarAction(R.drawable.ic_delete, context.getString(R.string.menu_delete))
|
||||
deleteAction.clickAction = {
|
||||
delete(context, file)
|
||||
}
|
||||
@@ -83,7 +106,8 @@ class FileDetailRepresentation : Representation, KoinComponent {
|
||||
toolbar.addAction(hideAction, ToolbarView.PLACEMENT_END)
|
||||
|
||||
if (file !is GDriveFile) {
|
||||
val shareAction = ToolbarAction(R.drawable.ic_share, context.getString(R.string.menu_share))
|
||||
val shareAction =
|
||||
ToolbarAction(R.drawable.ic_share, context.getString(R.string.menu_share))
|
||||
shareAction.clickAction = {
|
||||
share(context, file)
|
||||
}
|
||||
@@ -93,16 +117,19 @@ class FileDetailRepresentation : Representation, KoinComponent {
|
||||
|
||||
private fun delete(context: Context, file: File) {
|
||||
MaterialAlertDialogBuilder(context)
|
||||
.setMessage(context.getString(
|
||||
if (file.isDirectory) R.string.alert_delete_directory
|
||||
else R.string.alert_delete_file,
|
||||
file.path))
|
||||
.setPositiveButton(android.R.string.ok) {dialog, _ ->
|
||||
.setMessage(
|
||||
context.getString(
|
||||
if (file.isDirectory) R.string.alert_delete_directory
|
||||
else R.string.alert_delete_file,
|
||||
file.path
|
||||
)
|
||||
)
|
||||
.setPositiveButton(android.R.string.ok) { dialog, _ ->
|
||||
val fileViewModel: FilesViewModel by (context as AppCompatActivity).viewModel()
|
||||
dialog.dismiss()
|
||||
fileViewModel.deleteFile(file)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) {dialog, _ ->
|
||||
.setNegativeButton(android.R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
@@ -111,9 +138,11 @@ class FileDetailRepresentation : Representation, KoinComponent {
|
||||
private fun share(context: Context, fileDetail: File) {
|
||||
val shareIntent = Intent(Intent.ACTION_SEND)
|
||||
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
val uri = FileProvider.getUriForFile(context,
|
||||
context.applicationContext.packageName + ".fileprovider",
|
||||
java.io.File(fileDetail.path))
|
||||
val uri = FileProvider.getUriForFile(
|
||||
context,
|
||||
context.applicationContext.packageName + ".fileprovider",
|
||||
java.io.File(fileDetail.path)
|
||||
)
|
||||
shareIntent.putExtra(Intent.EXTRA_STREAM, uri)
|
||||
shareIntent.type = fileDetail.mimeType
|
||||
context.startActivity(Intent.createChooser(shareIntent, null))
|
||||
@@ -122,17 +151,35 @@ class FileDetailRepresentation : Representation, KoinComponent {
|
||||
private fun getInfo(context: Context, file: File): String {
|
||||
val sb = StringBuilder()
|
||||
|
||||
sb.append(context.getString(R.string.file_meta_data_entry, context.getString(R.string.file_meta_type), file.mimeType))
|
||||
sb.append(
|
||||
context.getString(
|
||||
R.string.file_meta_data_entry,
|
||||
context.getString(R.string.file_meta_type),
|
||||
file.mimeType
|
||||
)
|
||||
)
|
||||
|
||||
for ((k, v) in file.metaData) {
|
||||
sb.append("\n")
|
||||
.append(context.getString(R.string.file_meta_data_entry, context.getString(k), v))
|
||||
.append(context.getString(R.string.file_meta_data_entry, context.getString(k), v))
|
||||
}
|
||||
if (!file.isDirectory) {
|
||||
sb.append("\n").append(context.getString(R.string.file_meta_data_entry, context.getString(R.string.file_meta_size), formatFileSize(file.size)))
|
||||
sb.append("\n").append(
|
||||
context.getString(
|
||||
R.string.file_meta_data_entry,
|
||||
context.getString(R.string.file_meta_size),
|
||||
formatFileSize(file.size)
|
||||
)
|
||||
)
|
||||
}
|
||||
if (file.path.isNotEmpty()) {
|
||||
sb.append("\n").append(context.getString(R.string.file_meta_data_entry, context.getString(R.string.file_meta_path), file.path))
|
||||
sb.append("\n").append(
|
||||
context.getString(
|
||||
R.string.file_meta_data_entry,
|
||||
context.getString(R.string.file_meta_path),
|
||||
file.path
|
||||
)
|
||||
)
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
@@ -1,22 +1,24 @@
|
||||
package de.mm20.launcher2.ui.legacy.search
|
||||
|
||||
import android.content.Context
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.transition.Scene
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.badges.BadgeProvider
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.badges.BadgeRepository
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.ktx.lifecycleScope
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.ktx.lifecycleOwner
|
||||
import de.mm20.launcher2.legacy.helper.ActivityStarter
|
||||
import de.mm20.launcher2.search.data.File
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||
import de.mm20.launcher2.ui.legacy.view.FavoriteSwipeAction
|
||||
import de.mm20.launcher2.ui.legacy.view.HideSwipeAction
|
||||
import de.mm20.launcher2.ui.legacy.view.LauncherIconView
|
||||
import de.mm20.launcher2.ui.legacy.view.SwipeCardView
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
@@ -24,10 +26,16 @@ import org.koin.core.component.inject
|
||||
|
||||
class FileListRepresentation : Representation, KoinComponent {
|
||||
|
||||
val iconRepository: IconRepository by inject()
|
||||
val badgeProvider: BadgeProvider by inject()
|
||||
private val iconRepository: IconRepository by inject()
|
||||
private val badgeRepository: BadgeRepository by inject()
|
||||
|
||||
override fun getScene(rootView: SearchableView, searchable: Searchable, previousRepresentation: Int?): Scene {
|
||||
private var job: Job? = null
|
||||
|
||||
override fun getScene(
|
||||
rootView: SearchableView,
|
||||
searchable: Searchable,
|
||||
previousRepresentation: Int?
|
||||
): Scene {
|
||||
val file = searchable as File
|
||||
val context = rootView.context as AppCompatActivity
|
||||
val scene = Scene.getSceneForLayout(rootView, R.layout.view_file_list, rootView.context)
|
||||
@@ -36,12 +44,21 @@ class FileListRepresentation : Representation, KoinComponent {
|
||||
findViewById<TextView>(R.id.fileLabel).text = file.label
|
||||
findViewById<TextView>(R.id.fileInfo).text = file.getFileType(context)
|
||||
findViewById<LauncherIconView>(R.id.icon).apply {
|
||||
badge = badgeProvider.getLiveBadge(file.badgeKey)
|
||||
shape = LauncherIconView.getDefaultShape(context)
|
||||
icon = iconRepository.getIconIfCached(file)
|
||||
lifecycleScope.launch {
|
||||
iconRepository.getIcon(file, (84 * rootView.dp).toInt()).collectLatest {
|
||||
icon = it
|
||||
job = rootView.scope.launch {
|
||||
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
iconRepository.getIcon(searchable, (84 * rootView.dp).toInt())
|
||||
.collectLatest {
|
||||
icon = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
badgeRepository.getBadge(searchable.badgeKey).collectLatest {
|
||||
badge = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +75,9 @@ class FileListRepresentation : Representation, KoinComponent {
|
||||
}
|
||||
}
|
||||
}
|
||||
scene.setExitAction {
|
||||
job?.cancel()
|
||||
}
|
||||
return scene
|
||||
}
|
||||
}
|
||||
+41
-14
@@ -4,11 +4,13 @@ import android.os.Build
|
||||
import android.widget.TextView
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.transition.Scene
|
||||
import de.mm20.launcher2.badges.BadgeProvider
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.badges.BadgeRepository
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.ktx.lifecycleScope
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.ktx.lifecycleOwner
|
||||
import de.mm20.launcher2.search.data.AppShortcut
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.R
|
||||
@@ -17,18 +19,25 @@ import de.mm20.launcher2.ui.legacy.view.FavoriteToolbarAction
|
||||
import de.mm20.launcher2.ui.legacy.view.LauncherIconView
|
||||
import de.mm20.launcher2.ui.legacy.view.ToolbarAction
|
||||
import de.mm20.launcher2.ui.legacy.view.ToolbarView
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
@RequiresApi(Build.VERSION_CODES.N_MR1)
|
||||
class AppShortcutDetailRepresentation: Representation, KoinComponent {
|
||||
class AppShortcutDetailRepresentation : Representation, KoinComponent {
|
||||
|
||||
val iconRepository: IconRepository by inject()
|
||||
val badgeProvider: BadgeProvider by inject()
|
||||
private val iconRepository: IconRepository by inject()
|
||||
private val badgeRepository: BadgeRepository by inject()
|
||||
|
||||
override fun getScene(rootView: SearchableView, searchable: Searchable, previousRepresentation: Int?): Scene {
|
||||
private var job: Job? = null
|
||||
|
||||
override fun getScene(
|
||||
rootView: SearchableView,
|
||||
searchable: Searchable,
|
||||
previousRepresentation: Int?
|
||||
): Scene {
|
||||
val appShortcut = searchable as AppShortcut
|
||||
val context = rootView.context as AppCompatActivity
|
||||
val scene = Scene.getSceneForLayout(rootView, R.layout.view_application_detail, context)
|
||||
@@ -38,32 +47,50 @@ class AppShortcutDetailRepresentation: Representation, KoinComponent {
|
||||
setOnLongClickListener(null)
|
||||
findViewById<TextView>(R.id.appName).text = appShortcut.label
|
||||
findViewById<LauncherIconView>(R.id.icon).apply {
|
||||
badge = badgeProvider.getLiveBadge(appShortcut.badgeKey)
|
||||
shape = LauncherIconView.getDefaultShape(context)
|
||||
icon = iconRepository.getIconIfCached(appShortcut)
|
||||
lifecycleScope.launch {
|
||||
iconRepository.getIcon(appShortcut, (84 * rootView.dp).toInt()).collectLatest {
|
||||
icon = it
|
||||
job = rootView.scope.launch {
|
||||
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
iconRepository.getIcon(searchable, (84 * rootView.dp).toInt())
|
||||
.collectLatest {
|
||||
icon = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
badgeRepository.getBadge(searchable.badgeKey).collectLatest {
|
||||
badge = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val appName = appShortcut.appName
|
||||
findViewById<TextView>(R.id.appInfo).text = context.getString(R.string.shortcut_summary, appName)
|
||||
findViewById<TextView>(R.id.appInfo).text =
|
||||
context.getString(R.string.shortcut_summary, appName)
|
||||
|
||||
val toolbar = findViewById<ToolbarView>(R.id.appToolbar)
|
||||
setupToolbar(this, toolbar, appShortcut)
|
||||
|
||||
}
|
||||
}
|
||||
scene.setExitAction {
|
||||
job?.cancel()
|
||||
}
|
||||
return scene
|
||||
}
|
||||
|
||||
private fun setupToolbar(searchableView: SearchableView, toolbar: ToolbarView, shortcut: AppShortcut) {
|
||||
private fun setupToolbar(
|
||||
searchableView: SearchableView,
|
||||
toolbar: ToolbarView,
|
||||
shortcut: AppShortcut
|
||||
) {
|
||||
val context = searchableView.context
|
||||
val favAction = FavoriteToolbarAction(context, shortcut)
|
||||
toolbar.addAction(favAction, ToolbarView.PLACEMENT_END)
|
||||
|
||||
val backAction = ToolbarAction(R.drawable.ic_arrow_back, context.getString(R.string.menu_back))
|
||||
val backAction =
|
||||
ToolbarAction(R.drawable.ic_arrow_back, context.getString(R.string.menu_back))
|
||||
backAction.clickAction = {
|
||||
searchableView.back()
|
||||
}
|
||||
|
||||
+30
-14
@@ -7,12 +7,13 @@ import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.transition.Scene
|
||||
import com.bumptech.glide.Glide
|
||||
import de.mm20.launcher2.badges.BadgeProvider
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.ktx.lifecycleScope
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.ktx.lifecycleOwner
|
||||
import de.mm20.launcher2.legacy.helper.ActivityStarter
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.search.data.Website
|
||||
@@ -22,7 +23,7 @@ import de.mm20.launcher2.ui.legacy.view.FavoriteToolbarAction
|
||||
import de.mm20.launcher2.ui.legacy.view.LauncherIconView
|
||||
import de.mm20.launcher2.ui.legacy.view.ToolbarAction
|
||||
import de.mm20.launcher2.ui.legacy.view.ToolbarView
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
@@ -30,13 +31,18 @@ import org.koin.core.component.inject
|
||||
|
||||
class WebsiteDetailRepresentation : Representation, KoinComponent {
|
||||
|
||||
val iconRepository: IconRepository by inject()
|
||||
val badgeProvider: BadgeProvider by inject()
|
||||
private val iconRepository: IconRepository by inject()
|
||||
private var job: Job? = null
|
||||
|
||||
override fun getScene(rootView: SearchableView, searchable: Searchable, previousRepresentation: Int?): Scene {
|
||||
override fun getScene(
|
||||
rootView: SearchableView,
|
||||
searchable: Searchable,
|
||||
previousRepresentation: Int?
|
||||
): Scene {
|
||||
val website = searchable as Website
|
||||
val context = rootView.context as AppCompatActivity
|
||||
val scene = Scene.getSceneForLayout(rootView, R.layout.view_website_detail, rootView.context)
|
||||
val scene =
|
||||
Scene.getSceneForLayout(rootView, R.layout.view_website_detail, rootView.context)
|
||||
scene.setEnterAction {
|
||||
with(rootView) {
|
||||
if (!hasBack()) {
|
||||
@@ -67,12 +73,14 @@ class WebsiteDetailRepresentation : Representation, KoinComponent {
|
||||
label.transitionName = null
|
||||
websiteFavIcon.transitionName = "icon"
|
||||
websiteFavIcon.apply {
|
||||
badge = badgeProvider.getLiveBadge(website.badgeKey)
|
||||
shape = LauncherIconView.getDefaultShape(context)
|
||||
icon = iconRepository.getIconIfCached(website)
|
||||
lifecycleScope.launch {
|
||||
iconRepository.getIcon(website, (84 * rootView.dp).toInt()).collectLatest {
|
||||
icon = it
|
||||
job = rootView.scope.launch {
|
||||
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
iconRepository.getIcon(website, (84 * rootView.dp).toInt())
|
||||
.collectLatest {
|
||||
icon = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -89,6 +97,10 @@ class WebsiteDetailRepresentation : Representation, KoinComponent {
|
||||
setupMenu(rootView, toolbar, website)
|
||||
}
|
||||
}
|
||||
|
||||
scene.setExitAction {
|
||||
job?.cancel()
|
||||
}
|
||||
return scene
|
||||
}
|
||||
|
||||
@@ -97,7 +109,8 @@ class WebsiteDetailRepresentation : Representation, KoinComponent {
|
||||
toolbar.clear()
|
||||
|
||||
if (rootView.hasBack()) {
|
||||
val backAction = ToolbarAction(R.drawable.ic_arrow_back, context.getString(R.string.menu_back))
|
||||
val backAction =
|
||||
ToolbarAction(R.drawable.ic_arrow_back, context.getString(R.string.menu_back))
|
||||
backAction.clickAction = {
|
||||
rootView.back()
|
||||
}
|
||||
@@ -115,7 +128,10 @@ class WebsiteDetailRepresentation : Representation, KoinComponent {
|
||||
|
||||
private fun share(context: Context, website: Website) {
|
||||
val shareIntent = Intent(Intent.ACTION_SEND)
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, "${website.label}\n\n${website.description}\n\n${website.url}")
|
||||
shareIntent.putExtra(
|
||||
Intent.EXTRA_TEXT,
|
||||
"${website.label}\n\n${website.description}\n\n${website.url}"
|
||||
)
|
||||
shareIntent.type = "text/plain"
|
||||
context.startActivity(Intent.createChooser(shareIntent, null))
|
||||
}
|
||||
|
||||
@@ -7,11 +7,14 @@ import android.widget.FrameLayout
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.transition.Scene
|
||||
import com.bumptech.glide.Glide
|
||||
import de.mm20.launcher2.badges.BadgeProvider
|
||||
import de.mm20.launcher2.badges.BadgeRepository
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.ktx.lifecycleOwner
|
||||
import de.mm20.launcher2.ktx.lifecycleScope
|
||||
import de.mm20.launcher2.legacy.helper.ActivityStarter
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
@@ -22,6 +25,7 @@ import de.mm20.launcher2.ui.legacy.view.FavoriteToolbarAction
|
||||
import de.mm20.launcher2.ui.legacy.view.LauncherIconView
|
||||
import de.mm20.launcher2.ui.legacy.view.ToolbarAction
|
||||
import de.mm20.launcher2.ui.legacy.view.ToolbarView
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
@@ -30,9 +34,11 @@ import org.koin.core.component.inject
|
||||
|
||||
class WebsiteListRepresentation : Representation, KoinComponent {
|
||||
|
||||
val iconRepository: IconRepository by inject()
|
||||
private val iconRepository: IconRepository by inject()
|
||||
private val badgeRepository: BadgeRepository by inject()
|
||||
|
||||
private var job: Job? = null
|
||||
|
||||
val badgeProvider: BadgeProvider by inject()
|
||||
|
||||
override fun getScene(
|
||||
rootView: SearchableView,
|
||||
@@ -72,12 +78,21 @@ class WebsiteListRepresentation : Representation, KoinComponent {
|
||||
label.transitionName = null
|
||||
websiteFavIcon.transitionName = "icon"
|
||||
websiteFavIcon.apply {
|
||||
badge = badgeProvider.getLiveBadge(website.badgeKey)
|
||||
shape = LauncherIconView.getDefaultShape(context)
|
||||
icon = iconRepository.getIconIfCached(website)
|
||||
lifecycleScope.launch {
|
||||
iconRepository.getIcon(website, (84 * rootView.dp).toInt()).collectLatest {
|
||||
icon = it
|
||||
job = rootView.scope.launch {
|
||||
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
launch {
|
||||
iconRepository.getIcon(searchable, (84 * rootView.dp).toInt())
|
||||
.collectLatest {
|
||||
icon = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
badgeRepository.getBadge(searchable.badgeKey).collectLatest {
|
||||
badge = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -13,6 +13,10 @@ import de.mm20.launcher2.ui.legacy.search.*
|
||||
import de.mm20.launcher2.ui.legacy.transition.LauncherCards
|
||||
import de.mm20.launcher2.ui.legacy.transition.LauncherIconViewTransition
|
||||
import de.mm20.launcher2.ui.legacy.view.AspectRationImageView
|
||||
import kotlinx.coroutines.CoroutineScope
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.cancel
|
||||
|
||||
@SuppressLint("ViewConstructor")
|
||||
open class SearchableView(context: Context, representation: Int) : FrameLayout(context) {
|
||||
@@ -26,6 +30,8 @@ open class SearchableView(context: Context, representation: Int) : FrameLayout(c
|
||||
|
||||
private var defaultRepresentation = representation
|
||||
|
||||
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||
|
||||
var representation = representation
|
||||
set(value) {
|
||||
val oldVal = field
|
||||
@@ -107,8 +113,11 @@ open class SearchableView(context: Context, representation: Int) : FrameLayout(c
|
||||
|
||||
private fun applyScene(scene: Scene) {
|
||||
val transition = TransitionSet().apply {
|
||||
addTransition(ChangeBounds().setInterpolator(DecelerateInterpolator()).excludeTarget(
|
||||
AspectRationImageView::class.java, true))
|
||||
addTransition(
|
||||
ChangeBounds().setInterpolator(DecelerateInterpolator()).excludeTarget(
|
||||
AspectRationImageView::class.java, true
|
||||
)
|
||||
)
|
||||
addTransition(LauncherIconViewTransition())
|
||||
addTransition(TextResize())
|
||||
addTransition(LauncherCards())
|
||||
@@ -132,12 +141,21 @@ open class SearchableView(context: Context, representation: Int) : FrameLayout(c
|
||||
return defaultRepresentation != REPRESENTATION_FULL
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
scope.cancel()
|
||||
}
|
||||
|
||||
companion object {
|
||||
const val REPRESENTATION_GRID = 0
|
||||
const val REPRESENTATION_LIST = 1
|
||||
const val REPRESENTATION_FULL = 2
|
||||
|
||||
fun getView(context: Context, searchable: Searchable?, representation: Int): SearchableView {
|
||||
fun getView(
|
||||
context: Context,
|
||||
searchable: Searchable?,
|
||||
representation: Int
|
||||
): SearchableView {
|
||||
return SearchableView(context, representation)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.graphics.*
|
||||
import android.graphics.drawable.AdaptiveIconDrawable
|
||||
import android.os.Build
|
||||
import android.util.AttributeSet
|
||||
import android.util.Log
|
||||
import android.view.HapticFeedbackConstants
|
||||
import android.view.MotionEvent
|
||||
import android.view.View
|
||||
@@ -14,23 +15,34 @@ import android.view.ViewConfiguration
|
||||
import androidx.annotation.RequiresApi
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import com.bartoszlipinski.viewpropertyobjectanimator.ViewPropertyObjectAnimator
|
||||
import de.mm20.launcher2.badges.Badge
|
||||
import de.mm20.launcher2.badges.BadgeRepository
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.ktx.toRectF
|
||||
import de.mm20.launcher2.icons.LauncherIcon
|
||||
import de.mm20.launcher2.ktx.lifecycleOwner
|
||||
import de.mm20.launcher2.ktx.lifecycleScope
|
||||
import de.mm20.launcher2.preferences.IconShape
|
||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.legacy.helper.BitmapHolder
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.MutableStateFlow
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.lang.Math.pow
|
||||
import java.lang.Runnable
|
||||
import kotlin.math.abs
|
||||
import kotlin.math.hypot
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class LauncherIconView : View {
|
||||
class LauncherIconView : View, KoinComponent {
|
||||
constructor(context: Context) : super(context)
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(context, attrs, defStyleRes)
|
||||
@@ -71,17 +83,12 @@ class LauncherIconView : View {
|
||||
invalidate()
|
||||
}
|
||||
|
||||
var badge: LiveData<Badge>? = null
|
||||
var badge: Badge? = null
|
||||
set(value) {
|
||||
field = value
|
||||
value?.observe(context as AppCompatActivity, badgeObserver)
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private val badgeObserver = Observer<Badge> {
|
||||
invalidate()
|
||||
}
|
||||
|
||||
private val iconObserver: (LauncherIcon) -> Unit = {
|
||||
foregroundScale = it.foregroundScale
|
||||
backgroundScale = it.backgroundScale
|
||||
@@ -341,13 +348,11 @@ class LauncherIconView : View {
|
||||
badgeRect.right = drawRect.right.toFloat()
|
||||
badgeRect.bottom = drawRect.bottom.toFloat()
|
||||
|
||||
val badge = badge?.value ?: return
|
||||
val badge = badge ?: return
|
||||
val badgeNumber = badge.number
|
||||
val badgeProgress = badge.progress
|
||||
val badgeIcon = badge.icon ?: badge.iconRes?.let { ContextCompat.getDrawable(context, it) }
|
||||
|
||||
if (badgeNumber == null && badgeProgress == null && badgeIcon == null) return
|
||||
|
||||
badgePaint.color = icon?.badgeColor ?: 0
|
||||
canvas.drawOval(badgeRect, badgeShadowPaint)
|
||||
canvas.drawOval(badgeRect, badgePaint)
|
||||
|
||||
@@ -23,6 +23,7 @@ import de.mm20.launcher2.ui.base.BaseActivity
|
||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||
import de.mm20.launcher2.ui.settings.about.AboutSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.appearance.AppearanceSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.badges.BadgeSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.calendarwidget.CalendarWidgetSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.clockwidget.ClockWidgetSettingsScreen
|
||||
import de.mm20.launcher2.ui.settings.debug.DebugSettingsScreen
|
||||
@@ -109,6 +110,9 @@ class SettingsActivity : BaseActivity() {
|
||||
composable("settings/widgets/clock") {
|
||||
ClockWidgetSettingsScreen()
|
||||
}
|
||||
composable("settings/badges") {
|
||||
BadgeSettingsScreen()
|
||||
}
|
||||
composable("settings/about") {
|
||||
AboutSettingsScreen()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,55 @@
|
||||
package de.mm20.launcher2.ui.settings.badges
|
||||
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.preferences.PreferenceScreen
|
||||
import de.mm20.launcher2.ui.component.preferences.SwitchPreference
|
||||
|
||||
@Composable
|
||||
fun BadgeSettingsScreen() {
|
||||
val viewModel: BadgeSettingsScreenVM = viewModel()
|
||||
PreferenceScreen(title = stringResource(R.string.preference_screen_badges)) {
|
||||
item {
|
||||
val notifications by viewModel.notifications.observeAsState()
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.preference_notification_badges),
|
||||
summary = stringResource(R.string.preference_notification_badges_summary),
|
||||
value = notifications == true,
|
||||
onValueChanged = {
|
||||
viewModel.setNotifications(it)
|
||||
}
|
||||
)
|
||||
val cloudFiles by viewModel.cloudFiles.observeAsState()
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.preference_cloud_badges),
|
||||
summary = stringResource(R.string.preference_cloud_badges_summary),
|
||||
value = cloudFiles == true,
|
||||
onValueChanged = {
|
||||
viewModel.setCloudFiles(it)
|
||||
}
|
||||
)
|
||||
val suspendedApps by viewModel.suspendedApps.observeAsState()
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.preference_suspended_badges),
|
||||
summary = stringResource(R.string.preference_suspended_badges_summary),
|
||||
value = suspendedApps == true,
|
||||
onValueChanged = {
|
||||
viewModel.setSuspendedApps(it)
|
||||
}
|
||||
)
|
||||
val shortcuts by viewModel.shortcuts.observeAsState()
|
||||
SwitchPreference(
|
||||
title = stringResource(R.string.preference_shortcut_badges),
|
||||
summary = stringResource(R.string.preference_shortcut_badges_summary),
|
||||
value = shortcuts == true,
|
||||
onValueChanged = {
|
||||
viewModel.setShortcuts(it)
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,71 @@
|
||||
package de.mm20.launcher2.ui.settings.badges
|
||||
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import kotlinx.coroutines.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class BadgeSettingsScreenVM : ViewModel(), KoinComponent {
|
||||
|
||||
private val dataStore: LauncherDataStore by inject()
|
||||
|
||||
val notifications = dataStore.data.map { it.badges.notifications }.asLiveData()
|
||||
fun setNotifications(notifications: Boolean) {
|
||||
viewModelScope.launch {
|
||||
dataStore.updateData {
|
||||
it.toBuilder()
|
||||
.setBadges(
|
||||
it.badges.toBuilder()
|
||||
.setNotifications(notifications)
|
||||
)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val cloudFiles = dataStore.data.map { it.badges.cloudFiles }.asLiveData()
|
||||
fun setCloudFiles(cloudFiles: Boolean) {
|
||||
viewModelScope.launch {
|
||||
dataStore.updateData {
|
||||
it.toBuilder()
|
||||
.setBadges(
|
||||
it.badges.toBuilder()
|
||||
.setCloudFiles(cloudFiles)
|
||||
)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val shortcuts = dataStore.data.map { it.badges.shortcuts }.asLiveData()
|
||||
fun setShortcuts(shortcuts: Boolean) {
|
||||
viewModelScope.launch {
|
||||
dataStore.updateData {
|
||||
it.toBuilder()
|
||||
.setBadges(
|
||||
it.badges.toBuilder()
|
||||
.setShortcuts(shortcuts)
|
||||
)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
val suspendedApps = dataStore.data.map { it.badges.suspendedApps }.asLiveData()
|
||||
fun setSuspendedApps(suspendedApps: Boolean) {
|
||||
viewModelScope.launch {
|
||||
dataStore.updateData {
|
||||
it.toBuilder()
|
||||
.setBadges(
|
||||
it.badges.toBuilder()
|
||||
.setSuspendedApps(suspendedApps)
|
||||
)
|
||||
.build()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -58,6 +58,18 @@
|
||||
app:layout_constraintTop_toBottomOf="@+id/appName"
|
||||
tools:text="Version 1.0\ncom.app.name" />
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/notifications"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:chipSpacingVertical="4dp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/icon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/appInfo"/>
|
||||
|
||||
<com.google.android.material.chip.ChipGroup
|
||||
android:id="@+id/appShortcuts"
|
||||
android:layout_width="0dp"
|
||||
@@ -67,9 +79,7 @@
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/icon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/appInfo">
|
||||
|
||||
</com.google.android.material.chip.ChipGroup>
|
||||
app:layout_constraintTop_toBottomOf="@+id/notifications"/>
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier2"
|
||||
|
||||
Reference in New Issue
Block a user