Remove unused code

This commit is contained in:
MM20 2022-02-18 23:40:05 +01:00
parent 5e18d25335
commit 07573fe72f
No known key found for this signature in database
GPG Key ID: 0B61A8F2DEAFA389
38 changed files with 6 additions and 3890 deletions

View File

@ -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 {

View File

@ -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
}
}

View File

@ -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()
}

View File

@ -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)
}
}
}
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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()
}
}
}

View File

@ -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
}
}

View File

@ -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"
}
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}
}

View File

@ -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
}

View File

@ -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
}
}

View File

@ -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)
}
}
}
}

View File

@ -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)
}
}

View File

@ -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))
}
}

View File

@ -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))
}
}

View File

@ -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))
}
}

View File

@ -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))
}
}

View File

@ -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)
}
}
}

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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:0019: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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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" />

View File

@ -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>

View File

@ -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>

View File

@ -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>

View File

@ -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>