Remove unused code
This commit is contained in:
parent
5e18d25335
commit
07573fe72f
@ -7,12 +7,10 @@ import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import androidx.activity.viewModels
|
||||
import androidx.core.view.*
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.ViewTreeLifecycleOwner
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.savedstate.ViewTreeSavedStateRegistryOwner
|
||||
import androidx.core.view.WindowCompat
|
||||
import androidx.core.view.WindowInsetsCompat
|
||||
import androidx.core.view.WindowInsetsControllerCompat
|
||||
import androidx.core.view.doOnNextLayout
|
||||
import com.afollestad.materialdialogs.LayoutMode
|
||||
import com.afollestad.materialdialogs.MaterialDialog
|
||||
import com.afollestad.materialdialogs.bottomsheets.BottomSheet
|
||||
@ -25,9 +23,6 @@ import de.mm20.launcher2.ui.base.BaseActivity
|
||||
import de.mm20.launcher2.ui.databinding.ActivityLauncherBinding
|
||||
import de.mm20.launcher2.ui.launcher.modals.EditFavoritesView
|
||||
import de.mm20.launcher2.ui.launcher.modals.HiddenItemsView
|
||||
import de.mm20.launcher2.ui.legacy.helper.ActivityStarter
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.android.ext.android.inject
|
||||
|
||||
|
||||
@ -61,7 +56,8 @@ class LauncherActivity : BaseActivity() {
|
||||
}
|
||||
|
||||
val windowController = WindowInsetsControllerCompat(window, binding.rootView)
|
||||
windowController.systemBarsBehavior = WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
windowController.systemBarsBehavior =
|
||||
WindowInsetsControllerCompat.BEHAVIOR_SHOW_TRANSIENT_BARS_BY_SWIPE
|
||||
|
||||
viewModel.lightStatusBar.observe(this) {
|
||||
windowController.isAppearanceLightStatusBars = it
|
||||
@ -133,7 +129,6 @@ class LauncherActivity : BaseActivity() {
|
||||
|
||||
override fun onResume() {
|
||||
super.onResume()
|
||||
ActivityStarter.create(binding.rootView)
|
||||
binding.activityStartOverlay.visibility = View.INVISIBLE
|
||||
|
||||
binding.container.doOnNextLayout {
|
||||
|
||||
@ -1,35 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.fragment
|
||||
|
||||
import android.os.Bundle
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import com.google.android.material.bottomsheet.BottomSheetDialogFragment
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||
|
||||
class SearchableBottomSheet(val searchable: Searchable) : BottomSheetDialogFragment() {
|
||||
|
||||
private var view: SearchableView? = null
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
setStyle(BottomSheetDialogFragment.STYLE_NORMAL, R.style.TransparentBottomSheetTheme)
|
||||
}
|
||||
|
||||
override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View? {
|
||||
view = SearchableView(requireContext(), SearchableView.REPRESENTATION_FULL)
|
||||
view?.searchable = searchable
|
||||
view?.onBack = {
|
||||
dismiss()
|
||||
}
|
||||
return view
|
||||
}
|
||||
|
||||
override fun onDestroyView() {
|
||||
super.onDestroyView()
|
||||
view?.searchable = null
|
||||
view = null
|
||||
}
|
||||
}
|
||||
@ -1,104 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.helper
|
||||
|
||||
import android.app.PendingIntent
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Rect
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.view.ViewGroupOverlay
|
||||
import android.widget.Toast
|
||||
import androidx.core.app.ActivityOptionsCompat
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.R
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
object ActivityStarter : KoinComponent {
|
||||
|
||||
private val favoritesRepository: FavoritesRepository by inject()
|
||||
private lateinit var overlayView: WeakReference<ViewGroupOverlay>
|
||||
private lateinit var rootView: WeakReference<ViewGroup>
|
||||
|
||||
fun create(rootView: ViewGroup) {
|
||||
ActivityStarter.rootView = WeakReference(rootView)
|
||||
overlayView = WeakReference(rootView.overlay)
|
||||
}
|
||||
|
||||
fun start(
|
||||
context: Context,
|
||||
transitionView: View,
|
||||
item: Searchable? = null,
|
||||
intent: Intent? = null,
|
||||
pendingIntent: PendingIntent? = null
|
||||
): Boolean {
|
||||
if (!startActivity(context, item, intent, pendingIntent, transitionView)) return false
|
||||
|
||||
return true
|
||||
}
|
||||
|
||||
private fun startActivity(
|
||||
context: Context,
|
||||
item: Searchable? = null,
|
||||
intent: Intent? = null,
|
||||
pendingIntent: PendingIntent? = null,
|
||||
sourceView: View
|
||||
): Boolean {
|
||||
val pos = intArrayOf(0, 0)
|
||||
sourceView.getLocationOnScreen(pos)
|
||||
val sourceBounds =
|
||||
Rect(pos[0], pos[1], pos[0] + sourceView.width, pos[1] + sourceView.height)
|
||||
|
||||
val bundle = getActivityOptions(sourceView, sourceBounds).toBundle()
|
||||
|
||||
if (pendingIntent != null) {
|
||||
return try {
|
||||
pendingIntent.send()
|
||||
true
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
if (item != null) {
|
||||
if (item.launch(context, bundle)) {
|
||||
favoritesRepository.incrementLaunchCounter(item)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
val i = intent ?: return false
|
||||
|
||||
return if (i.resolveActivity(context.packageManager) != null) {
|
||||
context.startActivity(i, bundle)
|
||||
true
|
||||
} else {
|
||||
Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show()
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
private fun getActivityOptions(
|
||||
sourceView: View,
|
||||
sourceBounds: Rect?
|
||||
): ActivityOptionsCompat {
|
||||
return ActivityOptionsCompat.makeClipRevealAnimation(
|
||||
sourceView,
|
||||
0,
|
||||
0,
|
||||
sourceView.width,
|
||||
sourceView.height
|
||||
)
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
interface ActivityStarterCallback {
|
||||
fun onResume()
|
||||
}
|
||||
@ -1,457 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.search
|
||||
|
||||
import android.app.Notification
|
||||
import android.app.PendingIntent
|
||||
import android.app.ProgressDialog
|
||||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.content.pm.ApplicationInfo
|
||||
import android.content.pm.LauncherApps
|
||||
import android.content.pm.PackageInstaller
|
||||
import android.content.pm.PackageManager
|
||||
import android.content.res.ColorStateList
|
||||
import android.graphics.PorterDuff
|
||||
import android.graphics.PorterDuffColorFilter
|
||||
import android.graphics.drawable.Drawable
|
||||
import android.graphics.drawable.LayerDrawable
|
||||
import android.graphics.drawable.ShapeDrawable
|
||||
import android.graphics.drawable.shapes.OvalShape
|
||||
import android.net.Uri
|
||||
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
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.content.FileProvider
|
||||
import androidx.core.content.getSystemService
|
||||
import androidx.core.graphics.alpha
|
||||
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.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.lifecycleOwner
|
||||
import de.mm20.launcher2.notifications.NotificationRepository
|
||||
import de.mm20.launcher2.search.data.AppInstallation
|
||||
import de.mm20.launcher2.search.data.Application
|
||||
import de.mm20.launcher2.search.data.LauncherApp
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.transition.ChangingLayoutTransition
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.legacy.helper.ActivityStarter
|
||||
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.flow.map
|
||||
import kotlinx.coroutines.launch
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.util.concurrent.Executors
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
class ApplicationDetailRepresentation : Representation, KoinComponent {
|
||||
|
||||
private val iconRepository: IconRepository by inject()
|
||||
private val badgeRepository: BadgeRepository by inject()
|
||||
private val notificationRepository: NotificationRepository by inject()
|
||||
|
||||
private var job: Job? = null
|
||||
|
||||
override fun getScene(
|
||||
rootView: SearchableView,
|
||||
searchable: Searchable,
|
||||
previousRepresentation: Int?
|
||||
): Scene {
|
||||
val application = searchable as Application
|
||||
val context = rootView.context as AppCompatActivity
|
||||
val scene = Scene.getSceneForLayout(rootView, R.layout.view_application_detail, context)
|
||||
scene.setEnterAction {
|
||||
with(rootView) {
|
||||
setOnClickListener(null)
|
||||
setOnLongClickListener(null)
|
||||
findViewById<TextView>(R.id.appName).text = application.label
|
||||
val iconView = findViewById<LauncherIconView>(R.id.icon).apply {
|
||||
icon = iconRepository.getIconIfCached(application)
|
||||
shape = LauncherIconView.currentShape
|
||||
}
|
||||
|
||||
val notificationView = findViewById<ChipGroup>(R.id.notifications)
|
||||
notificationView.layoutTransition = ChangingLayoutTransition()
|
||||
|
||||
job = rootView.scope.launch {
|
||||
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
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)
|
||||
}
|
||||
}
|
||||
launch {
|
||||
LauncherIconView.getDefaultShape().collectLatest {
|
||||
iconView.shape = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
findViewById<SwipeCardView>(R.id.appCard).also {
|
||||
it.leftAction = FavoriteSwipeAction(context, application)
|
||||
it.rightAction = HideSwipeAction(context, application)
|
||||
}
|
||||
val appInfo = findViewById<TextView>(R.id.appInfo)
|
||||
appInfo.text = if (application !is AppInstallation) {
|
||||
context.getString(
|
||||
R.string.app_info,
|
||||
application.version ?: "",
|
||||
application.`package`
|
||||
)
|
||||
} else {
|
||||
val callback = object : PackageInstaller.SessionCallback() {
|
||||
override fun onActiveChanged(p0: Int, p1: Boolean) {
|
||||
}
|
||||
|
||||
override fun onFinished(sessionId: Int, success: Boolean) {
|
||||
if (sessionId == application.session.sessionId) {
|
||||
context.packageManager.packageInstaller.unregisterSessionCallback(
|
||||
this
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onBadgingChanged(p0: Int) {
|
||||
}
|
||||
|
||||
override fun onCreated(p0: Int) {
|
||||
}
|
||||
|
||||
override fun onProgressChanged(sessionId: Int, progress: Float) {
|
||||
if (sessionId == application.session.sessionId) {
|
||||
appInfo.text = context.getString(
|
||||
R.string.installation_in_progress,
|
||||
(progress * 100).roundToInt()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
context.packageManager.packageInstaller.registerSessionCallback(callback)
|
||||
context.getString(
|
||||
R.string.installation_in_progress,
|
||||
(application.session.progress * 100).roundToInt()
|
||||
)
|
||||
}
|
||||
|
||||
val appShortcuts = findViewById<ChipGroup>(R.id.appShortcuts)
|
||||
appShortcuts.layoutTransition = ChangingLayoutTransition()
|
||||
setupShortcuts(appShortcuts, application)
|
||||
|
||||
val toolbar = findViewById<ToolbarView>(R.id.appToolbar)
|
||||
setupToolbar(this, toolbar, application)
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
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()
|
||||
|
||||
val backAction =
|
||||
ToolbarAction(R.drawable.ic_arrow_back, context.getString(R.string.menu_back))
|
||||
backAction.clickAction = {
|
||||
rootView.back()
|
||||
}
|
||||
toolbar.addAction(backAction, ToolbarView.PLACEMENT_START)
|
||||
|
||||
if (app !is AppInstallation) {
|
||||
val favAction = FavoriteToolbarAction(context, app)
|
||||
toolbar.addAction(favAction, ToolbarView.PLACEMENT_END)
|
||||
}
|
||||
|
||||
if (app !is AppInstallation) {
|
||||
val infoAction =
|
||||
ToolbarAction(R.drawable.ic_info_outline, context.getString(R.string.menu_app_info))
|
||||
infoAction.clickAction = {
|
||||
val launcherApps = context.getSystemService<LauncherApps>()!!
|
||||
launcherApps.startAppDetailsActivity(
|
||||
ComponentName(app.`package`, app.activity),
|
||||
app.castToOrNull<LauncherApp>()?.getUser() ?: Process.myUserHandle(),
|
||||
null,
|
||||
null
|
||||
)
|
||||
}
|
||||
toolbar.addAction(infoAction, ToolbarView.PLACEMENT_END)
|
||||
}
|
||||
|
||||
val shareAction = ToolbarAction(R.drawable.ic_share, context.getString(R.string.menu_share))
|
||||
val storeDetails = app.getStoreDetails(context)
|
||||
if (app !is AppInstallation) {
|
||||
if (storeDetails == null) {
|
||||
shareAction.clickAction = {
|
||||
shareApk(context, app)
|
||||
}
|
||||
} else {
|
||||
shareAction.subActions.add(ToolbarSubaction(
|
||||
context.getString(R.string.share_menu_store_link, storeDetails.label)
|
||||
) { shareLink(context, storeDetails.url) })
|
||||
|
||||
shareAction.subActions.add(ToolbarSubaction(
|
||||
context.getString(R.string.share_menu_apk_file)
|
||||
) { shareApk(context, app) })
|
||||
}
|
||||
toolbar.addAction(shareAction, ToolbarView.PLACEMENT_END)
|
||||
} else {
|
||||
if (storeDetails != null) {
|
||||
shareAction.clickAction = {
|
||||
shareLink(context, storeDetails.url)
|
||||
}
|
||||
toolbar.addAction(shareAction, ToolbarView.PLACEMENT_END)
|
||||
}
|
||||
}
|
||||
|
||||
if (app !is AppInstallation) {
|
||||
if (app.flags and ApplicationInfo.FLAG_SYSTEM == 0) {
|
||||
val uninstallAction =
|
||||
ToolbarAction(R.drawable.ic_delete, context.getString(R.string.menu_uninstall))
|
||||
uninstallAction.clickAction = {
|
||||
val intent = Intent(Intent.ACTION_DELETE)
|
||||
intent.data = Uri.parse("package:" + app.`package`)
|
||||
context.startActivity(intent)
|
||||
rootView.back()
|
||||
}
|
||||
toolbar.addAction(uninstallAction, ToolbarView.PLACEMENT_END)
|
||||
}
|
||||
|
||||
val hideAction = VisibilityToolbarAction(context, app)
|
||||
toolbar.addAction(hideAction, ToolbarView.PLACEMENT_END)
|
||||
}
|
||||
}
|
||||
|
||||
private fun setupShortcuts(appShortcuts: ChipGroup, app: Application) {
|
||||
val context = appShortcuts.context
|
||||
val launcherApps =
|
||||
context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps
|
||||
if (launcherApps.hasShortcutHostPermission()) {
|
||||
val shortcuts = app.shortcuts
|
||||
|
||||
val repository: FavoritesRepository by inject()
|
||||
|
||||
var count = 0
|
||||
for (si in shortcuts) {
|
||||
if (count > 4) break
|
||||
count++
|
||||
val view = Chip(context)
|
||||
view.text = si.label
|
||||
|
||||
view.chipIcon = createShortcutDrawable(
|
||||
launcherApps.getShortcutBadgedIconDrawable(
|
||||
si.launcherShortcut,
|
||||
context.resources.displayMetrics.densityDpi
|
||||
)
|
||||
)
|
||||
|
||||
view.chipIconSize = 24 * context.dp
|
||||
|
||||
view.chipIconTint = null
|
||||
|
||||
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.closeIcon = context.getDrawable(R.drawable.ic_star_solid)
|
||||
view.closeIconTint = ColorStateList.valueOf(
|
||||
ContextCompat.getColor(
|
||||
context,
|
||||
R.color.text_color_primary
|
||||
)
|
||||
)
|
||||
val isPinned = repository.isPinned(si).asLiveData()
|
||||
|
||||
isPinned.observe(context as LifecycleOwner, Observer {
|
||||
view.isCloseIconVisible = isPinned.value == true
|
||||
})
|
||||
|
||||
view.setOnClickListener {
|
||||
ActivityStarter.start(context, view)
|
||||
launcherApps.startShortcut(si.launcherShortcut, null, null)
|
||||
}
|
||||
view.setOnLongClickListener {
|
||||
if (isPinned.value == true) {
|
||||
repository.unpinItem(si)
|
||||
} else {
|
||||
repository.pinItem(si)
|
||||
}
|
||||
true
|
||||
}
|
||||
view.setOnCloseIconClickListener {
|
||||
repository.unpinItem(si)
|
||||
view.isCloseIconVisible = false
|
||||
}
|
||||
appShortcuts.addView(view)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun createShortcutDrawable(drawable: Drawable?): Drawable {
|
||||
val bgShape = ShapeDrawable(OvalShape()).apply {
|
||||
paint.color = 0xFFF5F5F5.toInt()
|
||||
}
|
||||
if (drawable == null) return bgShape
|
||||
return LayerDrawable(
|
||||
arrayOf(
|
||||
bgShape,
|
||||
drawable
|
||||
)
|
||||
)
|
||||
}
|
||||
|
||||
private fun getNotificationChipIcon(context: Context, notification: Notification): Drawable? {
|
||||
return notification.getBadgeIcon(context, context.packageName)?.let {
|
||||
val _4dp = (4 * context.dp).roundToInt()
|
||||
return@let LayerDrawable(arrayOf(
|
||||
ShapeDrawable(
|
||||
OvalShape()
|
||||
).apply {
|
||||
colorFilter = PorterDuffColorFilter(
|
||||
ContextCompat.getColor(context, R.color.shortcut_icon_background),
|
||||
PorterDuff.Mode.SRC_ATOP
|
||||
)
|
||||
},
|
||||
it.apply {
|
||||
colorFilter = PorterDuffColorFilter(
|
||||
notification.color.takeIf { it.alpha > 0 }
|
||||
?: ContextCompat.getColor(context, R.color.text_color_secondary),
|
||||
PorterDuff.Mode.SRC_ATOP
|
||||
)
|
||||
}
|
||||
)).apply {
|
||||
setLayerInset(1, _4dp, _4dp, _4dp, _4dp)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun shareLink(context: Context, storeUrl: String) {
|
||||
val shareIntent = Intent(Intent.ACTION_SEND)
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, storeUrl)
|
||||
shareIntent.type = "text/plain"
|
||||
context.startActivity(Intent.createChooser(shareIntent, null))
|
||||
}
|
||||
|
||||
private fun shareApk(context: Context, app: Application) {
|
||||
val handler = Handler()
|
||||
val progressDialog = ProgressDialog(context)
|
||||
progressDialog.show()
|
||||
val executor = Executors.newSingleThreadExecutor()
|
||||
executor.execute {
|
||||
try {
|
||||
val info = context.packageManager
|
||||
.getApplicationInfo(app.`package`, 0)
|
||||
val file = java.io.File(info.publicSourceDir)
|
||||
val fileCopy = java.io.File(
|
||||
context.cacheDir,
|
||||
"${app.`package`}-${app.version}.apk"
|
||||
)
|
||||
try {
|
||||
file.copyTo(fileCopy, false)
|
||||
} catch (e: FileAlreadyExistsException) {
|
||||
// Do nothing. If the file is already there we don't have to copy it again.
|
||||
}
|
||||
handler.post {
|
||||
progressDialog.hide()
|
||||
val shareIntent = Intent(Intent.ACTION_SEND)
|
||||
shareIntent.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
|
||||
val uri = FileProvider.getUriForFile(
|
||||
context,
|
||||
context.applicationContext.packageName + ".fileprovider",
|
||||
fileCopy
|
||||
)
|
||||
shareIntent.putExtra(Intent.EXTRA_STREAM, uri)
|
||||
shareIntent.type = "application/vnd.android.package-archive"
|
||||
context.startActivity(Intent.createChooser(shareIntent, null))
|
||||
}
|
||||
} catch (e: PackageManager.NameNotFoundException) {
|
||||
CrashReporter.logException(e)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,96 +0,0 @@
|
||||
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.icons.IconRepository
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.ktx.lifecycleOwner
|
||||
import de.mm20.launcher2.ui.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
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class BasicGridRepresentation : Representation, KoinComponent {
|
||||
|
||||
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 context = rootView.context as AppCompatActivity
|
||||
val scene = Scene.getSceneForLayout(rootView, R.layout.view_basic_grid, rootView.context)
|
||||
scene.setEnterAction {
|
||||
with(rootView) {
|
||||
val text = findViewById<TextView>(R.id.label)
|
||||
text.text = searchable.label
|
||||
/*text.alpha = 0f
|
||||
text.animate()
|
||||
.setStartDelay(300)
|
||||
.setDuration(200)
|
||||
.alpha(1f)
|
||||
.start()*/
|
||||
findViewById<LauncherIconView>(R.id.icon).apply {
|
||||
setOnClickListener {
|
||||
if (!ActivityStarter.start(
|
||||
context,
|
||||
rootView.findViewById(R.id.card),
|
||||
item = searchable
|
||||
)
|
||||
) {
|
||||
rootView.representation = SearchableView.REPRESENTATION_FULL
|
||||
}
|
||||
}
|
||||
icon = iconRepository.getIconIfCached(searchable)
|
||||
shape = LauncherIconView.currentShape
|
||||
|
||||
job = rootView.scope.launch {
|
||||
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
launch {
|
||||
iconRepository.getIcon(searchable, (84 * rootView.dp).toInt())
|
||||
.collectLatest {
|
||||
icon = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
badgeRepository.getBadge(searchable.badgeKey).collectLatest {
|
||||
badge = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
LauncherIconView.getDefaultShape().collectLatest {
|
||||
shape = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
setOnLongClickListener {
|
||||
rootView.representation = SearchableView.REPRESENTATION_FULL
|
||||
true
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
scene.setExitAction {
|
||||
job?.cancel()
|
||||
}
|
||||
|
||||
return scene
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,127 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.search
|
||||
|
||||
import android.content.ContentUris
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.CalendarContract
|
||||
import android.text.format.DateUtils
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.TextView
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.transition.Scene
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ktx.setStartCompoundDrawable
|
||||
import de.mm20.launcher2.ui.legacy.helper.ActivityStarter
|
||||
import de.mm20.launcher2.search.data.CalendarEvent
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||
import de.mm20.launcher2.ui.legacy.view.*
|
||||
import java.net.URLEncoder
|
||||
|
||||
class CalendarDetailRepresentation : Representation {
|
||||
override fun getScene(rootView: SearchableView, searchable: Searchable, previousRepresentation: Int?): Scene {
|
||||
val calendarEvent = searchable as CalendarEvent
|
||||
|
||||
val scene = Scene.getSceneForLayout(rootView, R.layout.view_calendar_detail, rootView.context)
|
||||
scene.setEnterAction {
|
||||
with(rootView) {
|
||||
findViewById<TextView>(R.id.calendarLabel).text = calendarEvent.label
|
||||
findViewById<View>(R.id.calendarColor).setBackgroundColor(CalendarEvent.getDisplayColor(context, calendarEvent.color))
|
||||
findViewById<SwipeCardView>(R.id.calendarEventCard).also {
|
||||
it.leftAction = FavoriteSwipeAction(context, calendarEvent)
|
||||
it.rightAction = HideSwipeAction(context, calendarEvent)
|
||||
it.setOnClickListener {
|
||||
rootView.representation = SearchableView.REPRESENTATION_FULL
|
||||
}
|
||||
}
|
||||
val toolbar = findViewById<ToolbarView>(R.id.calendarToolbar)
|
||||
/*toolbar.alpha = 0f
|
||||
toolbar.animate()
|
||||
.setStartDelay(100)
|
||||
.setDuration(200)
|
||||
.alpha(1f)
|
||||
.start()*/
|
||||
setupMenu(rootView, toolbar, calendarEvent)
|
||||
addShortcuts(rootView, calendarEvent)
|
||||
}
|
||||
}
|
||||
return scene
|
||||
}
|
||||
|
||||
private fun setupMenu(rootView: SearchableView, toolbar: ToolbarView, event: CalendarEvent) {
|
||||
|
||||
val context = rootView.context
|
||||
|
||||
val backAction = ToolbarAction(R.drawable.ic_arrow_back, context.getString(R.string.menu_back))
|
||||
backAction.clickAction = {
|
||||
rootView.back()
|
||||
}
|
||||
toolbar.addAction(backAction, ToolbarView.PLACEMENT_START)
|
||||
|
||||
val favAction = FavoriteToolbarAction(context, event)
|
||||
toolbar.addAction(favAction, ToolbarView.PLACEMENT_END)
|
||||
|
||||
val hideAction = VisibilityToolbarAction(context, event)
|
||||
toolbar.addAction(hideAction, ToolbarView.PLACEMENT_END)
|
||||
|
||||
val openAction = ToolbarAction(R.drawable.ic_open_external, context.getString(R.string.calendar_menu_open_externally))
|
||||
openAction.clickAction = {
|
||||
val uri = ContentUris.withAppendedId(CalendarContract.Events.CONTENT_URI, event.id)
|
||||
val intent = Intent(Intent.ACTION_VIEW).setData(uri)
|
||||
ActivityStarter.start(context, rootView, intent = intent)
|
||||
}
|
||||
toolbar.addAction(openAction, ToolbarView.PLACEMENT_END)
|
||||
}
|
||||
|
||||
private fun addShortcuts(rootView: SearchableView, event: CalendarEvent) {
|
||||
|
||||
val context = rootView.context
|
||||
val shortcutContainer = rootView.findViewById<LinearLayout>(R.id.calendarShortcuts)
|
||||
|
||||
val timeView = (View.inflate(context, R.layout.view_list_item, null) as TextView).also {
|
||||
it.setStartCompoundDrawable(R.drawable.ic_time)
|
||||
it.text = formatTime(context, event)
|
||||
}
|
||||
|
||||
shortcutContainer.addView(timeView)
|
||||
|
||||
|
||||
if (event.description.isNotEmpty()) {
|
||||
val descriptionView = (View.inflate(context, R.layout.view_list_item, null) as TextView).also {
|
||||
it.setStartCompoundDrawable(R.drawable.ic_description)
|
||||
it.text = HtmlCompat.fromHtml(event.description, HtmlCompat.FROM_HTML_MODE_COMPACT)
|
||||
}
|
||||
shortcutContainer.addView(descriptionView)
|
||||
}
|
||||
|
||||
if (event.location.isNotEmpty()) {
|
||||
val locationView = (View.inflate(context, R.layout.view_list_item, null) as TextView).also {
|
||||
it.setStartCompoundDrawable(R.drawable.ic_location)
|
||||
it.text = event.location
|
||||
it.setOnClickListener {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse("geo:0,0?q=${URLEncoder.encode(event.location, "utf8")}")
|
||||
ActivityStarter.start(context, rootView, intent = intent)
|
||||
}
|
||||
}
|
||||
shortcutContainer.addView(locationView)
|
||||
}
|
||||
|
||||
if (event.attendees.isNotEmpty()) {
|
||||
val attendeesView = (View.inflate(context, R.layout.view_list_item, null) as TextView).also {
|
||||
it.setStartCompoundDrawable(R.drawable.ic_attendees)
|
||||
it.text = event.attendees.joinToString { it }
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
private fun formatTime(context: Context, event: CalendarEvent): String {
|
||||
if (event.allDay) return DateUtils.formatDateRange(context, event.startTime, event.endTime, DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_WEEKDAY)
|
||||
return DateUtils.formatDateRange(context, event.startTime, event.endTime, DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_WEEKDAY)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,110 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.search
|
||||
|
||||
import android.content.Context
|
||||
import android.text.format.DateUtils
|
||||
import android.view.View
|
||||
import android.widget.TextView
|
||||
import androidx.transition.Scene
|
||||
import de.mm20.launcher2.search.data.CalendarEvent
|
||||
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.SwipeCardView
|
||||
import java.text.DateFormat
|
||||
import java.text.SimpleDateFormat
|
||||
import java.util.*
|
||||
|
||||
class CalendarListRepresentation : Representation {
|
||||
override fun getScene(
|
||||
rootView: SearchableView,
|
||||
searchable: Searchable,
|
||||
previousRepresentation: Int?
|
||||
): Scene {
|
||||
val calendarEvent = searchable as CalendarEvent
|
||||
val scene = Scene.getSceneForLayout(rootView, R.layout.view_calendar_list, rootView.context)
|
||||
scene.setEnterAction {
|
||||
with(rootView) {
|
||||
findViewById<TextView>(R.id.calendarLabel).text = calendarEvent.label
|
||||
|
||||
findViewById<View>(R.id.calendarColor).setBackgroundColor(
|
||||
CalendarEvent.getDisplayColor(
|
||||
context,
|
||||
calendarEvent.color
|
||||
)
|
||||
)
|
||||
findViewById<SwipeCardView>(R.id.calendarEventCard).also {
|
||||
it.leftAction = FavoriteSwipeAction(context, calendarEvent)
|
||||
it.rightAction = HideSwipeAction(context, calendarEvent)
|
||||
it.setOnClickListener {
|
||||
rootView.representation = SearchableView.REPRESENTATION_FULL
|
||||
}
|
||||
}
|
||||
val isToday =
|
||||
DateUtils.isToday(calendarEvent.startTime) && DateUtils.isToday(calendarEvent.endTime)
|
||||
findViewById<TextView>(R.id.eventDateTime).text = if (isToday) {
|
||||
if (calendarEvent.allDay) {
|
||||
context.getString(R.string.calendar_event_allday)
|
||||
} else {
|
||||
DateUtils.formatDateRange(
|
||||
context,
|
||||
calendarEvent.startTime,
|
||||
calendarEvent.endTime,
|
||||
DateUtils.FORMAT_SHOW_TIME
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (calendarEvent.allDay) {
|
||||
DateUtils.formatDateRange(
|
||||
context,
|
||||
calendarEvent.startTime,
|
||||
calendarEvent.endTime,
|
||||
DateUtils.FORMAT_SHOW_DATE
|
||||
)
|
||||
} else {
|
||||
DateUtils.formatDateRange(
|
||||
context,
|
||||
calendarEvent.startTime,
|
||||
calendarEvent.endTime,
|
||||
DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return scene
|
||||
}
|
||||
|
||||
private fun formatTime(context: Context, event: CalendarEvent): String {
|
||||
val df = DateFormat.getTimeInstance(DateFormat.SHORT)
|
||||
return when {
|
||||
event.startTime == event.endTime -> {
|
||||
df.format(Date(event.startTime))
|
||||
}
|
||||
event.allDay -> {
|
||||
context.getString(R.string.calendar_event_allday)
|
||||
}
|
||||
else -> {
|
||||
DateUtils.formatDateRange(
|
||||
context, event.startTime, event.endTime,
|
||||
DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_ABBREV_MONTH
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun formatDate(event: CalendarEvent): Pair<String, String> {
|
||||
val calendar = Calendar.getInstance()
|
||||
calendar.timeInMillis = event.startTime
|
||||
val today = Calendar.getInstance()
|
||||
today.timeInMillis = System.currentTimeMillis()
|
||||
val line1 =
|
||||
if (calendar[Calendar.YEAR] == today[Calendar.YEAR] && calendar[Calendar.MONTH] == calendar[Calendar.MONTH]) {
|
||||
SimpleDateFormat("EEE").format(event.startTime)
|
||||
} else SimpleDateFormat("MMM").format(event.startTime)
|
||||
val line2 = calendar[Calendar.DAY_OF_MONTH].toString()
|
||||
return line1 to line2
|
||||
}
|
||||
|
||||
}
|
||||
@ -1,379 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.search
|
||||
|
||||
import android.content.ActivityNotFoundException
|
||||
import android.content.ContentUris
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.ContactsContract
|
||||
import android.view.Gravity
|
||||
import android.view.Menu
|
||||
import android.view.View
|
||||
import android.widget.LinearLayout
|
||||
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.BadgeRepository
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.ktx.lifecycleOwner
|
||||
import de.mm20.launcher2.ktx.setStartCompoundDrawable
|
||||
import de.mm20.launcher2.ui.legacy.helper.ActivityStarter
|
||||
import de.mm20.launcher2.search.data.Contact
|
||||
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
|
||||
import org.koin.core.component.inject
|
||||
import java.net.URLEncoder
|
||||
|
||||
class ContactDetailRepresentation : Representation, KoinComponent {
|
||||
|
||||
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
|
||||
val context = rootView.context as AppCompatActivity
|
||||
val scene =
|
||||
Scene.getSceneForLayout(rootView, R.layout.view_contact_detail, rootView.context)
|
||||
scene.setEnterAction {
|
||||
with(rootView) {
|
||||
findViewById<LauncherIconView>(R.id.icon).apply {
|
||||
icon = iconRepository.getIconIfCached(contact)
|
||||
shape = LauncherIconView.currentShape
|
||||
job = rootView.scope.launch {
|
||||
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
launch {
|
||||
iconRepository.getIcon(contact, (84 * rootView.dp).toInt())
|
||||
.collectLatest {
|
||||
icon = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
badgeRepository.getBadge(contact.badgeKey).collectLatest {
|
||||
badge = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
LauncherIconView.getDefaultShape().collectLatest {
|
||||
shape = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
findViewById<TextView>(R.id.contactName).text = contact.displayName
|
||||
findViewById<SwipeCardView>(R.id.contactCard).also {
|
||||
it.leftAction = FavoriteSwipeAction(context, contact)
|
||||
it.rightAction = HideSwipeAction(context, contact)
|
||||
}
|
||||
val toolbar = findViewById<ToolbarView>(R.id.contactToolbar)
|
||||
setupMenu(this, toolbar, contact)
|
||||
addShortcuts(rootView, contact)
|
||||
}
|
||||
}
|
||||
scene.setExitAction {
|
||||
job?.cancel()
|
||||
}
|
||||
return scene
|
||||
}
|
||||
|
||||
private fun setupMenu(rootView: SearchableView, toolbar: ToolbarView, contact: Contact) {
|
||||
val context = rootView.context
|
||||
|
||||
val backAction =
|
||||
ToolbarAction(R.drawable.ic_arrow_back, context.getString(R.string.menu_back))
|
||||
backAction.clickAction = {
|
||||
rootView.back()
|
||||
}
|
||||
toolbar.addAction(backAction, ToolbarView.PLACEMENT_START)
|
||||
|
||||
val favAction = FavoriteToolbarAction(context, contact)
|
||||
toolbar.addAction(favAction, ToolbarView.PLACEMENT_END)
|
||||
|
||||
val hideAction = VisibilityToolbarAction(context, contact)
|
||||
toolbar.addAction(hideAction, ToolbarView.PLACEMENT_END)
|
||||
|
||||
val openAction = ToolbarAction(
|
||||
R.drawable.ic_open_external,
|
||||
context.getString(R.string.contacts_menu_open_externally)
|
||||
)
|
||||
openAction.clickAction = {
|
||||
try {
|
||||
val uri =
|
||||
ContentUris.withAppendedId(ContactsContract.Contacts.CONTENT_URI, contact.id)
|
||||
val intent = Intent(Intent.ACTION_VIEW).setData(uri)
|
||||
ActivityStarter.start(context, rootView, intent = intent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
toolbar.addAction(openAction, ToolbarView.PLACEMENT_END)
|
||||
}
|
||||
|
||||
private fun addShortcuts(rootView: SearchableView, contact: Contact) {
|
||||
|
||||
val context = rootView.context
|
||||
val shortcutContainer = rootView.findViewById<LinearLayout>(R.id.contactShortcuts)
|
||||
|
||||
|
||||
if (contact.phones.isNotEmpty()) {
|
||||
val callView = (View.inflate(context, R.layout.view_list_item, null) as TextView).also {
|
||||
it.setStartCompoundDrawable(R.drawable.ic_call)
|
||||
if (contact.phones.size == 1) {
|
||||
it.text = contact.phones.first().label
|
||||
it.setOnClickListener {
|
||||
call(rootView, contact.phones.first().data)
|
||||
}
|
||||
} else {
|
||||
it.text =
|
||||
context.getString(R.string.contact_multiple_numbers, contact.phones.size)
|
||||
it.setOnClickListener {
|
||||
val menu = PopupMenu(context, it, Gravity.START)
|
||||
val phones = contact.phones.toList()
|
||||
for ((i, phone) in phones.withIndex()) {
|
||||
menu.menu.add(Menu.NONE, i, Menu.NONE, phone.label)
|
||||
}
|
||||
menu.setOnMenuItemClickListener {
|
||||
call(rootView, phones[it.itemId].data)
|
||||
true
|
||||
}
|
||||
menu.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
shortcutContainer.addView(callView)
|
||||
}
|
||||
|
||||
if (contact.phones.isNotEmpty()) {
|
||||
val messageView =
|
||||
(View.inflate(context, R.layout.view_list_item, null) as TextView).also {
|
||||
it.setStartCompoundDrawable(R.drawable.ic_message)
|
||||
if (contact.phones.size == 1) {
|
||||
it.text = contact.phones.first().label
|
||||
it.setOnClickListener {
|
||||
message(rootView, contact.phones.first().data)
|
||||
}
|
||||
} else {
|
||||
it.text = context.getString(
|
||||
R.string.contact_multiple_numbers,
|
||||
contact.phones.size
|
||||
)
|
||||
it.setOnClickListener {
|
||||
val menu = PopupMenu(context, it, Gravity.START)
|
||||
val phones = contact.phones.toList()
|
||||
for ((i, phone) in phones.withIndex()) {
|
||||
menu.menu.add(Menu.NONE, i, Menu.NONE, phone.label)
|
||||
}
|
||||
menu.setOnMenuItemClickListener {
|
||||
message(rootView, phones[it.itemId].data)
|
||||
true
|
||||
}
|
||||
menu.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
shortcutContainer.addView(messageView)
|
||||
}
|
||||
|
||||
if (contact.emails.isNotEmpty()) {
|
||||
val emailView =
|
||||
(View.inflate(context, R.layout.view_list_item, null) as TextView).also {
|
||||
it.setStartCompoundDrawable(R.drawable.ic_mail)
|
||||
if (contact.emails.size == 1) {
|
||||
it.text = contact.emails.first().label
|
||||
it.setOnClickListener {
|
||||
email(rootView, contact.emails.first().data)
|
||||
}
|
||||
} else {
|
||||
it.text =
|
||||
context.getString(R.string.contact_multiple_emails, contact.emails.size)
|
||||
it.setOnClickListener {
|
||||
val menu = PopupMenu(context, it, Gravity.START)
|
||||
val emails = contact.emails.toList()
|
||||
for ((i, email) in emails.withIndex()) {
|
||||
menu.menu.add(Menu.NONE, i, Menu.NONE, email.label)
|
||||
}
|
||||
menu.setOnMenuItemClickListener {
|
||||
email(rootView, emails[it.itemId].data)
|
||||
true
|
||||
}
|
||||
menu.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
shortcutContainer.addView(emailView)
|
||||
}
|
||||
|
||||
if (contact.telegram.isNotEmpty()) {
|
||||
val telegramView =
|
||||
(View.inflate(context, R.layout.view_list_item, null) as TextView).also {
|
||||
it.setStartCompoundDrawable(R.drawable.ic_telegram)
|
||||
if (contact.telegram.size == 1) {
|
||||
it.text = contact.telegram.first().label
|
||||
it.setOnClickListener {
|
||||
telegram(rootView, contact.telegram.first().data)
|
||||
}
|
||||
} else {
|
||||
it.text = context.getString(
|
||||
R.string.contact_multiple_numbers,
|
||||
contact.telegram.size
|
||||
)
|
||||
it.setOnClickListener {
|
||||
val menu = PopupMenu(context, it, Gravity.START)
|
||||
val phones = contact.telegram.toList()
|
||||
for ((i, phone) in phones.withIndex()) {
|
||||
menu.menu.add(Menu.NONE, i, Menu.NONE, phone.label)
|
||||
}
|
||||
menu.setOnMenuItemClickListener {
|
||||
telegram(rootView, phones[it.itemId].data)
|
||||
true
|
||||
}
|
||||
menu.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
shortcutContainer.addView(telegramView)
|
||||
}
|
||||
if (contact.whatsapp.isNotEmpty()) {
|
||||
val whatsappView =
|
||||
(View.inflate(context, R.layout.view_list_item, null) as TextView).also {
|
||||
it.setStartCompoundDrawable(R.drawable.ic_whatsapp)
|
||||
if (contact.whatsapp.size == 1) {
|
||||
it.text = contact.whatsapp.first().label
|
||||
it.setOnClickListener {
|
||||
whatsapp(rootView, contact.whatsapp.first().data)
|
||||
}
|
||||
} else {
|
||||
it.text = context.getString(
|
||||
R.string.contact_multiple_numbers,
|
||||
contact.whatsapp.size
|
||||
)
|
||||
it.setOnClickListener {
|
||||
val menu = PopupMenu(context, it, Gravity.START)
|
||||
val phones = contact.whatsapp.toList()
|
||||
for ((i, phone) in phones.withIndex()) {
|
||||
menu.menu.add(Menu.NONE, i, Menu.NONE, phone.label)
|
||||
}
|
||||
menu.setOnMenuItemClickListener {
|
||||
whatsapp(rootView, phones[it.itemId].data)
|
||||
true
|
||||
}
|
||||
menu.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
shortcutContainer.addView(whatsappView)
|
||||
}
|
||||
if (contact.postals.isNotEmpty()) {
|
||||
val locationView =
|
||||
(View.inflate(context, R.layout.view_list_item, null) as TextView).also {
|
||||
it.setStartCompoundDrawable(R.drawable.ic_location)
|
||||
if (contact.postals.size == 1) {
|
||||
it.text = contact.postals.first().label
|
||||
it.setOnClickListener {
|
||||
navigate(rootView, contact.postals.first().data)
|
||||
}
|
||||
} else {
|
||||
it.text = context.getString(
|
||||
R.string.contact_multiple_postals,
|
||||
contact.postals.size
|
||||
)
|
||||
it.setOnClickListener {
|
||||
val menu = PopupMenu(context, it, Gravity.START)
|
||||
val postals = contact.postals.toList()
|
||||
for ((i, postal) in postals.withIndex()) {
|
||||
menu.menu.add(Menu.NONE, i, Menu.NONE, postal.label)
|
||||
}
|
||||
menu.setOnMenuItemClickListener {
|
||||
navigate(rootView, postals[it.itemId].data)
|
||||
true
|
||||
}
|
||||
menu.show()
|
||||
}
|
||||
}
|
||||
}
|
||||
shortcutContainer.addView(locationView)
|
||||
}
|
||||
}
|
||||
|
||||
private fun call(rootView: SearchableView, data: String) {
|
||||
val context = rootView.context
|
||||
try {
|
||||
val callIntent = Intent(Intent.ACTION_DIAL)
|
||||
callIntent.data = Uri.parse(data)
|
||||
ActivityStarter.start(context, rootView, intent = callIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun message(rootView: SearchableView, data: String) {
|
||||
val context = rootView.context
|
||||
try {
|
||||
val messageIntent = Intent(Intent.ACTION_VIEW)
|
||||
messageIntent.data = Uri.parse(data)
|
||||
ActivityStarter.start(context, rootView, intent = messageIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun email(rootView: SearchableView, data: String) {
|
||||
val context = rootView.context
|
||||
try {
|
||||
val mailIntent = Intent(Intent.ACTION_VIEW)
|
||||
mailIntent.data = Uri.parse(data)
|
||||
ActivityStarter.start(context, rootView, intent = mailIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun whatsapp(rootView: SearchableView, data: String) {
|
||||
val context = rootView.context
|
||||
try {
|
||||
val whatsappIntent = Intent(Intent.ACTION_VIEW)
|
||||
whatsappIntent.data = Uri.parse(data)
|
||||
ActivityStarter.start(context, rootView, intent = whatsappIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun telegram(rootView: SearchableView, userId: String) {
|
||||
val context = rootView.context
|
||||
try {
|
||||
val telegramIntent = Intent(Intent.ACTION_VIEW)
|
||||
telegramIntent.data = Uri.parse("tg:openmessage?user_id=$userId")
|
||||
ActivityStarter.start(context, rootView, intent = telegramIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
|
||||
private fun navigate(rootView: SearchableView, location: String) {
|
||||
val context = rootView.context
|
||||
try {
|
||||
val mapsIntent = Intent(Intent.ACTION_VIEW)
|
||||
mapsIntent.data = Uri.parse("geo:0,0?q=${URLEncoder.encode(location, "utf8")}")
|
||||
ActivityStarter.start(context, rootView, intent = mapsIntent)
|
||||
} catch (e: ActivityNotFoundException) {
|
||||
Toast.makeText(context, R.string.activity_not_found, Toast.LENGTH_SHORT).show()
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,89 +0,0 @@
|
||||
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.icons.IconRepository
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.ktx.lifecycleOwner
|
||||
import de.mm20.launcher2.search.data.Contact
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
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
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class ContactListRepresentation : Representation, KoinComponent {
|
||||
|
||||
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
|
||||
val context = rootView.context as AppCompatActivity
|
||||
val scene = Scene.getSceneForLayout(rootView, R.layout.view_contact_list, rootView.context)
|
||||
scene.setEnterAction {
|
||||
with(rootView) {
|
||||
findViewById<LauncherIconView>(R.id.icon).apply {
|
||||
icon = iconRepository.getIconIfCached(contact)
|
||||
shape = LauncherIconView.currentShape
|
||||
job = rootView.scope.launch {
|
||||
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
launch {
|
||||
iconRepository.getIcon(searchable, (84 * rootView.dp).toInt())
|
||||
.collectLatest {
|
||||
icon = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
badgeRepository.getBadge(searchable.badgeKey).collectLatest {
|
||||
badge = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
LauncherIconView.getDefaultShape().collectLatest {
|
||||
shape = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
findViewById<TextView>(R.id.contactName).text = contact.displayName
|
||||
val contactSummary = findViewById<TextView>(R.id.contactSummary)
|
||||
contactSummary.text = contact.summary
|
||||
findViewById<SwipeCardView>(R.id.contactCard).also {
|
||||
it.leftAction = FavoriteSwipeAction(context, contact)
|
||||
it.rightAction = HideSwipeAction(context, contact)
|
||||
it.setOnClickListener {
|
||||
rootView.representation = SearchableView.REPRESENTATION_FULL
|
||||
}
|
||||
}
|
||||
contactSummary.alpha = 0f
|
||||
contactSummary.animate()
|
||||
.setStartDelay(100)
|
||||
.setDuration(200)
|
||||
.alpha(1f)
|
||||
.start()
|
||||
|
||||
}
|
||||
}
|
||||
scene.setExitAction {
|
||||
job?.cancel()
|
||||
}
|
||||
return scene
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@ -1,202 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.search
|
||||
|
||||
import android.content.Context
|
||||
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.badges.BadgeRepository
|
||||
import de.mm20.launcher2.files.FileRepository
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
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.core.component.KoinComponent
|
||||
import org.koin.core.component.get
|
||||
import org.koin.core.component.inject
|
||||
import java.text.DecimalFormat
|
||||
|
||||
class FileDetailRepresentation : Representation, KoinComponent {
|
||||
|
||||
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 file = searchable as File
|
||||
val context = rootView.context as AppCompatActivity
|
||||
val scene = Scene.getSceneForLayout(rootView, R.layout.view_file_detail, rootView.context)
|
||||
scene.setEnterAction {
|
||||
with(rootView) {
|
||||
findViewById<TextView>(R.id.fileLabel).text = file.label
|
||||
findViewById<TextView>(R.id.fileInfo).text = getInfo(context, file)
|
||||
findViewById<LauncherIconView>(R.id.icon).apply {
|
||||
icon = iconRepository.getIconIfCached(file)
|
||||
shape = LauncherIconView.currentShape
|
||||
job = rootView.scope.launch {
|
||||
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
launch {
|
||||
iconRepository.getIcon(searchable, (84 * rootView.dp).toInt())
|
||||
.collectLatest {
|
||||
icon = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
badgeRepository.getBadge(searchable.badgeKey).collectLatest {
|
||||
badge = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
LauncherIconView.getDefaultShape().collectLatest {
|
||||
shape = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
findViewById<SwipeCardView>(R.id.fileCard).also {
|
||||
it.leftAction = FavoriteSwipeAction(context, file)
|
||||
it.rightAction = HideSwipeAction(context, file)
|
||||
}
|
||||
setupMenu(rootView, findViewById(R.id.fileToolbar), file)
|
||||
}
|
||||
}
|
||||
scene.setExitAction {
|
||||
job?.cancel()
|
||||
}
|
||||
return scene
|
||||
}
|
||||
|
||||
private fun setupMenu(rootView: SearchableView, toolbar: ToolbarView, file: File) {
|
||||
val context = toolbar.context
|
||||
toolbar.clear()
|
||||
|
||||
val backAction =
|
||||
ToolbarAction(R.drawable.ic_arrow_back, context.getString(R.string.menu_back))
|
||||
backAction.clickAction = {
|
||||
rootView.back()
|
||||
}
|
||||
toolbar.addAction(backAction, ToolbarView.PLACEMENT_START)
|
||||
|
||||
val favAction = FavoriteToolbarAction(context, file)
|
||||
toolbar.addAction(favAction, ToolbarView.PLACEMENT_END)
|
||||
|
||||
if (file.isDeletable) {
|
||||
val deleteAction =
|
||||
ToolbarAction(R.drawable.ic_delete, context.getString(R.string.menu_delete))
|
||||
deleteAction.clickAction = {
|
||||
delete(context, file)
|
||||
}
|
||||
toolbar.addAction(deleteAction, ToolbarView.PLACEMENT_END)
|
||||
}
|
||||
|
||||
val hideAction = VisibilityToolbarAction(context, file)
|
||||
toolbar.addAction(hideAction, ToolbarView.PLACEMENT_END)
|
||||
|
||||
if (file !is GDriveFile) {
|
||||
val shareAction =
|
||||
ToolbarAction(R.drawable.ic_share, context.getString(R.string.menu_share))
|
||||
shareAction.clickAction = {
|
||||
share(context, file)
|
||||
}
|
||||
toolbar.addAction(shareAction, ToolbarView.PLACEMENT_END)
|
||||
}
|
||||
}
|
||||
|
||||
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, _ ->
|
||||
dialog.dismiss()
|
||||
deleteFile(file)
|
||||
}
|
||||
.setNegativeButton(android.R.string.cancel) { dialog, _ ->
|
||||
dialog.dismiss()
|
||||
}
|
||||
.show()
|
||||
}
|
||||
|
||||
private fun deleteFile(file: File) {
|
||||
val fileRepository: FileRepository = get()
|
||||
fileRepository.deleteFile(file)
|
||||
}
|
||||
|
||||
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)
|
||||
)
|
||||
shareIntent.putExtra(Intent.EXTRA_STREAM, uri)
|
||||
shareIntent.type = fileDetail.mimeType
|
||||
context.startActivity(Intent.createChooser(shareIntent, null))
|
||||
}
|
||||
|
||||
private fun getInfo(context: Context, file: File): String {
|
||||
val sb = StringBuilder()
|
||||
|
||||
sb.append(
|
||||
context.getString(
|
||||
R.string.file_meta_type,
|
||||
file.mimeType
|
||||
)
|
||||
)
|
||||
|
||||
for ((k, v) in file.metaData) {
|
||||
sb.append("\n")
|
||||
.append(context.getString(k, v))
|
||||
}
|
||||
if (!file.isDirectory) {
|
||||
sb.append("\n").append(
|
||||
context.getString(
|
||||
R.string.file_meta_size,
|
||||
formatFileSize(file.size)
|
||||
)
|
||||
)
|
||||
}
|
||||
if (file.path.isNotEmpty()) {
|
||||
sb.append("\n").append(
|
||||
context.getString(
|
||||
R.string.file_meta_path,
|
||||
file.path
|
||||
)
|
||||
)
|
||||
}
|
||||
return sb.toString()
|
||||
}
|
||||
|
||||
private fun formatFileSize(size: Long): String {
|
||||
return when {
|
||||
size < 1000L -> "$size Bytes"
|
||||
size < 1000000L -> "${DecimalFormat("#,##0.#").format(size / 1000.0)} kB"
|
||||
size < 1000000000L -> "${DecimalFormat("#,##0.#").format(size / 1000000.0)} MB"
|
||||
size < 1000000000000L -> "${DecimalFormat("#,##0.#").format(size / 1000000000.0)} GB"
|
||||
else -> "${DecimalFormat("#,##0.#").format(size / 1000000000000.0)} TB"
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,88 +0,0 @@
|
||||
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.icons.IconRepository
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.ktx.lifecycleOwner
|
||||
import de.mm20.launcher2.ui.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
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class FileListRepresentation : Representation, KoinComponent {
|
||||
|
||||
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 file = searchable as File
|
||||
val context = rootView.context as AppCompatActivity
|
||||
val scene = Scene.getSceneForLayout(rootView, R.layout.view_file_list, rootView.context)
|
||||
scene.setEnterAction {
|
||||
with(rootView) {
|
||||
findViewById<TextView>(R.id.fileLabel).text = file.label
|
||||
findViewById<TextView>(R.id.fileInfo).text = file.getFileType(context)
|
||||
findViewById<LauncherIconView>(R.id.icon).apply {
|
||||
icon = iconRepository.getIconIfCached(file)
|
||||
shape = LauncherIconView.currentShape
|
||||
job = rootView.scope.launch {
|
||||
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
launch {
|
||||
iconRepository.getIcon(searchable, (84 * rootView.dp).toInt())
|
||||
.collectLatest {
|
||||
icon = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
badgeRepository.getBadge(searchable.badgeKey).collectLatest {
|
||||
badge = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
LauncherIconView.getDefaultShape().collectLatest {
|
||||
shape = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
findViewById<SwipeCardView>(R.id.fileCard).apply {
|
||||
setOnClickListener {
|
||||
ActivityStarter.start(context, rootView, item = file)
|
||||
}
|
||||
setOnLongClickListener {
|
||||
rootView.representation = SearchableView.REPRESENTATION_FULL
|
||||
true
|
||||
}
|
||||
leftAction = FavoriteSwipeAction(context, file)
|
||||
rightAction = HideSwipeAction(context, file)
|
||||
}
|
||||
}
|
||||
}
|
||||
scene.setExitAction {
|
||||
job?.cancel()
|
||||
}
|
||||
return scene
|
||||
}
|
||||
}
|
||||
@ -1,25 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.search
|
||||
|
||||
import android.widget.TextView
|
||||
import androidx.transition.Scene
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.legacy.data.InformationText
|
||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||
import de.mm20.launcher2.ui.legacy.view.InnerCardView
|
||||
|
||||
class InformationListRepresentation: Representation {
|
||||
override fun getScene(rootView: SearchableView, searchable: Searchable, previousRepresentation: Int?): Scene {
|
||||
val informationText = searchable as InformationText
|
||||
val scene = Scene.getSceneForLayout(rootView, R.layout.view_information_list, rootView.context)
|
||||
scene.setEnterAction {
|
||||
rootView.findViewById<TextView>(R.id.informationText).text = informationText.label
|
||||
if (informationText.clickAction != null) {
|
||||
rootView.findViewById<InnerCardView>(R.id.card).setOnClickListener {
|
||||
informationText.clickAction.invoke()
|
||||
}
|
||||
}
|
||||
}
|
||||
return scene
|
||||
}
|
||||
}
|
||||
@ -1,55 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.search
|
||||
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.material3.TextButton
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.transition.Scene
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.search.data.MissingPermission
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.MdcLauncherTheme
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.MissingPermissionBanner
|
||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.get
|
||||
|
||||
class PermissionListRepresentation : Representation, KoinComponent {
|
||||
override fun getScene(
|
||||
rootView: SearchableView,
|
||||
searchable: Searchable,
|
||||
previousRepresentation: Int?
|
||||
): Scene {
|
||||
val missingPermission = searchable as MissingPermission
|
||||
val context = rootView.context
|
||||
val scene =
|
||||
Scene.getSceneForLayout(rootView, R.layout.view_permission_list, rootView.context)
|
||||
scene.setEnterAction {
|
||||
val permissionsManager: PermissionsManager = get()
|
||||
rootView.findViewById<ComposeView>(R.id.composeView).setContent {
|
||||
MdcLauncherTheme {
|
||||
MissingPermissionBanner(
|
||||
text = missingPermission.label,
|
||||
onClick = {
|
||||
permissionsManager.requestPermission(
|
||||
context as AppCompatActivity,
|
||||
missingPermission.permissionGroup
|
||||
)
|
||||
},
|
||||
secondaryAction = {
|
||||
val secondaryAction = missingPermission.secondaryAction
|
||||
val secondaryActionLabel = missingPermission.secondaryActionLabel
|
||||
if (secondaryAction != null && secondaryActionLabel != null)
|
||||
TextButton(onClick = secondaryAction) {
|
||||
Text(text = secondaryActionLabel, style = MaterialTheme.typography.labelLarge)
|
||||
}
|
||||
}
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
return scene
|
||||
}
|
||||
}
|
||||
@ -1,9 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.search
|
||||
|
||||
import androidx.transition.Scene
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||
|
||||
interface Representation {
|
||||
fun getScene(rootView: SearchableView, searchable: Searchable, previousRepresentation: Int?) : Scene
|
||||
}
|
||||
@ -1,346 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.search
|
||||
|
||||
import android.animation.LayoutTransition
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import androidx.core.view.children
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.recyclerview.widget.DiffUtil
|
||||
import androidx.recyclerview.widget.ListUpdateCallback
|
||||
import de.mm20.launcher2.ktx.ceilToInt
|
||||
import de.mm20.launcher2.ktx.lifecycleOwner
|
||||
import de.mm20.launcher2.ktx.lifecycleScope
|
||||
import de.mm20.launcher2.ui.legacy.helper.ActivityStarter
|
||||
import de.mm20.launcher2.ui.legacy.helper.ActivityStarterCallback
|
||||
import de.mm20.launcher2.preferences.LauncherDataStore
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.actor
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.flow.distinctUntilChanged
|
||||
import kotlinx.coroutines.flow.first
|
||||
import kotlinx.coroutines.flow.map
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.util.*
|
||||
import kotlin.math.min
|
||||
|
||||
class SearchGridView : ViewGroup, KoinComponent {
|
||||
|
||||
val dataStore: LauncherDataStore by inject()
|
||||
|
||||
private val columnCountPreference = dataStore.data
|
||||
.map { it.grid.columnCount.takeIf { it > 0 } ?: 5 }
|
||||
.distinctUntilChanged()
|
||||
|
||||
var job: Job? = null
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
job?.cancel()
|
||||
lifecycleScope.launch {
|
||||
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
columnCountPreference.collectLatest {
|
||||
columnCount = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
job?.cancel()
|
||||
}
|
||||
|
||||
|
||||
var columnCount: Int = 1
|
||||
set(value) {
|
||||
if (value > 0) {
|
||||
field = value
|
||||
requestLayout()
|
||||
}
|
||||
else throw IllegalArgumentException("columnCount must be positive (is $value)")
|
||||
}
|
||||
|
||||
@ObsoleteCoroutinesApi
|
||||
private val updateActor = lifecycleScope
|
||||
.actor<List<Searchable>>(Dispatchers.Main, capacity = Channel.CONFLATED) {
|
||||
for (newItems in channel) {
|
||||
val oldItems = currentItems
|
||||
val diffResult = withContext(Dispatchers.Default) {
|
||||
SearchDiffUtil.calculateDiff(oldItems, newItems)
|
||||
}
|
||||
currentItems = newItems
|
||||
applyDiff(diffResult)
|
||||
}
|
||||
}
|
||||
|
||||
@ObsoleteCoroutinesApi
|
||||
fun submitItems(items: List<Searchable>?) {
|
||||
if (items == null) return
|
||||
if (items.getOrNull(expandedItem)?.key != currentItems.getOrNull(expandedItem)?.key) expandedItem =
|
||||
-1
|
||||
lifecycleScope.launch {
|
||||
updateActor.send(items)
|
||||
}
|
||||
}
|
||||
|
||||
private var expandedItem = -1
|
||||
set(value) {
|
||||
(getChildAt(field) as? SearchableView)?.back()
|
||||
requestLayout()
|
||||
field = value
|
||||
}
|
||||
|
||||
private var currentItems = listOf<Searchable>()
|
||||
|
||||
/**
|
||||
* The height of each row. An absolute pixel size or [ROW_HEIGHT_AUTO]
|
||||
*/
|
||||
var rowHeight: Int = ROW_HEIGHT_AUTO
|
||||
|
||||
constructor(context: Context) : this(context, null)
|
||||
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(
|
||||
context,
|
||||
attrs,
|
||||
defStyleRes
|
||||
) {
|
||||
attrs?.let {
|
||||
val ta =
|
||||
context.theme.obtainStyledAttributes(it, R.styleable.SearchGridView, 0, defStyleRes)
|
||||
rowHeight = ta.getDimensionPixelSize(R.styleable.SearchGridView_rowHeight, -1)
|
||||
ta.recycle()
|
||||
}
|
||||
layoutTransition = LayoutTransition().also {
|
||||
it.enableTransitionType(LayoutTransition.CHANGING)
|
||||
}
|
||||
clipChildren = false
|
||||
}
|
||||
|
||||
init {
|
||||
columnCount = runBlocking {
|
||||
columnCountPreference.first()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onMeasure(widthMeasureSpec: Int, heightMeasureSpec: Int) {
|
||||
super.onMeasure(widthMeasureSpec, heightMeasureSpec)
|
||||
val widthSpec = MeasureSpec.makeMeasureSpec(
|
||||
(MeasureSpec.getSize(widthMeasureSpec) - paddingLeft - paddingRight) / columnCount,
|
||||
MeasureSpec.EXACTLY
|
||||
)
|
||||
val heightSpec = MeasureSpec.makeMeasureSpec(0, MeasureSpec.UNSPECIFIED)
|
||||
|
||||
val colWidth = 0
|
||||
children.forEachIndexed { i, v ->
|
||||
if (i == expandedItem) {
|
||||
v.measure(widthMeasureSpec, heightSpec)
|
||||
} else {
|
||||
v.measure(widthSpec, heightSpec)
|
||||
}
|
||||
}
|
||||
val rowHeight = if (rowHeight != ROW_HEIGHT_AUTO) rowHeight else {
|
||||
children.maxByOrNull {
|
||||
if (indexOfChild(it) == expandedItem) return@maxByOrNull 0
|
||||
it.measuredHeight
|
||||
}?.measuredHeight ?: 0
|
||||
}
|
||||
|
||||
val width = when (MeasureSpec.getMode(widthMeasureSpec)) {
|
||||
MeasureSpec.EXACTLY -> MeasureSpec.getSize(widthMeasureSpec)
|
||||
MeasureSpec.AT_MOST -> min(
|
||||
colWidth * columnCount + paddingLeft + paddingRight,
|
||||
MeasureSpec.getSize(widthMeasureSpec)
|
||||
)
|
||||
MeasureSpec.UNSPECIFIED -> colWidth * columnCount + paddingLeft + paddingRight
|
||||
else -> colWidth * columnCount
|
||||
}
|
||||
|
||||
|
||||
val visibleChildCount = children.count { it.visibility != View.GONE }
|
||||
val rowCount = (visibleChildCount / columnCount.toFloat()).ceilToInt()
|
||||
var height = rowHeight * rowCount + (getChildAt(expandedItem)?.measuredHeight
|
||||
?: 0) + paddingTop + paddingBottom
|
||||
|
||||
if (expandedItem == childCount - 1 && (childCount % columnCount == 1) || expandedItem != -1 && columnCount == 1) {
|
||||
height -= rowHeight
|
||||
}
|
||||
|
||||
setMeasuredDimension(
|
||||
View.resolveSize(width, widthMeasureSpec),
|
||||
View.resolveSize(height, heightMeasureSpec)
|
||||
)
|
||||
}
|
||||
|
||||
override fun onLayout(changed: Boolean, l: Int, t: Int, r: Int, b: Int) {
|
||||
val rowHeight = if (rowHeight != ROW_HEIGHT_AUTO) rowHeight else {
|
||||
children.maxByOrNull {
|
||||
if (indexOfChild(it) == expandedItem) return@maxByOrNull 0
|
||||
it.measuredHeight
|
||||
}?.measuredHeight ?: 0
|
||||
}
|
||||
val width = measuredWidth
|
||||
val colWidth = (width - paddingLeft - paddingRight) / columnCount
|
||||
|
||||
|
||||
val visibleChildCount = children.count { it.visibility != View.GONE }
|
||||
val rowCount = (visibleChildCount / columnCount.toFloat()).ceilToInt()
|
||||
|
||||
var x: Int
|
||||
var y = paddingTop
|
||||
var i = 0
|
||||
for (row in 0 until rowCount) {
|
||||
x = paddingLeft
|
||||
if (row * columnCount <= expandedItem && expandedItem < (row + 1) * columnCount) {
|
||||
if (row == 0) y = 0
|
||||
val child = getChildAt(expandedItem) ?: continue
|
||||
child.layout(0, y, x + child.measuredWidth, y + child.measuredHeight)
|
||||
y += child.measuredHeight
|
||||
if (columnCount == 1) {
|
||||
i++
|
||||
continue
|
||||
}
|
||||
}
|
||||
|
||||
for (col in 0 until columnCount) {
|
||||
if (i == expandedItem) {
|
||||
x += colWidth
|
||||
i++
|
||||
continue
|
||||
}
|
||||
val child = getChildAt(i) ?: break
|
||||
child.layout(x, y, x + colWidth, y + rowHeight)
|
||||
x += colWidth
|
||||
i++
|
||||
}
|
||||
y += rowHeight
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a diff queue. Enqueues to postponedDiffs if an activity is starting (leaving this view
|
||||
* in an unstable state) or if postponedDiffs is not empty and [force] is not set.
|
||||
*/
|
||||
private fun applyDiff(diff: Queue<DiffAction>) {
|
||||
val representation =
|
||||
if (columnCount == 1) SearchableView.REPRESENTATION_LIST else SearchableView.REPRESENTATION_GRID
|
||||
while (diff.isNotEmpty()) {
|
||||
val action = diff.poll() ?: continue
|
||||
if (action.action == DiffAction.ACTION_INSERT) {
|
||||
val searchableView = SearchableView.getView(context, action.item, representation)
|
||||
searchableView.representation = representation
|
||||
searchableView.searchable = action.item
|
||||
searchableView.onRepresentationChange = { _, newRepr ->
|
||||
expandedItem = if (newRepr == SearchableView.REPRESENTATION_FULL) {
|
||||
(getChildAt(expandedItem) as? SearchableView)?.back()
|
||||
indexOfChild(searchableView)
|
||||
} else -1
|
||||
}
|
||||
val params = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
||||
searchableView.layoutParams = params
|
||||
addView(searchableView, action.position)
|
||||
}
|
||||
if (action.action == DiffAction.ACTION_DELETE) {
|
||||
removeViewAt(action.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
companion object {
|
||||
/**
|
||||
* Row height is automatically set to match the largest children
|
||||
*/
|
||||
const val ROW_HEIGHT_AUTO = -1
|
||||
}
|
||||
}
|
||||
|
||||
class QueueUpdateCallback : ListUpdateCallback {
|
||||
|
||||
val operations = mutableListOf<DiffAction>()
|
||||
|
||||
override fun onChanged(position: Int, count: Int, payload: Any?) {
|
||||
}
|
||||
|
||||
override fun onMoved(fromPosition: Int, toPosition: Int) {
|
||||
operations += DiffAction(action = DiffAction.ACTION_DELETE, position = fromPosition)
|
||||
operations += DiffAction(action = DiffAction.ACTION_INSERT, position = toPosition)
|
||||
}
|
||||
|
||||
override fun onInserted(position: Int, count: Int) {
|
||||
for (i in 0 until count) {
|
||||
operations += DiffAction(action = DiffAction.ACTION_INSERT, position = position + i)
|
||||
}
|
||||
}
|
||||
|
||||
override fun onRemoved(position: Int, count: Int) {
|
||||
for (i in 1..count) {
|
||||
operations += DiffAction(
|
||||
action = DiffAction.ACTION_DELETE,
|
||||
position = position + (count - i)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
object SearchDiffUtil {
|
||||
fun calculateDiff(oldItems: List<Searchable>, newItems: List<Searchable>): Queue<DiffAction> {
|
||||
|
||||
if (oldItems.isEmpty() && newItems.isEmpty()) return ArrayDeque()
|
||||
|
||||
val callback = object : DiffUtil.Callback() {
|
||||
override fun areItemsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||
return oldItems[oldItemPosition].key == newItems[newItemPosition].key
|
||||
}
|
||||
|
||||
override fun getOldListSize(): Int {
|
||||
return oldItems.size
|
||||
}
|
||||
|
||||
override fun getNewListSize(): Int {
|
||||
return newItems.size
|
||||
}
|
||||
|
||||
override fun areContentsTheSame(oldItemPosition: Int, newItemPosition: Int): Boolean {
|
||||
return areItemsTheSame(oldItemPosition, newItemPosition)
|
||||
}
|
||||
}
|
||||
|
||||
val diffResult = DiffUtil.calculateDiff(callback, false)
|
||||
|
||||
val updateCallback = QueueUpdateCallback()
|
||||
|
||||
diffResult.dispatchUpdatesTo(updateCallback)
|
||||
|
||||
val result = ArrayDeque<DiffAction>()
|
||||
|
||||
val mutableNewItems = mutableListOf<Searchable?>()
|
||||
mutableNewItems.addAll(newItems)
|
||||
|
||||
for (i in updateCallback.operations.asReversed()) {
|
||||
if (i.action == DiffAction.ACTION_INSERT) {
|
||||
i.item = mutableNewItems[i.position]
|
||||
mutableNewItems.removeAt(i.position)
|
||||
} else {
|
||||
mutableNewItems.add(i.position, null)
|
||||
}
|
||||
}
|
||||
|
||||
result.addAll(updateCallback.operations)
|
||||
|
||||
return result
|
||||
}
|
||||
}
|
||||
|
||||
data class DiffAction(val action: Int, val position: Int, var item: Searchable? = null) {
|
||||
companion object {
|
||||
const val ACTION_INSERT = 1
|
||||
const val ACTION_DELETE = -1
|
||||
}
|
||||
}
|
||||
@ -1,88 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.search
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.widget.LinearLayout
|
||||
import de.mm20.launcher2.ktx.lifecycleScope
|
||||
import de.mm20.launcher2.ui.legacy.helper.ActivityStarter
|
||||
import de.mm20.launcher2.ui.legacy.helper.ActivityStarterCallback
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.transition.ChangingLayoutTransition
|
||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.ObsoleteCoroutinesApi
|
||||
import kotlinx.coroutines.channels.Channel
|
||||
import kotlinx.coroutines.channels.actor
|
||||
import kotlinx.coroutines.launch
|
||||
import kotlinx.coroutines.withContext
|
||||
import java.util.*
|
||||
|
||||
class SearchListView : LinearLayout {
|
||||
|
||||
@ObsoleteCoroutinesApi
|
||||
private val updateActor = lifecycleScope
|
||||
.actor<List<Searchable>>(Dispatchers.Main, capacity = Channel.CONFLATED) {
|
||||
for (newItems in channel) {
|
||||
val oldItems = currentItems
|
||||
val diffResult = withContext(Dispatchers.Default) {
|
||||
SearchDiffUtil.calculateDiff(oldItems, newItems)
|
||||
}
|
||||
currentItems = newItems
|
||||
applyDiff(diffResult)
|
||||
}
|
||||
}
|
||||
|
||||
@ObsoleteCoroutinesApi
|
||||
fun submitItems(items: List<Searchable>?) {
|
||||
if (items == null) return
|
||||
if (items.getOrNull(expandedItem)?.key != currentItems.getOrNull(expandedItem)?.key) expandedItem = -1
|
||||
lifecycleScope.launch {
|
||||
updateActor.send(items)
|
||||
}
|
||||
}
|
||||
|
||||
private var expandedItem = -1
|
||||
set(value) {
|
||||
(getChildAt(field) as? SearchableView)?.back()
|
||||
requestLayout()
|
||||
field = value
|
||||
}
|
||||
|
||||
private var currentItems = listOf<Searchable>()
|
||||
|
||||
constructor(context: Context) : this(context, null)
|
||||
constructor(context: Context, attrs: AttributeSet?) : this(context, attrs, 0)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(context, attrs, defStyleRes) {
|
||||
layoutTransition = ChangingLayoutTransition()
|
||||
clipChildren = false
|
||||
orientation = VERTICAL
|
||||
}
|
||||
|
||||
/**
|
||||
* Applies a diff queue. Enqueues to postponedDiffs if an activity is starting (leaving this view
|
||||
* in an unstable state) or if postponedDiffs is not empty and [force] is not set.
|
||||
*/
|
||||
private fun applyDiff(diff: Queue<DiffAction>) {
|
||||
val representation = SearchableView.REPRESENTATION_LIST
|
||||
while (diff.isNotEmpty()) {
|
||||
val action = diff.poll() ?: continue
|
||||
if (action.action == DiffAction.ACTION_INSERT) {
|
||||
val searchableView = SearchableView.getView(context, action.item, representation)
|
||||
searchableView.representation = representation
|
||||
searchableView.searchable = action.item
|
||||
searchableView.onRepresentationChange = { _, newRepr ->
|
||||
expandedItem = if (newRepr == SearchableView.REPRESENTATION_FULL) {
|
||||
(getChildAt(expandedItem) as? SearchableView)?.back()
|
||||
indexOfChild(searchableView)
|
||||
} else -1
|
||||
}
|
||||
val params = LayoutParams(LayoutParams.MATCH_PARENT, LayoutParams.WRAP_CONTENT)
|
||||
searchableView.layoutParams = params
|
||||
addView(searchableView, action.position)
|
||||
}
|
||||
if (action.action == DiffAction.ACTION_DELETE) {
|
||||
removeViewAt(action.position)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,104 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.search
|
||||
|
||||
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.BadgeRepository
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
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
|
||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||
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
|
||||
|
||||
class AppShortcutDetailRepresentation : Representation, KoinComponent {
|
||||
|
||||
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 appShortcut = searchable as AppShortcut
|
||||
val context = rootView.context as AppCompatActivity
|
||||
val scene = Scene.getSceneForLayout(rootView, R.layout.view_application_detail, context)
|
||||
scene.setEnterAction {
|
||||
with(rootView) {
|
||||
setOnClickListener(null)
|
||||
setOnLongClickListener(null)
|
||||
findViewById<TextView>(R.id.appName).text = appShortcut.label
|
||||
findViewById<LauncherIconView>(R.id.icon).apply {
|
||||
icon = iconRepository.getIconIfCached(appShortcut)
|
||||
shape = LauncherIconView.currentShape
|
||||
job = rootView.scope.launch {
|
||||
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
launch {
|
||||
iconRepository.getIcon(searchable, (84 * rootView.dp).toInt())
|
||||
.collectLatest {
|
||||
icon = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
badgeRepository.getBadge(searchable.badgeKey).collectLatest {
|
||||
badge = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
LauncherIconView.getDefaultShape().collectLatest {
|
||||
shape = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
val appName = appShortcut.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
|
||||
) {
|
||||
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))
|
||||
backAction.clickAction = {
|
||||
searchableView.back()
|
||||
}
|
||||
toolbar.addAction(backAction, ToolbarView.PLACEMENT_START)
|
||||
|
||||
}
|
||||
}
|
||||
@ -1,145 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.search
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.view.View
|
||||
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 coil.load
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.ktx.lifecycleOwner
|
||||
import de.mm20.launcher2.ui.legacy.helper.ActivityStarter
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.search.data.Website
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||
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
|
||||
|
||||
class WebsiteDetailRepresentation : Representation, KoinComponent {
|
||||
|
||||
private val iconRepository: IconRepository by inject()
|
||||
private var job: Job? = null
|
||||
|
||||
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)
|
||||
scene.setEnterAction {
|
||||
with(rootView) {
|
||||
if (!hasBack()) {
|
||||
scene.sceneRoot.elevation = 0f
|
||||
scene.sceneRoot.setBackgroundColor(0)
|
||||
scene.sceneRoot.setOnClickListener {
|
||||
ActivityStarter.start(context, rootView, website)
|
||||
}
|
||||
}
|
||||
val label = findViewById<TextView>(R.id.websiteTitle)
|
||||
label.text = website.label
|
||||
findViewById<TextView>(R.id.websiteDescription).text = website.description
|
||||
val websiteImage = findViewById<ImageView>(R.id.websiteImage)
|
||||
val websiteFavIcon = findViewById<LauncherIconView>(R.id.websiteFavIcon)
|
||||
when {
|
||||
website.image.isNotBlank() -> {
|
||||
websiteImage.visibility = View.VISIBLE
|
||||
websiteFavIcon.visibility = FrameLayout.GONE
|
||||
websiteImage.load(website.image)
|
||||
websiteImage.transitionName = "icon"
|
||||
label.transitionName = "label"
|
||||
websiteFavIcon.transitionName = null
|
||||
}
|
||||
website.favicon.isNotBlank() -> {
|
||||
websiteFavIcon.visibility = View.VISIBLE
|
||||
websiteImage.visibility = FrameLayout.GONE
|
||||
websiteImage.transitionName = null
|
||||
label.transitionName = null
|
||||
websiteFavIcon.transitionName = "icon"
|
||||
websiteFavIcon.apply {
|
||||
icon = iconRepository.getIconIfCached(website)
|
||||
shape = LauncherIconView.currentShape
|
||||
job = rootView.scope.launch {
|
||||
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
launch {
|
||||
iconRepository.getIcon(website, (84 * rootView.dp).toInt())
|
||||
.collectLatest {
|
||||
icon = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
LauncherIconView.getDefaultShape().collectLatest {
|
||||
shape = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
websiteFavIcon.visibility = View.GONE
|
||||
websiteImage.visibility = FrameLayout.GONE
|
||||
websiteImage.transitionName = null
|
||||
websiteFavIcon.transitionName = null
|
||||
label.transitionName = null
|
||||
}
|
||||
}
|
||||
val toolbar = findViewById<ToolbarView>(R.id.websiteToolbar)
|
||||
setupMenu(rootView, toolbar, website)
|
||||
}
|
||||
}
|
||||
|
||||
scene.setExitAction {
|
||||
job?.cancel()
|
||||
}
|
||||
return scene
|
||||
}
|
||||
|
||||
private fun setupMenu(rootView: SearchableView, toolbar: ToolbarView, searchable: Website) {
|
||||
val context = rootView.context
|
||||
toolbar.clear()
|
||||
|
||||
if (rootView.hasBack()) {
|
||||
val backAction =
|
||||
ToolbarAction(R.drawable.ic_arrow_back, context.getString(R.string.menu_back))
|
||||
backAction.clickAction = {
|
||||
rootView.back()
|
||||
}
|
||||
toolbar.addAction(backAction, ToolbarView.PLACEMENT_START)
|
||||
}
|
||||
val favAction = FavoriteToolbarAction(context, searchable)
|
||||
toolbar.addAction(favAction, ToolbarView.PLACEMENT_END)
|
||||
|
||||
val shareAction = ToolbarAction(R.drawable.ic_share, context.getString(R.string.menu_share))
|
||||
shareAction.clickAction = {
|
||||
share(context, searchable)
|
||||
}
|
||||
toolbar.addAction(shareAction, ToolbarView.PLACEMENT_END)
|
||||
}
|
||||
|
||||
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.type = "text/plain"
|
||||
context.startActivity(Intent.createChooser(shareIntent, null))
|
||||
}
|
||||
}
|
||||
@ -1,142 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.search
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.view.View
|
||||
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 coil.load
|
||||
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.ui.legacy.helper.ActivityStarter
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.search.data.Website
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||
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
|
||||
|
||||
class WebsiteListRepresentation : Representation, KoinComponent {
|
||||
|
||||
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 website = searchable as Website
|
||||
val context = rootView.context as AppCompatActivity
|
||||
val scene = Scene.getSceneForLayout(rootView, R.layout.view_website_list, rootView.context)
|
||||
scene.setEnterAction {
|
||||
with(rootView) {
|
||||
if (!hasBack()) {
|
||||
scene.sceneRoot.elevation = 0f
|
||||
scene.sceneRoot.setBackgroundColor(0)
|
||||
scene.sceneRoot.setOnClickListener {
|
||||
ActivityStarter.start(context, rootView, website)
|
||||
}
|
||||
}
|
||||
val label = findViewById<TextView>(R.id.websiteTitle)
|
||||
label.text = website.label
|
||||
findViewById<TextView>(R.id.websiteDescription).text = website.description
|
||||
val websiteImage = findViewById<ImageView>(R.id.websiteImage)
|
||||
val websiteFavIcon = findViewById<LauncherIconView>(R.id.websiteFavIcon)
|
||||
when {
|
||||
website.image.isNotBlank() -> {
|
||||
websiteImage.visibility = View.VISIBLE
|
||||
websiteFavIcon.visibility = FrameLayout.GONE
|
||||
websiteImage.load(website.image)
|
||||
websiteImage.load(website.image)
|
||||
websiteImage.transitionName = "icon"
|
||||
label.transitionName = "label"
|
||||
websiteFavIcon.transitionName = null
|
||||
}
|
||||
website.favicon.isNotBlank() -> {
|
||||
websiteFavIcon.visibility = View.VISIBLE
|
||||
websiteImage.visibility = FrameLayout.GONE
|
||||
websiteImage.transitionName = null
|
||||
label.transitionName = null
|
||||
websiteFavIcon.transitionName = "icon"
|
||||
websiteFavIcon.apply {
|
||||
icon = iconRepository.getIconIfCached(website)
|
||||
shape = LauncherIconView.currentShape
|
||||
job = rootView.scope.launch {
|
||||
rootView.lifecycleOwner.repeatOnLifecycle(Lifecycle.State.CREATED) {
|
||||
launch {
|
||||
iconRepository.getIcon(searchable, (84 * rootView.dp).toInt())
|
||||
.collectLatest {
|
||||
icon = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
badgeRepository.getBadge(searchable.badgeKey).collectLatest {
|
||||
badge = it
|
||||
}
|
||||
}
|
||||
launch {
|
||||
LauncherIconView.getDefaultShape().collectLatest {
|
||||
shape = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else -> {
|
||||
websiteFavIcon.visibility = View.GONE
|
||||
websiteImage.visibility = FrameLayout.GONE
|
||||
websiteImage.transitionName = null
|
||||
websiteFavIcon.transitionName = null
|
||||
label.transitionName = null
|
||||
}
|
||||
}
|
||||
val toolbar = findViewById<ToolbarView>(R.id.websiteToolbar)
|
||||
setupMenu(rootView, toolbar, website)
|
||||
}
|
||||
}
|
||||
return scene
|
||||
}
|
||||
|
||||
private fun setupMenu(rootView: SearchableView, toolbar: ToolbarView, searchable: Website) {
|
||||
val context = rootView.context
|
||||
toolbar.clear()
|
||||
|
||||
val favAction = FavoriteToolbarAction(context, searchable)
|
||||
toolbar.addAction(favAction, ToolbarView.PLACEMENT_END)
|
||||
|
||||
val shareAction = ToolbarAction(R.drawable.ic_share, context.getString(R.string.menu_share))
|
||||
shareAction.clickAction = {
|
||||
share(context, searchable)
|
||||
}
|
||||
toolbar.addAction(shareAction, ToolbarView.PLACEMENT_END)
|
||||
}
|
||||
|
||||
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.type = "text/plain"
|
||||
context.startActivity(Intent.createChooser(shareIntent, null))
|
||||
}
|
||||
}
|
||||
@ -1,76 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.search
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.transition.Scene
|
||||
import coil.load
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.search.data.Wikipedia
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||
import de.mm20.launcher2.ui.legacy.view.FavoriteToolbarAction
|
||||
import de.mm20.launcher2.ui.legacy.view.ToolbarAction
|
||||
import de.mm20.launcher2.ui.legacy.view.ToolbarView
|
||||
|
||||
class WikipediaDetailRepresentation : Representation {
|
||||
override fun getScene(rootView: SearchableView, searchable: Searchable, previousRepresentation: Int?): Scene {
|
||||
val wikipedia = searchable as Wikipedia
|
||||
val scene = Scene.getSceneForLayout(rootView, R.layout.view_wikipedia_detail, rootView.context)
|
||||
scene.setEnterAction {
|
||||
with(rootView) {
|
||||
findViewById<TextView>(R.id.wikipediaTitle).text = wikipedia.label
|
||||
findViewById<TextView>(R.id.wikipediaText).text = HtmlCompat.fromHtml(wikipedia.text, HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||
findViewById<ImageView>(R.id.wikipediaImage).also {
|
||||
if (wikipedia.image.isNullOrBlank()) {
|
||||
it.visibility = View.GONE
|
||||
it.setImageDrawable(null)
|
||||
} else {
|
||||
if (wikipedia.image?.endsWith(".png") == true) {
|
||||
it.scaleType = ImageView.ScaleType.CENTER_INSIDE
|
||||
} else {
|
||||
it.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||
}
|
||||
it.visibility = View.VISIBLE
|
||||
it.load(wikipedia.image)
|
||||
}
|
||||
}
|
||||
val toolbar = findViewById<ToolbarView>(R.id.wikipediaToolbar)
|
||||
setupMenu(rootView, toolbar, wikipedia)
|
||||
}
|
||||
}
|
||||
return scene
|
||||
}
|
||||
|
||||
private fun setupMenu(rootView: SearchableView, toolbar: ToolbarView, wikipedia: Wikipedia) {
|
||||
val context = rootView.context
|
||||
if (rootView.hasBack()) {
|
||||
val backAction = ToolbarAction(R.drawable.ic_arrow_back, context.getString(R.string.menu_back))
|
||||
backAction.clickAction = {
|
||||
rootView.back()
|
||||
}
|
||||
toolbar.addAction(backAction, ToolbarView.PLACEMENT_START)
|
||||
}
|
||||
val favAction = FavoriteToolbarAction(context, wikipedia)
|
||||
toolbar.addAction(favAction, ToolbarView.PLACEMENT_END)
|
||||
|
||||
val shareAction = ToolbarAction(R.drawable.ic_share, context.getString(R.string.menu_share))
|
||||
shareAction.clickAction = {
|
||||
share(context, wikipedia)
|
||||
}
|
||||
toolbar.addAction(shareAction, ToolbarView.PLACEMENT_END)
|
||||
}
|
||||
|
||||
private fun share(context: Context, wikipedia: Wikipedia) {
|
||||
val text = HtmlCompat.fromHtml(wikipedia.text, HtmlCompat.FROM_HTML_MODE_LEGACY).toString()
|
||||
val shareIntent = Intent(Intent.ACTION_SEND)
|
||||
shareIntent.putExtra(Intent.EXTRA_TEXT, "${wikipedia.label}\n\n" +
|
||||
"${text.substring(0, 200)}…\n\n" +
|
||||
"${context.getString(R.string.wikipedia_url)}/wiki?curid=${wikipedia.id}")
|
||||
shareIntent.type = "text/plain"
|
||||
context.startActivity(Intent.createChooser(shareIntent, null))
|
||||
}
|
||||
}
|
||||
@ -1,80 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.search
|
||||
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.TextView
|
||||
import androidx.core.text.HtmlCompat
|
||||
import androidx.transition.Scene
|
||||
import coil.load
|
||||
import coil.size.Scale
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.search.data.Wikipedia
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||
import de.mm20.launcher2.ui.legacy.view.FavoriteToolbarAction
|
||||
import de.mm20.launcher2.ui.legacy.view.ToolbarAction
|
||||
import de.mm20.launcher2.ui.legacy.view.ToolbarView
|
||||
|
||||
class WikipediaListRepresentation : Representation {
|
||||
override fun getScene(
|
||||
rootView: SearchableView,
|
||||
searchable: Searchable,
|
||||
previousRepresentation: Int?
|
||||
): Scene {
|
||||
val wikipedia = searchable as Wikipedia
|
||||
val scene =
|
||||
Scene.getSceneForLayout(rootView, R.layout.view_wikipedia_list, rootView.context)
|
||||
scene.setEnterAction {
|
||||
with(rootView) {
|
||||
findViewById<TextView>(R.id.wikipediaTitle).text = wikipedia.label
|
||||
findViewById<TextView>(R.id.wikipediaText).text =
|
||||
HtmlCompat.fromHtml(wikipedia.text, HtmlCompat.FROM_HTML_MODE_LEGACY)
|
||||
findViewById<ImageView>(R.id.wikipediaImage).also {
|
||||
if (wikipedia.image.isNullOrBlank()) {
|
||||
it.visibility = View.GONE
|
||||
it.setImageDrawable(null)
|
||||
} else {
|
||||
if (wikipedia.image?.endsWith(".png") == true) {
|
||||
it.scaleType = ImageView.ScaleType.CENTER_INSIDE
|
||||
} else {
|
||||
it.scaleType = ImageView.ScaleType.CENTER_CROP
|
||||
}
|
||||
it.visibility = View.VISIBLE
|
||||
it.load(wikipedia.image)
|
||||
}
|
||||
}
|
||||
val toolbar = findViewById<ToolbarView>(R.id.wikipediaToolbar)
|
||||
setupMenu(rootView, toolbar, wikipedia)
|
||||
}
|
||||
}
|
||||
return scene
|
||||
}
|
||||
|
||||
private fun setupMenu(rootView: SearchableView, toolbar: ToolbarView, wikipedia: Wikipedia) {
|
||||
val context = rootView.context
|
||||
toolbar.clear()
|
||||
|
||||
val favAction = FavoriteToolbarAction(context, wikipedia)
|
||||
toolbar.addAction(favAction, ToolbarView.PLACEMENT_END)
|
||||
|
||||
val shareAction = ToolbarAction(R.drawable.ic_share, context.getString(R.string.menu_share))
|
||||
shareAction.clickAction = {
|
||||
share(context, wikipedia)
|
||||
}
|
||||
toolbar.addAction(shareAction, ToolbarView.PLACEMENT_END)
|
||||
}
|
||||
|
||||
private fun share(context: Context, wikipedia: Wikipedia) {
|
||||
val text = wikipedia.text.toString()
|
||||
val shareIntent = Intent(Intent.ACTION_SEND)
|
||||
shareIntent.putExtra(
|
||||
Intent.EXTRA_TEXT, "${wikipedia.label}\n\n" +
|
||||
"${text.substring(0, 200)}…\n\n" +
|
||||
"${context.getString(R.string.wikipedia_url)}/wiki?curid=${wikipedia.id}"
|
||||
)
|
||||
shareIntent.type = "text/plain"
|
||||
context.startActivity(Intent.createChooser(shareIntent, null))
|
||||
}
|
||||
}
|
||||
@ -1,158 +0,0 @@
|
||||
package de.mm20.launcher2.ui.legacy.searchable
|
||||
|
||||
import android.annotation.SuppressLint
|
||||
import android.content.Context
|
||||
import android.os.Build
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import android.widget.FrameLayout
|
||||
import androidx.transition.*
|
||||
import de.mm20.launcher2.search.data.*
|
||||
import de.mm20.launcher2.transition.TextResize
|
||||
import de.mm20.launcher2.ui.legacy.data.InformationText
|
||||
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) {
|
||||
|
||||
var searchable: Searchable? = null
|
||||
set(value) {
|
||||
field = value
|
||||
updateRepresentation(null)
|
||||
}
|
||||
|
||||
|
||||
private var defaultRepresentation = representation
|
||||
|
||||
val scope = CoroutineScope(Job() + Dispatchers.Main)
|
||||
|
||||
var representation = representation
|
||||
set(value) {
|
||||
val oldVal = field
|
||||
field = value
|
||||
if (oldVal != value) {
|
||||
onRepresentationChange(oldVal, value)
|
||||
}
|
||||
updateRepresentation(oldVal)
|
||||
}
|
||||
var onRepresentationChange: (Int, Int) -> Unit = { _, _ -> }
|
||||
|
||||
init {
|
||||
clipChildren = false
|
||||
updateRepresentation(null)
|
||||
}
|
||||
|
||||
internal open fun updateRepresentation(previousRepresentation: Int?) {
|
||||
when (representation) {
|
||||
REPRESENTATION_FULL -> setFullRepresentation(previousRepresentation)
|
||||
REPRESENTATION_LIST -> setListRepresentation(previousRepresentation)
|
||||
REPRESENTATION_GRID -> setGridRepresentation(previousRepresentation)
|
||||
else -> throw IllegalArgumentException("Must be REPRESENTATION_GRID, REPRESENTATION_LIST or REPRESENTATION_FULL")
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
private fun setGridRepresentation(previousRepresentation: Int?) {
|
||||
val searchable = searchable
|
||||
if (searchable == null) {
|
||||
removeAllViews()
|
||||
return
|
||||
}
|
||||
val scene = BasicGridRepresentation().getScene(this, searchable, null)
|
||||
applyScene(scene)
|
||||
}
|
||||
|
||||
private fun setListRepresentation(previousRepresentation: Int?) {
|
||||
val searchable = searchable
|
||||
if (searchable == null) {
|
||||
removeAllViews()
|
||||
return
|
||||
}
|
||||
val representation = when (searchable) {
|
||||
is File -> FileListRepresentation()
|
||||
is Contact -> ContactListRepresentation()
|
||||
is CalendarEvent -> CalendarListRepresentation()
|
||||
is Website -> WebsiteListRepresentation()
|
||||
is Wikipedia -> WikipediaListRepresentation()
|
||||
is InformationText -> InformationListRepresentation()
|
||||
is MissingPermission -> PermissionListRepresentation()
|
||||
else -> return
|
||||
}
|
||||
applyScene(representation.getScene(this, searchable, previousRepresentation))
|
||||
}
|
||||
|
||||
|
||||
private fun setFullRepresentation(previousRepresentation: Int?) {
|
||||
val searchable = searchable
|
||||
if (searchable == null) {
|
||||
removeAllViews()
|
||||
return
|
||||
}
|
||||
val representation = when (searchable) {
|
||||
is Application -> ApplicationDetailRepresentation()
|
||||
is Website -> WebsiteDetailRepresentation()
|
||||
is File -> FileDetailRepresentation()
|
||||
is Contact -> ContactDetailRepresentation()
|
||||
is CalendarEvent -> CalendarDetailRepresentation()
|
||||
is Wikipedia -> WikipediaDetailRepresentation()
|
||||
is AppShortcut -> AppShortcutDetailRepresentation()
|
||||
else -> return
|
||||
}
|
||||
applyScene(representation.getScene(this, searchable, previousRepresentation))
|
||||
}
|
||||
|
||||
private fun applyScene(scene: Scene) {
|
||||
val transition = TransitionSet().apply {
|
||||
addTransition(
|
||||
ChangeBounds().setInterpolator(DecelerateInterpolator()).excludeTarget(
|
||||
AspectRationImageView::class.java, true
|
||||
)
|
||||
)
|
||||
addTransition(LauncherIconViewTransition())
|
||||
addTransition(TextResize())
|
||||
addTransition(LauncherCards())
|
||||
ordering = TransitionSet.ORDERING_TOGETHER
|
||||
setMatchOrder(Transition.MATCH_NAME, Transition.MATCH_ID)
|
||||
}
|
||||
TransitionManager.go(scene, transition)
|
||||
}
|
||||
|
||||
var onBack: (() -> Unit)? = null
|
||||
|
||||
fun back() {
|
||||
if (!hasBack()) {
|
||||
onBack?.invoke()
|
||||
return
|
||||
}
|
||||
representation = defaultRepresentation
|
||||
}
|
||||
|
||||
fun hasBack(): Boolean {
|
||||
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 {
|
||||
return SearchableView(context, representation)
|
||||
}
|
||||
}
|
||||
}
|
||||
@ -1,103 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<de.mm20.launcher2.ui.legacy.view.SwipeCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/ElevatedInnerCardViewStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/appCard"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_margin="8dp"
|
||||
android:transitionName="root"
|
||||
app:backgroundOpacity="255">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="true"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false">
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.view.LauncherIconView
|
||||
android:id="@id/icon"
|
||||
android:layout_width="100dp"
|
||||
android:layout_height="100dp"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:elevation="2dp"
|
||||
android:padding="8dp"
|
||||
android:transitionName="icon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?textAppearanceTitleMedium"
|
||||
app:layout_constraintEnd_toStartOf="@+id/icon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
tools:text="App name" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/appInfo"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginTop="4dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="2"
|
||||
android:textAppearance="?textAppearanceBodySmall"
|
||||
app:layout_constraintEnd_toStartOf="@+id/icon"
|
||||
app:layout_constraintStart_toStartOf="@+id/appName"
|
||||
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"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
app:layout_constraintEnd_toStartOf="@+id/icon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/notifications"/>
|
||||
|
||||
<androidx.constraintlayout.widget.Barrier
|
||||
android:id="@+id/barrier2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
app:barrierDirection="bottom"
|
||||
app:constraint_referenced_ids="appShortcuts,icon"
|
||||
tools:layout_editor_absoluteX="16dp"
|
||||
tools:layout_editor_absoluteY="330dp" />
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.view.ToolbarView
|
||||
android:id="@+id/appToolbar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="56dp"
|
||||
android:padding="4dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/barrier2" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</de.mm20.launcher2.ui.legacy.view.SwipeCardView>
|
||||
@ -1,50 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<de.mm20.launcher2.ui.legacy.view.LauncherCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/root"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:gravity="center"
|
||||
android:transitionName="root"
|
||||
app:cardElevation="0dp"
|
||||
app:backgroundOpacity="0">
|
||||
|
||||
<FrameLayout
|
||||
android:id="@+id/card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:gravity="center"
|
||||
android:transitionName="cardLayout">
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.view.LauncherIconView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:elevation="1dp"
|
||||
android:padding="8dp"
|
||||
android:transitionName="icon" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/label"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_gravity="center_horizontal"
|
||||
android:layout_marginStart="2dp"
|
||||
android:layout_marginTop="64dp"
|
||||
android:layout_marginEnd="2dp"
|
||||
android:layout_marginBottom="4dp"
|
||||
android:ellipsize="end"
|
||||
android:gravity="center"
|
||||
android:textAppearance="?textAppearanceBodySmall"
|
||||
android:maxLines="1"
|
||||
android:transitionName="label"
|
||||
tools:text="Application" />
|
||||
</FrameLayout>
|
||||
</de.mm20.launcher2.ui.legacy.view.LauncherCardView>
|
||||
@ -1,65 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<de.mm20.launcher2.ui.legacy.view.SwipeCardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/calendarEventCard"
|
||||
style="@style/ElevatedInnerCardViewStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:transitionName="root">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/constraintLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/calendarShortcuts"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/calendarColor"
|
||||
app:layout_constraintTop_toBottomOf="@+id/calendarLabel" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/calendarLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/calendarColor"
|
||||
app:layout_constraintTop_toTopOf="parent"
|
||||
android:textAppearance="?textAppearanceTitleMedium"
|
||||
tools:text="Event title" />
|
||||
|
||||
<View
|
||||
android:id="@+id/calendarColor"
|
||||
android:layout_width="8dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="#ff5500"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.view.ToolbarView
|
||||
android:id="@+id/calendarToolbar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="56dp"
|
||||
android:padding="4dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toEndOf="@+id/calendarColor"
|
||||
app:layout_constraintTop_toBottomOf="@+id/calendarShortcuts" />
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</de.mm20.launcher2.ui.legacy.view.SwipeCardView>
|
||||
@ -1,65 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<de.mm20.launcher2.ui.legacy.view.SwipeCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/calendarEventCard"
|
||||
style="@style/InnerCardViewStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:transitionName="root">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/constraintLayout"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/calendarLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="14dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_marginBottom="1dp"
|
||||
android:textAppearance="?textAppearanceTitleSmall"
|
||||
app:layout_constraintBottom_toTopOf="@+id/guideline4"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/calendarColor"
|
||||
tools:text="Event title" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/eventDateTime"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:paddingBottom="14dp"
|
||||
android:textAppearance="?textAppearanceBodySmall"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/calendarColor"
|
||||
app:layout_constraintTop_toTopOf="@+id/guideline4"
|
||||
tools:text="March 16, 17:00–19:00" />
|
||||
|
||||
<View
|
||||
android:id="@+id/calendarColor"
|
||||
android:layout_width="8dp"
|
||||
android:layout_height="0dp"
|
||||
android:background="#ff5500"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline4"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_percent="0.5" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
|
||||
</de.mm20.launcher2.ui.legacy.view.SwipeCardView>
|
||||
@ -1,146 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:id="@+id/calendarWidgetRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="true"
|
||||
android:clipChildren="false">
|
||||
|
||||
|
||||
<FrameLayout
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="0dp"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
app:layout_constraintBottom_toBottomOf="@+id/calendarDatePrev"
|
||||
app:layout_constraintEnd_toStartOf="@+id/calendarDateNext"
|
||||
app:layout_constraintStart_toEndOf="@+id/calendarDatePrev"
|
||||
app:layout_constraintTop_toTopOf="@+id/calendarDatePrev">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/calendarDate"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_gravity="center"
|
||||
|
||||
android:clickable="true"
|
||||
app:drawableEndCompat="@drawable/ic_arrow_drop_down"
|
||||
app:drawableTint="@color/text_color_primary"
|
||||
android:ellipsize="end"
|
||||
android:focusable="true"
|
||||
android:foreground="?selectableItemBackgroundBorderless"
|
||||
android:gravity="center"
|
||||
android:maxLines="1"
|
||||
android:text="@string/date_today"
|
||||
android:textAlignment="center"
|
||||
android:textAppearance="?textAppearanceTitleMedium" />
|
||||
</FrameLayout>
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/calendarDateNext"
|
||||
style="?iconStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?selectableItemBackgroundBorderless"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_arrow_right"
|
||||
app:layout_constraintEnd_toStartOf="@+id/calendarOpenApp"
|
||||
app:layout_constraintTop_toTopOf="@+id/calendarDatePrev" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/calendarOpenApp"
|
||||
style="?iconStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/calendar_widget_open_app"
|
||||
android:focusable="true"
|
||||
android:foreground="?selectableItemBackgroundBorderless"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_open_external"
|
||||
app:tooltipText="@string/calendar_widget_open_app"
|
||||
android:tooltipText="@string/calendar_widget_open_app"
|
||||
app:layout_constraintEnd_toStartOf="@+id/calendarNewEvent"
|
||||
app:layout_constraintTop_toTopOf="@+id/calendarDatePrev" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/calendarNewEvent"
|
||||
style="?iconStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:clickable="true"
|
||||
android:contentDescription="@string/calendar_widget_new_event"
|
||||
android:focusable="true"
|
||||
android:foreground="?selectableItemBackgroundBorderless"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_add"
|
||||
android:tooltipText="@string/calendar_widget_new_event"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/calendarDatePrev" />
|
||||
|
||||
<ImageView
|
||||
android:id="@+id/calendarDatePrev"
|
||||
style="?iconStyle"
|
||||
android:layout_width="48dp"
|
||||
android:layout_height="48dp"
|
||||
android:layout_alignParentTop="true"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:clickable="true"
|
||||
android:focusable="true"
|
||||
android:foreground="?selectableItemBackgroundBorderless"
|
||||
android:padding="12dp"
|
||||
android:src="@drawable/ic_arrow_left"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/calendarUpcomingEventsTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:text="@string/calendar_widget_pinned_events"
|
||||
android:textAppearance="?textAppearanceTitleMedium"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/calendarWidgetList" />
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.search.SearchListView
|
||||
android:id="@+id/calendarWidgetList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:padding="8dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/calendarDatePrev" />
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.search.SearchListView
|
||||
android:id="@+id/calendarWidgetPinnedList"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:padding="8dp"
|
||||
android:visibility="gone"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/calendarUpcomingEventsTitle" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@ -1,75 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<de.mm20.launcher2.ui.legacy.view.SwipeCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/ElevatedInnerCardViewStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:id="@+id/contactCard"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:transitionName="root"
|
||||
app:backgroundOpacity="255">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:animateLayoutChanges="true"
|
||||
android:clipChildren="false"
|
||||
android:transitionName="cardLayout">
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/contactShortcuts"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintHorizontal_bias="0.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/icon" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/contactName"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:maxLines="1"
|
||||
android:textColor="@color/text_color_primary"
|
||||
android:textAppearance="?textAppearanceTitleMedium"
|
||||
android:transitionName="contactLabel"
|
||||
app:layout_constraintBottom_toTopOf="@+id/guideline3"
|
||||
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||
app:layout_constraintTop_toTopOf="@+id/guideline3"
|
||||
tools:text="Ulick N. Owen" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline3"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_begin="32dp" />
|
||||
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.view.LauncherIconView
|
||||
android:id="@id/icon"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:padding="8dp"
|
||||
android:transitionName="icon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.view.ToolbarView
|
||||
android:id="@+id/contactToolbar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="56dp"
|
||||
android:padding="4dp"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/contactShortcuts" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</de.mm20.launcher2.ui.legacy.view.SwipeCardView>
|
||||
@ -1,73 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<de.mm20.launcher2.ui.legacy.view.SwipeCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/contactCard"
|
||||
style="@style/InnerCardViewStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:transitionName="root">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/contactRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:animateLayoutChanges="true"
|
||||
android:clipChildren="false"
|
||||
android:transitionName="cardLayout">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/contactName"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:maxLines="1"
|
||||
android:transitionName="contactLabel"
|
||||
app:layout_constraintBottom_toTopOf="@+id/guideline2"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||
android:textAppearance="?textAppearanceTitleSmall"
|
||||
tools:text="Ulick N. Owen" />
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/contactSummary"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:singleLine="true"
|
||||
android:textAppearance="?textAppearanceBodySmall"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/icon"
|
||||
app:layout_constraintTop_toTopOf="@+id/guideline2"
|
||||
tools:text="+4912312345678" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline2"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_begin="32dp" />
|
||||
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.view.LauncherIconView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:padding="8dp"
|
||||
android:transitionName="icon"
|
||||
app:layout_constraintBottom_toTopOf="@+id/guideline2"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/guideline2" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</de.mm20.launcher2.ui.legacy.view.SwipeCardView>
|
||||
@ -1,84 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<de.mm20.launcher2.ui.legacy.view.SwipeCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
style="@style/ElevatedInnerCardViewStyle"
|
||||
android:id="@+id/fileCard"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:transitionName="root"
|
||||
app:backgroundOpacity="255">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/fileListRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:transitionName="cardLayout">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fileLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?textAppearanceTitleMedium"
|
||||
android:transitionName="fileLabel"
|
||||
app:layout_constraintBottom_toTopOf="@+id/guideline"
|
||||
app:layout_constraintEnd_toStartOf="@+id/icon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:text="filename.txt" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fileInfo"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:textAppearance="?textAppearanceBodySmall"
|
||||
android:transitionName="description"
|
||||
app:layout_constraintEnd_toStartOf="@+id/icon"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/guideline"
|
||||
tools:layout_conversion_absoluteHeight="19dp"
|
||||
tools:layout_conversion_absoluteWidth="1dp"
|
||||
tools:text="Type: text/plain\nSize: 10 kB\nPath: /storage/emulated/0/filename.txt" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_begin="36dp" />
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.view.LauncherIconView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:padding="8dp"
|
||||
android:transitionName="icon"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.view.ToolbarView
|
||||
android:id="@+id/fileToolbar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="56dp"
|
||||
android:padding="4dp"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/fileInfo" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</de.mm20.launcher2.ui.legacy.view.SwipeCardView>
|
||||
@ -1,76 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<de.mm20.launcher2.ui.legacy.view.SwipeCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/fileCard"
|
||||
style="@style/InnerCardViewStyle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:transitionName="root">
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
android:id="@+id/fileListRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:clipChildren="false"
|
||||
android:clipToPadding="false"
|
||||
android:transitionName="cardLayout">
|
||||
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fileLabel"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="2dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="1"
|
||||
android:textAppearance="?textAppearanceTitleSmall"
|
||||
android:transitionName="fileLabel"
|
||||
app:layout_constraintBottom_toTopOf="@+id/guideline"
|
||||
app:layout_constraintEnd_toStartOf="@+id/icon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
tools:text="filename.txt" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/fileInfo"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="2dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:ellipsize="end"
|
||||
android:textAppearance="?textAppearanceBodySmall"
|
||||
android:transitionName="description"
|
||||
app:layout_constraintEnd_toStartOf="@+id/icon"
|
||||
app:layout_constraintHorizontal_bias="1.0"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/guideline"
|
||||
tools:layout_conversion_absoluteHeight="19dp"
|
||||
tools:layout_conversion_absoluteWidth="1dp"
|
||||
tools:text="Plain text" />
|
||||
|
||||
<androidx.constraintlayout.widget.Guideline
|
||||
android:id="@+id/guideline"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
app:layout_constraintGuide_percent="0.5" />
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.view.LauncherIconView
|
||||
android:id="@+id/icon"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:padding="8dp"
|
||||
android:transitionName="icon"
|
||||
app:layout_constraintBottom_toBottomOf="parent"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
</de.mm20.launcher2.ui.legacy.view.SwipeCardView>
|
||||
@ -1,28 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<de.mm20.launcher2.ui.legacy.view.InnerCardView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/card"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:foreground="?selectableItemBackground"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:transitionName="root">
|
||||
|
||||
<TextView
|
||||
android:id="@+id/informationText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:gravity="center_vertical"
|
||||
android:padding="12dp"
|
||||
android:textAppearance="?textAppearanceBodySmall"
|
||||
app:layout_constraintBottom_toTopOf="@+id/guideline4"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toEndOf="@+id/calendarColor"
|
||||
tools:text="Information" />
|
||||
|
||||
</de.mm20.launcher2.ui.legacy.view.InnerCardView>
|
||||
@ -1,17 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_gravity="center_vertical"
|
||||
android:drawablePadding="12dp"
|
||||
android:padding="12dp"
|
||||
tools:text="12:00"
|
||||
android:gravity="center_vertical"
|
||||
app:drawableTint="@color/text_color_primary"
|
||||
android:foreground="?selectableItemBackground"
|
||||
tools:drawableStart="@drawable/ic_time"
|
||||
android:textAppearance="?textAppearanceBodyMedium">
|
||||
|
||||
</TextView>
|
||||
@ -1,10 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.compose.ui.platform.ComposeView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/composeView"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:layout_marginStart="8dp"
|
||||
android:layout_marginTop="0dp"
|
||||
android:layout_marginEnd="8dp"
|
||||
android:layout_marginBottom="8dp"
|
||||
android:transitionName="root" />
|
||||
@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<de.mm20.launcher2.ui.legacy.view.LauncherCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:background="@color/fake_card_elevated_color"
|
||||
android:clipChildren="false"
|
||||
app:cardElevation="4dp"
|
||||
android:transitionName="root"
|
||||
app:backgroundOpacity="255">
|
||||
|
||||
<include layout="@layout/view_website_list" />
|
||||
|
||||
</de.mm20.launcher2.ui.legacy.view.LauncherCardView>
|
||||
@ -1,70 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
|
||||
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:transitionName="cardLayout">
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.view.AspectRationImageView
|
||||
android:id="@+id/websiteImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/color_divider"
|
||||
android:scaleType="centerCrop"
|
||||
app:aspectRatio="0.5625"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/websiteTitle"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/websiteImage"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="16dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_toStartOf="@id/websiteFavIcon"
|
||||
android:textAppearance="?textAppearanceTitleLarge"
|
||||
app:layout_constraintEnd_toStartOf="@+id/websiteFavIcon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/websiteImage" />
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.view.LauncherIconView
|
||||
android:id="@+id/websiteFavIcon"
|
||||
android:layout_width="64dp"
|
||||
android:layout_height="64dp"
|
||||
android:layout_alignParentEnd="true"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:padding="8dp"
|
||||
android:visibility="visible"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintTop_toTopOf="@+id/websiteTitle" />
|
||||
|
||||
<TextView
|
||||
android:id="@+id/websiteDescription"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_below="@+id/websiteTitle"
|
||||
android:layout_marginStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:layout_marginEnd="16dp"
|
||||
android:layout_toStartOf="@id/websiteFavIcon"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="5"
|
||||
android:textAppearance="?textAppearanceBodyMedium"
|
||||
app:layout_constraintEnd_toStartOf="@+id/websiteFavIcon"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/websiteTitle" />
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.view.ToolbarView
|
||||
android:id="@+id/websiteToolbar"
|
||||
android:layout_width="0dp"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="4dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/websiteDescription" />
|
||||
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
@ -1,15 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<de.mm20.launcher2.ui.legacy.view.LauncherCardView xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_margin="8dp"
|
||||
android:background="@color/fake_card_elevated_color"
|
||||
android:clipChildren="false"
|
||||
app:cardElevation="4dp"
|
||||
android:transitionName="root"
|
||||
app:backgroundOpacity="255">
|
||||
|
||||
<include layout="@layout/view_wikipedia_list" />
|
||||
|
||||
</de.mm20.launcher2.ui.legacy.view.LauncherCardView>
|
||||
@ -1,72 +0,0 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.constraintlayout.widget.ConstraintLayout
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:id="@+id/wikipediaRoot"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
android:transitionName="cardLayout">
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.view.AspectRationImageView
|
||||
android:id="@+id/wikipediaImage"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:background="@color/color_divider"
|
||||
android:scaleType="fitCenter"
|
||||
app:aspectRatio="0.5625"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toTopOf="parent"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wikipediaTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingTop="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:paddingBottom="4dp"
|
||||
android:textAppearance="?textAppearanceTitleLarge"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/wikipediaImage"
|
||||
tools:text="Wikipedia"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wikipediaSubTitle"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:text="@string/wikipedia_source"
|
||||
android:textAppearance="?textAppearanceLabelMedium"
|
||||
android:textColor="?colorSecondary"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/wikipediaTitle"/>
|
||||
|
||||
<TextView
|
||||
android:id="@+id/wikipediaText"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:paddingStart="16dp"
|
||||
android:layout_marginTop="8dp"
|
||||
android:paddingEnd="16dp"
|
||||
android:ellipsize="end"
|
||||
android:maxLines="8"
|
||||
android:textAppearance="?textAppearanceBodyMedium"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/wikipediaSubTitle"
|
||||
tools:text="Wikipedia (/ˌwɪkɪˈpiːdiə/ (About this sound listen), /ˌwɪkiˈpiːdiə/ (About this sound listen) WIK-ih-PEE-dee-ə) is a multilingual, web-based, free encyclopedia based on a model of openly editable content, a wiki. It is the largest and most popular general reference work on the World Wide Web,[3][4][5] and is one of the most popular websites by Alexa rank.[6] It is owned and supported by the Wikimedia Foundation, a non-profit organization which operates on money it receives from donors."/>
|
||||
|
||||
<de.mm20.launcher2.ui.legacy.view.ToolbarView
|
||||
android:id="@+id/wikipediaToolbar"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:padding="4dp"
|
||||
app:layout_constraintEnd_toEndOf="parent"
|
||||
app:layout_constraintStart_toStartOf="parent"
|
||||
app:layout_constraintTop_toBottomOf="@+id/wikipediaText"/>
|
||||
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||
Loading…
x
Reference in New Issue
Block a user