From 07573fe72fcc198f60dbdd1e2ed9908deab17b1a Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Fri, 18 Feb 2022 23:40:05 +0100 Subject: [PATCH] Remove unused code --- .../launcher2/ui/launcher/LauncherActivity.kt | 17 +- .../legacy/fragment/SearchableBottomSheet.kt | 35 -- .../ui/legacy/helper/ActivityStarter.kt | 104 ---- .../search/ApplicationDetailRepresentation.kt | 457 ------------------ .../legacy/search/BasicGridRepresentation.kt | 96 ---- .../search/CalendarDetailRepresentation.kt | 127 ----- .../search/CalendarListRepresentation.kt | 110 ----- .../search/ContactDetailRepresentation.kt | 379 --------------- .../search/ContactListRepresentation.kt | 89 ---- .../legacy/search/FileDetailRepresentation.kt | 202 -------- .../legacy/search/FileListRepresentation.kt | 88 ---- .../search/InformationListRepresentation.kt | 25 - .../search/PermissionListRepresentation.kt | 55 --- .../ui/legacy/search/Representation.kt | 9 - .../ui/legacy/search/SearchGridView.kt | 346 ------------- .../ui/legacy/search/SearchListView.kt | 88 ---- .../search/ShortcutDetailRepresentation.kt | 104 ---- .../search/WebsiteDetailRepresentation.kt | 145 ------ .../search/WebsiteListRepresentation.kt | 142 ------ .../search/WikipediaDetailRepresentation.kt | 76 --- .../search/WikipediaListRepresentation.kt | 80 --- .../ui/legacy/searchable/SearchableView.kt | 158 ------ .../res/layout/view_application_detail.xml | 103 ---- ui/src/main/res/layout/view_basic_grid.xml | 50 -- .../main/res/layout/view_calendar_detail.xml | 65 --- ui/src/main/res/layout/view_calendar_list.xml | 65 --- .../main/res/layout/view_calendar_widget.xml | 146 ------ .../main/res/layout/view_contact_detail.xml | 75 --- ui/src/main/res/layout/view_contact_list.xml | 73 --- ui/src/main/res/layout/view_file_detail.xml | 84 ---- ui/src/main/res/layout/view_file_list.xml | 76 --- .../main/res/layout/view_information_list.xml | 28 -- ui/src/main/res/layout/view_list_item.xml | 17 - .../main/res/layout/view_permission_list.xml | 10 - .../main/res/layout/view_website_detail.xml | 15 - ui/src/main/res/layout/view_website_list.xml | 70 --- .../main/res/layout/view_wikipedia_detail.xml | 15 - .../main/res/layout/view_wikipedia_list.xml | 72 --- 38 files changed, 6 insertions(+), 3890 deletions(-) delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/fragment/SearchableBottomSheet.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/helper/ActivityStarter.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/search/ApplicationDetailRepresentation.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/search/BasicGridRepresentation.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/search/CalendarDetailRepresentation.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/search/CalendarListRepresentation.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/search/ContactDetailRepresentation.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/search/ContactListRepresentation.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/search/FileDetailRepresentation.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/search/FileListRepresentation.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/search/InformationListRepresentation.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/search/PermissionListRepresentation.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/search/Representation.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/search/SearchGridView.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/search/SearchListView.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/search/ShortcutDetailRepresentation.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/search/WebsiteDetailRepresentation.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/search/WebsiteListRepresentation.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/search/WikipediaDetailRepresentation.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/search/WikipediaListRepresentation.kt delete mode 100644 ui/src/main/java/de/mm20/launcher2/ui/legacy/searchable/SearchableView.kt delete mode 100644 ui/src/main/res/layout/view_application_detail.xml delete mode 100644 ui/src/main/res/layout/view_basic_grid.xml delete mode 100644 ui/src/main/res/layout/view_calendar_detail.xml delete mode 100644 ui/src/main/res/layout/view_calendar_list.xml delete mode 100644 ui/src/main/res/layout/view_calendar_widget.xml delete mode 100644 ui/src/main/res/layout/view_contact_detail.xml delete mode 100644 ui/src/main/res/layout/view_contact_list.xml delete mode 100644 ui/src/main/res/layout/view_file_detail.xml delete mode 100644 ui/src/main/res/layout/view_file_list.xml delete mode 100644 ui/src/main/res/layout/view_information_list.xml delete mode 100644 ui/src/main/res/layout/view_list_item.xml delete mode 100644 ui/src/main/res/layout/view_permission_list.xml delete mode 100644 ui/src/main/res/layout/view_website_detail.xml delete mode 100644 ui/src/main/res/layout/view_website_list.xml delete mode 100644 ui/src/main/res/layout/view_wikipedia_detail.xml delete mode 100644 ui/src/main/res/layout/view_wikipedia_list.xml diff --git a/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt b/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt index 32b3ec40..387f131f 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/launcher/LauncherActivity.kt @@ -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 { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/fragment/SearchableBottomSheet.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/fragment/SearchableBottomSheet.kt deleted file mode 100644 index 24f17d6c..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/fragment/SearchableBottomSheet.kt +++ /dev/null @@ -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 - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/helper/ActivityStarter.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/helper/ActivityStarter.kt deleted file mode 100644 index 18cef7ba..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/helper/ActivityStarter.kt +++ /dev/null @@ -1,104 +0,0 @@ -package de.mm20.launcher2.ui.legacy.helper - -import android.app.PendingIntent -import android.content.ActivityNotFoundException -import android.content.Context -import android.content.Intent -import android.graphics.Rect -import android.view.View -import android.view.ViewGroup -import android.view.ViewGroupOverlay -import android.widget.Toast -import androidx.core.app.ActivityOptionsCompat -import de.mm20.launcher2.favorites.FavoritesRepository -import de.mm20.launcher2.search.data.Searchable -import de.mm20.launcher2.ui.R -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject -import java.lang.ref.WeakReference - -object ActivityStarter : KoinComponent { - - private val favoritesRepository: FavoritesRepository by inject() - private lateinit var overlayView: WeakReference - private lateinit var rootView: WeakReference - - 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() -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/ApplicationDetailRepresentation.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/ApplicationDetailRepresentation.kt deleted file mode 100644 index 9a18d35e..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/ApplicationDetailRepresentation.kt +++ /dev/null @@ -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(R.id.appName).text = application.label - val iconView = findViewById(R.id.icon).apply { - icon = iconRepository.getIconIfCached(application) - shape = LauncherIconView.currentShape - } - - val notificationView = findViewById(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(R.id.appCard).also { - it.leftAction = FavoriteSwipeAction(context, application) - it.rightAction = HideSwipeAction(context, application) - } - val appInfo = findViewById(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(R.id.appShortcuts) - appShortcuts.layoutTransition = ChangingLayoutTransition() - setupShortcuts(appShortcuts, application) - - val toolbar = findViewById(R.id.appToolbar) - setupToolbar(this, toolbar, application) - - } - } - - scene.setExitAction { - job?.cancel() - } - - return scene - } - - private fun updateNotifications( - chipGroup: ChipGroup, - notifications: List - ) { - 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.startAppDetailsActivity( - ComponentName(app.`package`, app.activity), - app.castToOrNull()?.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) - } - } - } - - -} diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/BasicGridRepresentation.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/BasicGridRepresentation.kt deleted file mode 100644 index c5f7cdc3..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/BasicGridRepresentation.kt +++ /dev/null @@ -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(R.id.label) - text.text = searchable.label - /*text.alpha = 0f - text.animate() - .setStartDelay(300) - .setDuration(200) - .alpha(1f) - .start()*/ - findViewById(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 - - } - -} diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/CalendarDetailRepresentation.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/CalendarDetailRepresentation.kt deleted file mode 100644 index 58ca0bb3..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/CalendarDetailRepresentation.kt +++ /dev/null @@ -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(R.id.calendarLabel).text = calendarEvent.label - findViewById(R.id.calendarColor).setBackgroundColor(CalendarEvent.getDisplayColor(context, calendarEvent.color)) - findViewById(R.id.calendarEventCard).also { - it.leftAction = FavoriteSwipeAction(context, calendarEvent) - it.rightAction = HideSwipeAction(context, calendarEvent) - it.setOnClickListener { - rootView.representation = SearchableView.REPRESENTATION_FULL - } - } - val toolbar = findViewById(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(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) - } - - -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/CalendarListRepresentation.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/CalendarListRepresentation.kt deleted file mode 100644 index 4eaac18b..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/CalendarListRepresentation.kt +++ /dev/null @@ -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(R.id.calendarLabel).text = calendarEvent.label - - findViewById(R.id.calendarColor).setBackgroundColor( - CalendarEvent.getDisplayColor( - context, - calendarEvent.color - ) - ) - findViewById(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(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 { - 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 - } - -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/ContactDetailRepresentation.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/ContactDetailRepresentation.kt deleted file mode 100644 index 96e984c8..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/ContactDetailRepresentation.kt +++ /dev/null @@ -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(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(R.id.contactName).text = contact.displayName - findViewById(R.id.contactCard).also { - it.leftAction = FavoriteSwipeAction(context, contact) - it.rightAction = HideSwipeAction(context, contact) - } - val toolbar = findViewById(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(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() - } - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/ContactListRepresentation.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/ContactListRepresentation.kt deleted file mode 100644 index 195b287f..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/ContactListRepresentation.kt +++ /dev/null @@ -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(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(R.id.contactName).text = contact.displayName - val contactSummary = findViewById(R.id.contactSummary) - contactSummary.text = contact.summary - findViewById(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 - } - - -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/FileDetailRepresentation.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/FileDetailRepresentation.kt deleted file mode 100644 index 2627cbfe..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/FileDetailRepresentation.kt +++ /dev/null @@ -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(R.id.fileLabel).text = file.label - findViewById(R.id.fileInfo).text = getInfo(context, file) - findViewById(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(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" - } - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/FileListRepresentation.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/FileListRepresentation.kt deleted file mode 100644 index bc335461..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/FileListRepresentation.kt +++ /dev/null @@ -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(R.id.fileLabel).text = file.label - findViewById(R.id.fileInfo).text = file.getFileType(context) - findViewById(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(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 - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/InformationListRepresentation.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/InformationListRepresentation.kt deleted file mode 100644 index 42f75279..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/InformationListRepresentation.kt +++ /dev/null @@ -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(R.id.informationText).text = informationText.label - if (informationText.clickAction != null) { - rootView.findViewById(R.id.card).setOnClickListener { - informationText.clickAction.invoke() - } - } - } - return scene - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/PermissionListRepresentation.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/PermissionListRepresentation.kt deleted file mode 100644 index 915df5e5..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/PermissionListRepresentation.kt +++ /dev/null @@ -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(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 - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/Representation.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/Representation.kt deleted file mode 100644 index 8d4dac5f..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/Representation.kt +++ /dev/null @@ -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 -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/SearchGridView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/SearchGridView.kt deleted file mode 100644 index 83b1deb9..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/SearchGridView.kt +++ /dev/null @@ -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>(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?) { - 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() - - /** - * 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) { - 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() - - 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, newItems: List): Queue { - - 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() - - val mutableNewItems = mutableListOf() - 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 - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/SearchListView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/SearchListView.kt deleted file mode 100644 index e5e5ec9b..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/SearchListView.kt +++ /dev/null @@ -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>(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?) { - 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() - - 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) { - 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) - } - } - } -} diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/ShortcutDetailRepresentation.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/ShortcutDetailRepresentation.kt deleted file mode 100644 index 1891126e..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/ShortcutDetailRepresentation.kt +++ /dev/null @@ -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(R.id.appName).text = appShortcut.label - findViewById(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(R.id.appInfo).text = - context.getString(R.string.shortcut_summary, appName) - - val toolbar = findViewById(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) - - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/WebsiteDetailRepresentation.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/WebsiteDetailRepresentation.kt deleted file mode 100644 index ca3b79f1..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/WebsiteDetailRepresentation.kt +++ /dev/null @@ -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(R.id.websiteTitle) - label.text = website.label - findViewById(R.id.websiteDescription).text = website.description - val websiteImage = findViewById(R.id.websiteImage) - val websiteFavIcon = findViewById(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(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)) - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/WebsiteListRepresentation.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/WebsiteListRepresentation.kt deleted file mode 100644 index 00ad274c..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/WebsiteListRepresentation.kt +++ /dev/null @@ -1,142 +0,0 @@ -package de.mm20.launcher2.ui.legacy.search - -import android.content.Context -import android.content.Intent -import android.view.View -import android.widget.FrameLayout -import android.widget.ImageView -import android.widget.TextView -import androidx.appcompat.app.AppCompatActivity -import androidx.lifecycle.Lifecycle -import androidx.lifecycle.repeatOnLifecycle -import androidx.transition.Scene -import coil.load -import de.mm20.launcher2.badges.BadgeRepository -import de.mm20.launcher2.icons.IconRepository -import de.mm20.launcher2.ktx.dp -import de.mm20.launcher2.ktx.lifecycleOwner -import de.mm20.launcher2.ui.legacy.helper.ActivityStarter -import de.mm20.launcher2.search.data.Searchable -import de.mm20.launcher2.search.data.Website -import de.mm20.launcher2.ui.R -import de.mm20.launcher2.ui.legacy.searchable.SearchableView -import de.mm20.launcher2.ui.legacy.view.FavoriteToolbarAction -import de.mm20.launcher2.ui.legacy.view.LauncherIconView -import de.mm20.launcher2.ui.legacy.view.ToolbarAction -import de.mm20.launcher2.ui.legacy.view.ToolbarView -import kotlinx.coroutines.Job -import kotlinx.coroutines.flow.collectLatest -import kotlinx.coroutines.launch -import org.koin.core.component.KoinComponent -import org.koin.core.component.inject - -class WebsiteListRepresentation : Representation, KoinComponent { - - private val iconRepository: IconRepository by inject() - private val badgeRepository: BadgeRepository by inject() - - private var job: Job? = null - - - override fun getScene( - rootView: SearchableView, - searchable: Searchable, - previousRepresentation: Int? - ): Scene { - val website = searchable as Website - val context = rootView.context as AppCompatActivity - val scene = Scene.getSceneForLayout(rootView, R.layout.view_website_list, rootView.context) - scene.setEnterAction { - with(rootView) { - if (!hasBack()) { - scene.sceneRoot.elevation = 0f - scene.sceneRoot.setBackgroundColor(0) - scene.sceneRoot.setOnClickListener { - ActivityStarter.start(context, rootView, website) - } - } - val label = findViewById(R.id.websiteTitle) - label.text = website.label - findViewById(R.id.websiteDescription).text = website.description - val websiteImage = findViewById(R.id.websiteImage) - val websiteFavIcon = findViewById(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(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)) - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/WikipediaDetailRepresentation.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/WikipediaDetailRepresentation.kt deleted file mode 100644 index 68bfefac..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/WikipediaDetailRepresentation.kt +++ /dev/null @@ -1,76 +0,0 @@ -package de.mm20.launcher2.ui.legacy.search - -import android.content.Context -import android.content.Intent -import android.view.View -import android.widget.ImageView -import android.widget.TextView -import androidx.core.text.HtmlCompat -import androidx.transition.Scene -import coil.load -import de.mm20.launcher2.search.data.Searchable -import de.mm20.launcher2.search.data.Wikipedia -import de.mm20.launcher2.ui.R -import de.mm20.launcher2.ui.legacy.searchable.SearchableView -import de.mm20.launcher2.ui.legacy.view.FavoriteToolbarAction -import de.mm20.launcher2.ui.legacy.view.ToolbarAction -import de.mm20.launcher2.ui.legacy.view.ToolbarView - -class WikipediaDetailRepresentation : Representation { - override fun getScene(rootView: SearchableView, searchable: Searchable, previousRepresentation: Int?): Scene { - val wikipedia = searchable as Wikipedia - val scene = Scene.getSceneForLayout(rootView, R.layout.view_wikipedia_detail, rootView.context) - scene.setEnterAction { - with(rootView) { - findViewById(R.id.wikipediaTitle).text = wikipedia.label - findViewById(R.id.wikipediaText).text = HtmlCompat.fromHtml(wikipedia.text, HtmlCompat.FROM_HTML_MODE_LEGACY) - findViewById(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(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)) - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/WikipediaListRepresentation.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/WikipediaListRepresentation.kt deleted file mode 100644 index 0964c038..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/search/WikipediaListRepresentation.kt +++ /dev/null @@ -1,80 +0,0 @@ -package de.mm20.launcher2.ui.legacy.search - -import android.content.Context -import android.content.Intent -import android.view.View -import android.widget.ImageView -import android.widget.TextView -import androidx.core.text.HtmlCompat -import androidx.transition.Scene -import coil.load -import coil.size.Scale -import de.mm20.launcher2.search.data.Searchable -import de.mm20.launcher2.search.data.Wikipedia -import de.mm20.launcher2.ui.R -import de.mm20.launcher2.ui.legacy.searchable.SearchableView -import de.mm20.launcher2.ui.legacy.view.FavoriteToolbarAction -import de.mm20.launcher2.ui.legacy.view.ToolbarAction -import de.mm20.launcher2.ui.legacy.view.ToolbarView - -class WikipediaListRepresentation : Representation { - override fun getScene( - rootView: SearchableView, - searchable: Searchable, - previousRepresentation: Int? - ): Scene { - val wikipedia = searchable as Wikipedia - val scene = - Scene.getSceneForLayout(rootView, R.layout.view_wikipedia_list, rootView.context) - scene.setEnterAction { - with(rootView) { - findViewById(R.id.wikipediaTitle).text = wikipedia.label - findViewById(R.id.wikipediaText).text = - HtmlCompat.fromHtml(wikipedia.text, HtmlCompat.FROM_HTML_MODE_LEGACY) - findViewById(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(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)) - } -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/searchable/SearchableView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/searchable/SearchableView.kt deleted file mode 100644 index c87a0f7b..00000000 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/searchable/SearchableView.kt +++ /dev/null @@ -1,158 +0,0 @@ -package de.mm20.launcher2.ui.legacy.searchable - -import android.annotation.SuppressLint -import android.content.Context -import android.os.Build -import android.view.animation.DecelerateInterpolator -import android.widget.FrameLayout -import androidx.transition.* -import de.mm20.launcher2.search.data.* -import de.mm20.launcher2.transition.TextResize -import de.mm20.launcher2.ui.legacy.data.InformationText -import de.mm20.launcher2.ui.legacy.search.* -import de.mm20.launcher2.ui.legacy.transition.LauncherCards -import de.mm20.launcher2.ui.legacy.transition.LauncherIconViewTransition -import de.mm20.launcher2.ui.legacy.view.AspectRationImageView -import kotlinx.coroutines.CoroutineScope -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.Job -import kotlinx.coroutines.cancel - -@SuppressLint("ViewConstructor") -open class SearchableView(context: Context, representation: Int) : FrameLayout(context) { - - var searchable: Searchable? = null - set(value) { - field = value - updateRepresentation(null) - } - - - private var defaultRepresentation = representation - - val scope = CoroutineScope(Job() + Dispatchers.Main) - - var representation = representation - set(value) { - val oldVal = field - field = value - if (oldVal != value) { - onRepresentationChange(oldVal, value) - } - updateRepresentation(oldVal) - } - var onRepresentationChange: (Int, Int) -> Unit = { _, _ -> } - - init { - clipChildren = false - updateRepresentation(null) - } - - internal open fun updateRepresentation(previousRepresentation: Int?) { - when (representation) { - REPRESENTATION_FULL -> setFullRepresentation(previousRepresentation) - REPRESENTATION_LIST -> setListRepresentation(previousRepresentation) - REPRESENTATION_GRID -> setGridRepresentation(previousRepresentation) - else -> throw IllegalArgumentException("Must be REPRESENTATION_GRID, REPRESENTATION_LIST or REPRESENTATION_FULL") - } - } - - - private fun setGridRepresentation(previousRepresentation: Int?) { - val searchable = searchable - if (searchable == null) { - removeAllViews() - return - } - val scene = BasicGridRepresentation().getScene(this, searchable, null) - applyScene(scene) - } - - private fun setListRepresentation(previousRepresentation: Int?) { - val searchable = searchable - if (searchable == null) { - removeAllViews() - return - } - val representation = when (searchable) { - is File -> FileListRepresentation() - is Contact -> ContactListRepresentation() - is CalendarEvent -> CalendarListRepresentation() - is Website -> WebsiteListRepresentation() - is Wikipedia -> WikipediaListRepresentation() - is InformationText -> InformationListRepresentation() - is MissingPermission -> PermissionListRepresentation() - else -> return - } - applyScene(representation.getScene(this, searchable, previousRepresentation)) - } - - - private fun setFullRepresentation(previousRepresentation: Int?) { - val searchable = searchable - if (searchable == null) { - removeAllViews() - return - } - val representation = when (searchable) { - is Application -> ApplicationDetailRepresentation() - is Website -> WebsiteDetailRepresentation() - is File -> FileDetailRepresentation() - is Contact -> ContactDetailRepresentation() - is CalendarEvent -> CalendarDetailRepresentation() - is Wikipedia -> WikipediaDetailRepresentation() - is AppShortcut -> AppShortcutDetailRepresentation() - else -> return - } - applyScene(representation.getScene(this, searchable, previousRepresentation)) - } - - private fun applyScene(scene: Scene) { - val transition = TransitionSet().apply { - addTransition( - ChangeBounds().setInterpolator(DecelerateInterpolator()).excludeTarget( - AspectRationImageView::class.java, true - ) - ) - addTransition(LauncherIconViewTransition()) - addTransition(TextResize()) - addTransition(LauncherCards()) - ordering = TransitionSet.ORDERING_TOGETHER - setMatchOrder(Transition.MATCH_NAME, Transition.MATCH_ID) - } - TransitionManager.go(scene, transition) - } - - var onBack: (() -> Unit)? = null - - fun back() { - if (!hasBack()) { - onBack?.invoke() - return - } - representation = defaultRepresentation - } - - fun hasBack(): Boolean { - return defaultRepresentation != REPRESENTATION_FULL - } - - override fun onDetachedFromWindow() { - super.onDetachedFromWindow() - scope.cancel() - } - - companion object { - const val REPRESENTATION_GRID = 0 - const val REPRESENTATION_LIST = 1 - const val REPRESENTATION_FULL = 2 - - fun getView( - context: Context, - searchable: Searchable?, - representation: Int - ): SearchableView { - return SearchableView(context, representation) - } - } -} \ No newline at end of file diff --git a/ui/src/main/res/layout/view_application_detail.xml b/ui/src/main/res/layout/view_application_detail.xml deleted file mode 100644 index 78dc90a9..00000000 --- a/ui/src/main/res/layout/view_application_detail.xml +++ /dev/null @@ -1,103 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ui/src/main/res/layout/view_basic_grid.xml b/ui/src/main/res/layout/view_basic_grid.xml deleted file mode 100644 index 7c66adab..00000000 --- a/ui/src/main/res/layout/view_basic_grid.xml +++ /dev/null @@ -1,50 +0,0 @@ - - - - - - - - - - - \ No newline at end of file diff --git a/ui/src/main/res/layout/view_calendar_detail.xml b/ui/src/main/res/layout/view_calendar_detail.xml deleted file mode 100644 index 732d0f8d..00000000 --- a/ui/src/main/res/layout/view_calendar_detail.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ui/src/main/res/layout/view_calendar_list.xml b/ui/src/main/res/layout/view_calendar_list.xml deleted file mode 100644 index fb28eea2..00000000 --- a/ui/src/main/res/layout/view_calendar_list.xml +++ /dev/null @@ -1,65 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ui/src/main/res/layout/view_calendar_widget.xml b/ui/src/main/res/layout/view_calendar_widget.xml deleted file mode 100644 index 36e74e0b..00000000 --- a/ui/src/main/res/layout/view_calendar_widget.xml +++ /dev/null @@ -1,146 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ui/src/main/res/layout/view_contact_detail.xml b/ui/src/main/res/layout/view_contact_detail.xml deleted file mode 100644 index 2f22085e..00000000 --- a/ui/src/main/res/layout/view_contact_detail.xml +++ /dev/null @@ -1,75 +0,0 @@ - - - - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ui/src/main/res/layout/view_contact_list.xml b/ui/src/main/res/layout/view_contact_list.xml deleted file mode 100644 index 6e3bb7b9..00000000 --- a/ui/src/main/res/layout/view_contact_list.xml +++ /dev/null @@ -1,73 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ui/src/main/res/layout/view_file_detail.xml b/ui/src/main/res/layout/view_file_detail.xml deleted file mode 100644 index 326ff5a5..00000000 --- a/ui/src/main/res/layout/view_file_detail.xml +++ /dev/null @@ -1,84 +0,0 @@ - - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ui/src/main/res/layout/view_file_list.xml b/ui/src/main/res/layout/view_file_list.xml deleted file mode 100644 index 02873737..00000000 --- a/ui/src/main/res/layout/view_file_list.xml +++ /dev/null @@ -1,76 +0,0 @@ - - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ui/src/main/res/layout/view_information_list.xml b/ui/src/main/res/layout/view_information_list.xml deleted file mode 100644 index 45573e3f..00000000 --- a/ui/src/main/res/layout/view_information_list.xml +++ /dev/null @@ -1,28 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/ui/src/main/res/layout/view_list_item.xml b/ui/src/main/res/layout/view_list_item.xml deleted file mode 100644 index f83ec9d4..00000000 --- a/ui/src/main/res/layout/view_list_item.xml +++ /dev/null @@ -1,17 +0,0 @@ - - - - \ No newline at end of file diff --git a/ui/src/main/res/layout/view_permission_list.xml b/ui/src/main/res/layout/view_permission_list.xml deleted file mode 100644 index 6ed12f9c..00000000 --- a/ui/src/main/res/layout/view_permission_list.xml +++ /dev/null @@ -1,10 +0,0 @@ - - \ No newline at end of file diff --git a/ui/src/main/res/layout/view_website_detail.xml b/ui/src/main/res/layout/view_website_detail.xml deleted file mode 100644 index 39798817..00000000 --- a/ui/src/main/res/layout/view_website_detail.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/ui/src/main/res/layout/view_website_list.xml b/ui/src/main/res/layout/view_website_list.xml deleted file mode 100644 index 61134b93..00000000 --- a/ui/src/main/res/layout/view_website_list.xml +++ /dev/null @@ -1,70 +0,0 @@ - - - - - - - - - - - - - - - \ No newline at end of file diff --git a/ui/src/main/res/layout/view_wikipedia_detail.xml b/ui/src/main/res/layout/view_wikipedia_detail.xml deleted file mode 100644 index 41ea38d5..00000000 --- a/ui/src/main/res/layout/view_wikipedia_detail.xml +++ /dev/null @@ -1,15 +0,0 @@ - - - - - - \ No newline at end of file diff --git a/ui/src/main/res/layout/view_wikipedia_list.xml b/ui/src/main/res/layout/view_wikipedia_list.xml deleted file mode 100644 index 830bda7e..00000000 --- a/ui/src/main/res/layout/view_wikipedia_list.xml +++ /dev/null @@ -1,72 +0,0 @@ - - - - - - - - - - - - - \ No newline at end of file