From 087d4fd455d0333466cda5e6b7679384e5926dcd Mon Sep 17 00:00:00 2001 From: MM20 <15646950+MM2-0@users.noreply.github.com> Date: Sun, 10 Oct 2021 12:15:54 +0200 Subject: [PATCH] Use dependency injection for most singletons --- app/build.gradle.kts | 3 +- .../de/mm20/launcher2/LauncherApplication.kt | 60 ++-- .../launcher2/activity/AddItemActivity.kt | 5 +- .../fragment/PreferencesAppearanceFragment.kt | 16 +- .../fragment/PreferencesBadgesFragment.kt | 19 +- .../fragment/PreferencesSearchFragment.kt | 3 + .../PreferencesWebSearchesFragment.kt | 104 ++++--- applications/build.gradle.kts | 2 + .../launcher2/applications/AppRepository.kt | 31 +- .../launcher2/applications/AppViewModel.kt | 11 +- .../de/mm20/launcher2/applications/Module.kt | 10 + .../launcher2/search/data/AppSerialization.kt | 139 +++++++++ .../mm20/launcher2/search/data/AppShortcut.kt | 63 +--- .../mm20/launcher2/search/data/LauncherApp.kt | 31 +- appsearch/build.gradle.kts | 2 + .../appsearch/AppSearchRepository.kt | 16 +- .../launcher2/appsearch/AppSearchViewModel.kt | 8 +- .../de/mm20/launcher2/appsearch/Module.kt | 10 + badges/build.gradle.kts | 2 + .../de/mm20/launcher2/badges/BadgeProvider.kt | 11 +- .../java/de/mm20/launcher2/badges/Module.kt | 8 + calculator/build.gradle.kts | 2 + .../calculator/CalculatorRepository.kt | 11 +- .../calculator/CalculatorViewModel.kt | 6 +- .../de/mm20/launcher2/calculator/Module.kt | 9 + calendar/build.gradle.kts | 2 + .../launcher2/calendar/CalendarRepository.kt | 38 +-- .../calendar/CalendarSerialization.kt | 103 +++++++ .../launcher2/calendar/CalendarViewModel.kt | 11 +- .../java/de/mm20/launcher2/calendar/Module.kt | 10 + .../launcher2/search/data/CalendarEvent.kt | 79 ----- contacts/build.gradle.kts | 2 + .../launcher2/contacts/ContactRepository.kt | 16 +- .../contacts/ContactSerialization.kt | 51 +++ .../launcher2/contacts/ContactViewModel.kt | 9 +- .../java/de/mm20/launcher2/contacts/Module.kt | 10 + .../de/mm20/launcher2/search/data/Contact.kt | 27 +- .../currencies/CurrencyRepository.kt | 8 - favorites/build.gradle.kts | 2 + .../mm20/launcher2/favorites/FavoritesItem.kt | 48 +-- .../favorites/FavoritesRepository.kt | 33 +- .../launcher2/favorites/FavoritesViewModel.kt | 36 +-- .../de/mm20/launcher2/favorites/Module.kt | 97 ++++++ .../favorites/SearchableDeserializer.kt | 47 --- files/build.gradle.kts | 2 + .../mm20/launcher2/files/FileSerialization.kt | 291 ++++++++++++++++++ .../mm20/launcher2/files/FilesRepository.kt | 23 +- .../de/mm20/launcher2/files/FilesViewModel.kt | 12 +- .../java/de/mm20/launcher2/files/Module.kt | 10 + .../de/mm20/launcher2/search/data/File.kt | 42 +-- .../mm20/launcher2/search/data/GDriveFile.kt | 87 ++---- .../launcher2/search/data/NextcloudFile.kt | 43 --- .../launcher2/search/data/OneDriveFile.kt | 46 --- .../launcher2/search/data/OwncloudFile.kt | 43 --- hiddenitems/build.gradle.kts | 2 + .../hiddenitems/HiddenItemsRepository.kt | 11 +- .../hiddenitems/HiddenItemsViewModel.kt | 9 +- .../de/mm20/launcher2/hiddenitems/Module.kt | 10 + icons/build.gradle.kts | 2 + .../icons/CalendarDynamicLauncherIcon.kt | 5 - .../icons/ClockDynamicLauncherIcon.kt | 5 +- .../launcher2/icons/DynamicIconController.kt | 10 +- .../launcher2/icons/DynamicLauncherIcon.kt | 3 + .../mm20/launcher2/icons/IconPackManager.kt | 262 +++++++++------- .../de/mm20/launcher2/icons/IconRepository.kt | 40 ++- .../java/de/mm20/launcher2/icons/Module.kt | 10 + music/build.gradle.kts | 2 + .../java/de/mm20/launcher2/music/Module.kt | 10 + .../mm20/launcher2/music/MusicRepository.kt | 8 +- .../de/mm20/launcher2/music/MusicViewModel.kt | 10 +- notifications/build.gradle.kts | 2 + .../notifications/NotificationService.kt | 27 +- search/build.gradle.kts | 2 + .../search/BaseSearchableRepository.kt | 11 +- .../java/de/mm20/launcher2/search/Module.kt | 12 + .../mm20/launcher2/search/SearchRepository.kt | 10 +- .../mm20/launcher2/search/SearchViewModel.kt | 12 +- .../search/SearchableDeserializer.kt | 14 + .../launcher2/search/SearchableSerializer.kt | 8 + .../launcher2/search/WebsearchRepository.kt | 11 +- .../launcher2/search/WebsearchViewModel.kt | 16 +- settings.gradle.kts | 10 +- ui/build.gradle.kts | 3 + .../ui/component/DefaultSwipeActions.kt | 3 +- .../mm20/launcher2/ui/component/SearchBar.kt | 5 +- .../de/mm20/launcher2/ui/component/Toolbar.kt | 5 +- .../launcher2/ui/component/WidgetColumn.kt | 3 +- .../ui/legacy/activity/LauncherActivity.kt | 23 +- .../ui/legacy/component/ApplicationView.kt | 4 +- .../ui/legacy/component/CalculatorView.kt | 4 +- .../ui/legacy/component/CalendarView.kt | 4 +- .../ui/legacy/component/ContactView.kt | 4 +- .../ui/legacy/component/EditFavoritesRow.kt | 9 +- .../ui/legacy/component/EditFavoritesView.kt | 6 +- .../ui/legacy/component/FavoritesView.kt | 3 +- .../launcher2/ui/legacy/component/FileView.kt | 4 +- .../ui/legacy/component/SearchBar.kt | 5 +- .../ui/legacy/component/UnitConverterView.kt | 5 +- .../ui/legacy/component/WebSearchView.kt | 3 +- .../ui/legacy/component/WebsiteView.kt | 4 +- .../ui/legacy/component/WikipediaView.kt | 4 +- .../ui/legacy/helper/ActivityStarter.kt | 8 +- .../search/ApplicationDetailRepresentation.kt | 21 +- .../legacy/search/BasicGridRepresentation.kt | 31 +- .../search/ContactDetailRepresentation.kt | 15 +- .../search/ContactListRepresentation.kt | 14 +- .../legacy/search/FileDetailRepresentation.kt | 17 +- .../legacy/search/FileListRepresentation.kt | 14 +- .../search/ShortcutDetailRepresentation.kt | 14 +- .../search/WebsiteDetailRepresentation.kt | 14 +- .../search/WebsiteListRepresentation.kt | 28 +- .../launcher2/ui/legacy/view/SwipeCardView.kt | 23 +- .../launcher2/ui/legacy/view/ToolbarView.kt | 51 +-- .../ui/legacy/widget/CalendarWidget.kt | 5 +- .../launcher2/ui/legacy/widget/MusicWidget.kt | 6 +- .../launcher2/ui/legacy/widget/SmartWidget.kt | 60 ---- .../launcher2/ui/search/ApplicationResults.kt | 4 +- .../launcher2/ui/search/CalculatorItem.kt | 4 +- .../launcher2/ui/search/FavoriteResults.kt | 5 +- .../mm20/launcher2/ui/search/FileResults.kt | 4 +- .../launcher2/ui/search/WikipediaResult.kt | 4 +- .../launcher2/ui/widget/CalendarWidget.kt | 3 +- .../mm20/launcher2/ui/widget/MusicWidget.kt | 6 +- unitconverter/build.gradle.kts | 2 + .../de/mm20/launcher2/unitconverter/Module.kt | 12 + .../unitconverter/UnitConverterRepository.kt | 16 +- .../unitconverter/UnitConverterViewModel.kt | 9 +- .../converters/CurrencyConverter.kt | 7 +- websites/build.gradle.kts | 2 + .../de/mm20/launcher2/search/data/Website.kt | 23 -- .../java/de/mm20/launcher2/websites/Module.kt | 12 + .../launcher2/websites/WebsiteRepository.kt | 21 +- .../websites/WebsiteSerialization.kt | 39 +++ .../launcher2/websites/WebsiteViewModel.kt | 9 +- widgets/build.gradle.kts | 2 + .../java/de/mm20/launcher2/widgets/Module.kt | 10 + .../launcher2/widgets/WidgetRepository.kt | 2 +- .../mm20/launcher2/widgets/WidgetViewModel.kt | 15 +- wikipedia/build.gradle.kts | 2 + .../mm20/launcher2/search/data/Wikipedia.kt | 19 -- .../de/mm20/launcher2/wikipedia/Module.kt | 10 + .../wikipedia/WikipediaRepository.kt | 15 +- .../wikipedia/WikipediaSerialization.kt | 34 ++ .../launcher2/wikipedia/WikipediaViewModel.kt | 9 +- 144 files changed, 1789 insertions(+), 1277 deletions(-) create mode 100644 applications/src/main/java/de/mm20/launcher2/applications/Module.kt create mode 100644 applications/src/main/java/de/mm20/launcher2/search/data/AppSerialization.kt create mode 100644 appsearch/src/main/java/de/mm20/launcher2/appsearch/Module.kt create mode 100644 badges/src/main/java/de/mm20/launcher2/badges/Module.kt create mode 100644 calculator/src/main/java/de/mm20/launcher2/calculator/Module.kt create mode 100644 calendar/src/main/java/de/mm20/launcher2/calendar/CalendarSerialization.kt create mode 100644 calendar/src/main/java/de/mm20/launcher2/calendar/Module.kt create mode 100644 contacts/src/main/java/de/mm20/launcher2/contacts/ContactSerialization.kt create mode 100644 contacts/src/main/java/de/mm20/launcher2/contacts/Module.kt create mode 100644 favorites/src/main/java/de/mm20/launcher2/favorites/Module.kt delete mode 100644 favorites/src/main/java/de/mm20/launcher2/favorites/SearchableDeserializer.kt create mode 100644 files/src/main/java/de/mm20/launcher2/files/FileSerialization.kt create mode 100644 files/src/main/java/de/mm20/launcher2/files/Module.kt create mode 100644 hiddenitems/src/main/java/de/mm20/launcher2/hiddenitems/Module.kt create mode 100644 icons/src/main/java/de/mm20/launcher2/icons/Module.kt create mode 100644 music/src/main/java/de/mm20/launcher2/music/Module.kt create mode 100644 search/src/main/java/de/mm20/launcher2/search/Module.kt create mode 100644 search/src/main/java/de/mm20/launcher2/search/SearchableDeserializer.kt create mode 100644 search/src/main/java/de/mm20/launcher2/search/SearchableSerializer.kt create mode 100644 unitconverter/src/main/java/de/mm20/launcher2/unitconverter/Module.kt create mode 100644 websites/src/main/java/de/mm20/launcher2/websites/Module.kt create mode 100644 websites/src/main/java/de/mm20/launcher2/websites/WebsiteSerialization.kt create mode 100644 widgets/src/main/java/de/mm20/launcher2/widgets/Module.kt create mode 100644 wikipedia/src/main/java/de/mm20/launcher2/wikipedia/Module.kt create mode 100644 wikipedia/src/main/java/de/mm20/launcher2/wikipedia/WikipediaSerialization.kt diff --git a/app/build.gradle.kts b/app/build.gradle.kts index 550048fd..b7c623cb 100644 --- a/app/build.gradle.kts +++ b/app/build.gradle.kts @@ -116,7 +116,7 @@ dependencies { implementation(libs.draglinearlayout) implementation(libs.viewpropertyobjectanimator) - implementation(libs.bundles.koin) + implementation(libs.koin.android) implementation(project(":applications")) implementation(project(":appsearch")) @@ -126,6 +126,7 @@ dependencies { implementation(project(":calendar")) implementation(project(":contacts")) implementation(project(":crashreporter")) + implementation(project(":currencies")) implementation(project(":favorites")) implementation(project(":files")) implementation(project(":g-services")) diff --git a/app/src/main/java/de/mm20/launcher2/LauncherApplication.kt b/app/src/main/java/de/mm20/launcher2/LauncherApplication.kt index 4319bee7..aeb8a681 100644 --- a/app/src/main/java/de/mm20/launcher2/LauncherApplication.kt +++ b/app/src/main/java/de/mm20/launcher2/LauncherApplication.kt @@ -7,12 +7,29 @@ import android.content.Intent import android.content.IntentFilter import android.graphics.Bitmap import androidx.appcompat.app.AppCompatDelegate +import de.mm20.launcher2.applications.applicationsModule +import de.mm20.launcher2.badges.badgesModule +import de.mm20.launcher2.calculator.calculatorModule +import de.mm20.launcher2.calendar.calendarModule +import de.mm20.launcher2.contacts.contactsModule import de.mm20.launcher2.debug.Debug -import de.mm20.launcher2.icons.IconRepository +import de.mm20.launcher2.favorites.favoritesModule +import de.mm20.launcher2.files.filesModule +import de.mm20.launcher2.hiddenitems.hiddenItemsModule +import de.mm20.launcher2.icons.iconsModule +import de.mm20.launcher2.music.musicModule import de.mm20.launcher2.preferences.LauncherPreferences import de.mm20.launcher2.preferences.Themes +import de.mm20.launcher2.search.searchModule import de.mm20.launcher2.ui.legacy.helper.WallpaperBlur +import de.mm20.launcher2.unitconverter.unitConverterModule +import de.mm20.launcher2.websites.websitesModule +import de.mm20.launcher2.widgets.widgetsModule +import de.mm20.launcher2.wikipedia.wikipediaModule import kotlinx.coroutines.* +import org.koin.android.ext.koin.androidContext +import org.koin.android.ext.koin.androidLogger +import org.koin.core.context.startKoin import java.text.Collator import kotlin.coroutines.CoroutineContext @@ -23,29 +40,12 @@ class LauncherApplication : Application(), CoroutineScope { var blurredWallpaper: Bitmap? = null - - private val appReceiver = object : BroadcastReceiver() { - override fun onReceive(context: Context?, intent: Intent?) { - IconRepository.getInstance(this@LauncherApplication).requestIconPackListUpdate() - } - } - - override fun onCreate() { super.onCreate() Debug() instance = this LauncherPreferences.initialize(this) - IconRepository.getInstance(this).requestIconPackListUpdate() - registerReceiver(appReceiver, IntentFilter().apply { - addAction(Intent.ACTION_PACKAGE_REPLACED) - addAction(Intent.ACTION_PACKAGE_ADDED) - addAction(Intent.ACTION_PACKAGE_REMOVED) - addAction(Intent.ACTION_MY_PACKAGE_REPLACED) - addAction(Intent.ACTION_PACKAGE_CHANGED) - addDataScheme("package") - }) val theme = LauncherPreferences.instance.theme AppCompatDelegate.setDefaultNightMode( when (theme) { @@ -58,6 +58,30 @@ class LauncherApplication : Application(), CoroutineScope { WallpaperBlur.requestBlur(this) @Suppress("DEPRECATION") // We need to access the wallpaper directly to blur it registerReceiver(WallpaperReceiver(), IntentFilter(Intent.ACTION_WALLPAPER_CHANGED)) + + startKoin { + androidLogger() + androidContext(this@LauncherApplication) + modules( + listOf( + applicationsModule, + calculatorModule, + badgesModule, + calendarModule, + contactsModule, + favoritesModule, + filesModule, + hiddenItemsModule, + iconsModule, + musicModule, + searchModule, + unitConverterModule, + websitesModule, + widgetsModule, + wikipediaModule + ) + ) + } } companion object { diff --git a/app/src/main/java/de/mm20/launcher2/activity/AddItemActivity.kt b/app/src/main/java/de/mm20/launcher2/activity/AddItemActivity.kt index f8b0e196..ee5538cb 100644 --- a/app/src/main/java/de/mm20/launcher2/activity/AddItemActivity.kt +++ b/app/src/main/java/de/mm20/launcher2/activity/AddItemActivity.kt @@ -7,9 +7,12 @@ import android.os.Build import android.os.Bundle import de.mm20.launcher2.favorites.FavoritesRepository import de.mm20.launcher2.search.data.AppShortcut +import org.koin.android.ext.android.inject class AddItemActivity : Activity() { + val favoritesRepository: FavoritesRepository by inject() + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.O) { @@ -20,7 +23,7 @@ class AddItemActivity : Activity() { packageManager.getApplicationInfo(shortcutInfo.`package`, 0) .loadLabel(packageManager).toString()) if (pinRequest.accept()) { - FavoritesRepository.getInstance(this).pinItem(shortcut) + favoritesRepository.pinItem(shortcut) } } finish() diff --git a/app/src/main/java/de/mm20/launcher2/fragment/PreferencesAppearanceFragment.kt b/app/src/main/java/de/mm20/launcher2/fragment/PreferencesAppearanceFragment.kt index 933c9175..e3b5054c 100644 --- a/app/src/main/java/de/mm20/launcher2/fragment/PreferencesAppearanceFragment.kt +++ b/app/src/main/java/de/mm20/launcher2/fragment/PreferencesAppearanceFragment.kt @@ -29,6 +29,7 @@ import de.mm20.launcher2.preferences.LauncherPreferences import de.mm20.launcher2.preferences.Themes import de.mm20.launcher2.ui.legacy.view.LauncherIconView import kotlinx.coroutines.launch +import org.koin.android.ext.android.inject class PreferencesAppearanceFragment : PreferenceFragmentCompat() { @@ -74,9 +75,10 @@ class PreferencesAppearanceFragment : PreferenceFragmentCompat() { true } - val manager = IconPackManager.getInstance(requireContext()) + val iconPackManager: IconPackManager by inject() + val iconRepository: IconRepository by inject() lifecycleScope.launch { - val packs = manager.getInstalledIconPacks() + val packs = iconPackManager.getInstalledIconPacks() findPreference("icon_pack")?.apply { entries = packs.map { it.name }.toMutableList().apply { add(0, "System") }.toTypedArray() entryValues = (-1 until packs.size).map { it.toString() }.toTypedArray() @@ -86,14 +88,14 @@ class PreferencesAppearanceFragment : PreferenceFragmentCompat() { } else { isEnabled = true summary = "%s" - value = packs.indexOfFirst { it.packageName == manager.selectedIconPack }.toString() + value = packs.indexOfFirst { it.packageName == iconPackManager.selectedIconPack }.toString() } setOnPreferenceChangeListener { _, newValue -> val index = (newValue as String).toInt() - IconRepository.getInstance(requireContext()).clearCache() - if (index == -1) manager.selectIconPack("") + iconRepository.clearCache() + if (index == -1) iconPackManager.selectIconPack("") else { - manager.selectIconPack(packs[index].packageName) + iconPackManager.selectIconPack(packs[index].packageName) } true } @@ -101,7 +103,7 @@ class PreferencesAppearanceFragment : PreferenceFragmentCompat() { } findPreference("legacy_icon_bg")?.setOnPreferenceChangeListener { _, _ -> - IconRepository.getInstance(requireContext()).clearCache() + iconRepository.clearCache() true } diff --git a/app/src/main/java/de/mm20/launcher2/fragment/PreferencesBadgesFragment.kt b/app/src/main/java/de/mm20/launcher2/fragment/PreferencesBadgesFragment.kt index 9e137b75..33bbb2d5 100644 --- a/app/src/main/java/de/mm20/launcher2/fragment/PreferencesBadgesFragment.kt +++ b/app/src/main/java/de/mm20/launcher2/fragment/PreferencesBadgesFragment.kt @@ -1,4 +1,4 @@ - package de.mm20.launcher2.fragment +package de.mm20.launcher2.fragment import android.os.Bundle import androidx.appcompat.app.AppCompatActivity @@ -7,32 +7,35 @@ import androidx.preference.PreferenceFragmentCompat import de.mm20.launcher2.R import de.mm20.launcher2.badges.BadgeProvider import de.mm20.launcher2.notifications.NotificationService +import org.koin.android.ext.android.inject class PreferencesBadgesFragment : PreferenceFragmentCompat() { + private val badgesProvider: BadgeProvider by inject() + override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { addPreferencesFromResource(R.xml.preferences_badges) findPreference("notification_badges")?.setOnPreferenceChangeListener { _, newValue -> if (newValue as Boolean) { - de.mm20.launcher2.notifications.NotificationService.getInstance()?.generateBadges() + NotificationService.getInstance()?.generateBadges() } else { - BadgeProvider.getInstance(requireContext()).removeNotificationBadges() + badgesProvider.removeNotificationBadges() } true } findPreference("suspended_badges")?.setOnPreferenceChangeListener { _, newValue -> if (newValue as Boolean) { - BadgeProvider.getInstance(requireContext()).addSuspendBadges() + badgesProvider.addSuspendBadges() } else { - BadgeProvider.getInstance(requireContext()).removeSuspendBadges() + badgesProvider.removeSuspendBadges() } true } findPreference("cloud_badges")?.setOnPreferenceChangeListener { _, newValue -> if (newValue as Boolean) { - BadgeProvider.getInstance(requireContext()).addCloudBadges() + badgesProvider.addCloudBadges() } else { - BadgeProvider.getInstance(requireContext()).removeCloudBadges() + badgesProvider.removeCloudBadges() } true } @@ -42,6 +45,6 @@ class PreferencesBadgesFragment : PreferenceFragmentCompat() { override fun onResume() { super.onResume() (activity as AppCompatActivity).supportActionBar - ?.setTitle(R.string.preference_screen_badges) + ?.setTitle(R.string.preference_screen_badges) } } \ No newline at end of file diff --git a/app/src/main/java/de/mm20/launcher2/fragment/PreferencesSearchFragment.kt b/app/src/main/java/de/mm20/launcher2/fragment/PreferencesSearchFragment.kt index 5422ebf9..4328088f 100644 --- a/app/src/main/java/de/mm20/launcher2/fragment/PreferencesSearchFragment.kt +++ b/app/src/main/java/de/mm20/launcher2/fragment/PreferencesSearchFragment.kt @@ -19,9 +19,12 @@ import de.mm20.launcher2.nextcloud.NextcloudApiHelper import de.mm20.launcher2.owncloud.OwncloudClient import de.mm20.launcher2.preferences.LauncherPreferences import kotlinx.coroutines.launch +import org.koin.android.ext.android.inject class PreferencesSearchFragment : PreferenceFragmentCompat() { + private val googleApiHelper: GoogleApiHelper by inject() + override fun onViewCreated(view: View, savedInstanceState: Bundle?) { super.onViewCreated(view, savedInstanceState) lifecycleScope.launch { diff --git a/app/src/main/java/de/mm20/launcher2/fragment/PreferencesWebSearchesFragment.kt b/app/src/main/java/de/mm20/launcher2/fragment/PreferencesWebSearchesFragment.kt index ec3d48bb..b311f9b6 100644 --- a/app/src/main/java/de/mm20/launcher2/fragment/PreferencesWebSearchesFragment.kt +++ b/app/src/main/java/de/mm20/launcher2/fragment/PreferencesWebSearchesFragment.kt @@ -17,7 +17,6 @@ import android.widget.ImageView import androidx.appcompat.app.AppCompatActivity import androidx.core.graphics.scale import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProvider import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat import com.afollestad.materialdialogs.MaterialDialog @@ -29,9 +28,9 @@ import com.bumptech.glide.request.target.SimpleTarget import com.bumptech.glide.request.transition.Transition import de.mm20.launcher2.R import de.mm20.launcher2.ktx.dp -import de.mm20.launcher2.search.SearchViewModel import de.mm20.launcher2.search.WebsearchViewModel import de.mm20.launcher2.search.data.Websearch +import org.koin.androidx.viewmodel.ext.android.viewModel import java.io.File import java.io.FileOutputStream import java.lang.ref.WeakReference @@ -42,9 +41,7 @@ class PreferencesWebSearchesFragment : PreferenceFragmentCompat() { private var sheetIcon: WeakReference? = null - private val viewModel by lazy { - ViewModelProvider(context as AppCompatActivity)[WebsearchViewModel::class.java] - } + private val viewModel: WebsearchViewModel by viewModel() override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { preferenceScreen = preferenceManager.createPreferenceScreen(activity) @@ -61,19 +58,23 @@ class PreferencesWebSearchesFragment : PreferenceFragmentCompat() { val pref = Preference(context) pref.title = search.label if (search.icon == null) { - val drawable = resources.getDrawable(R.drawable.ic_search, requireActivity().theme).mutate() + val drawable = + resources.getDrawable(R.drawable.ic_search, requireActivity().theme).mutate() drawable.setTintMode(PorterDuff.Mode.SRC_ATOP) drawable.setTint(search.color) pref.icon = drawable } else { Glide.with(requireContext()) - .asDrawable() - .load(search.icon) - .into(object : SimpleTarget() { - override fun onResourceReady(resource: Drawable, transition: Transition?) { - pref.icon = resource - } - }) + .asDrawable() + .load(search.icon) + .into(object : SimpleTarget() { + override fun onResourceReady( + resource: Drawable, + transition: Transition? + ) { + pref.icon = resource + } + }) } pref.setOnPreferenceClickListener { editSearch(search) @@ -109,23 +110,23 @@ class PreferencesWebSearchesFragment : PreferenceFragmentCompat() { imageTintList = ColorStateList.valueOf(websearch.color) } else { Glide.with(this) - .load(websearch.icon) - .into(this) + .load(websearch.icon) + .into(this) } sheetIcon = WeakReference(this) } val sheet = MaterialDialog(requireContext(), BottomSheet()) - .cornerRadius(8f) - .customView(view = dialogView) + .cornerRadius(8f) + .customView(view = dialogView) val radius = 8 * dialogView.dp dialogView.background = GradientDrawable().apply { cornerRadii = floatArrayOf( - radius, radius, // top left - radius, radius, // top right - 0f, 0f, // bottom left - 0f, 0f // bottom right + radius, radius, // top left + radius, radius, // top right + 0f, 0f, // bottom left + 0f, 0f // bottom right ) } @@ -134,30 +135,31 @@ class PreferencesWebSearchesFragment : PreferenceFragmentCompat() { sheet.noAutoDismiss() - .positiveButton(android.R.string.ok) { - val newUrl = urlEdit.text.toString() - val newName = nameEdit.text.toString() - if (!newUrl.contains("\${1}")) { - urlEdit.error = getString(R.string.websearch_dialog_url_error) - return@positiveButton + .positiveButton(android.R.string.ok) { + val newUrl = urlEdit.text.toString() + val newName = nameEdit.text.toString() + if (!newUrl.contains("\${1}")) { + urlEdit.error = getString(R.string.websearch_dialog_url_error) + return@positiveButton + } + File(requireContext().cacheDir, "websearch-tmp").takeIf { it.exists() }?.let { + websearch.icon?.let { File(it).takeIf { it.exists() }?.delete() } + val newFile = + File(requireContext().filesDir, "websearch-${System.currentTimeMillis()}") + it.copyTo(newFile, true) + it.delete() + newIcon = newFile.absolutePath + } + if (newIcon == null) { + websearch.icon?.let { File(it).takeIf { it.exists() }?.delete() } + } + websearch.urlTemplate = newUrl + websearch.label = newName + websearch.icon = newIcon + websearch.color = newColor + viewModel.insertWebsearch(websearch) + sheet.dismiss() } - File(requireContext().cacheDir, "websearch-tmp").takeIf { it.exists() }?.let { - websearch.icon?.let { File(it).takeIf { it.exists() }?.delete() } - val newFile = File(requireContext().filesDir, "websearch-${System.currentTimeMillis()}") - it.copyTo(newFile, true) - it.delete() - newIcon = newFile.absolutePath - } - if (newIcon == null) { - websearch.icon?.let { File(it).takeIf { it.exists() }?.delete() } - } - websearch.urlTemplate = newUrl - websearch.label = newName - websearch.icon = newIcon - websearch.color = newColor - viewModel.insertWebsearch(websearch) - sheet.dismiss() - } sheet.negativeButton(android.R.string.cancel) { sheet.cancel() @@ -188,15 +190,16 @@ class PreferencesWebSearchesFragment : PreferenceFragmentCompat() { } title(R.string.websearch_dialog_choose_icon_color) colorChooser( - colors = context.resources.getIntArray(R.array.color_chooser_presets), - allowCustomArgb = true, - showAlphaSelector = false + colors = context.resources.getIntArray(R.array.color_chooser_presets), + allowCustomArgb = true, + showAlphaSelector = false ) { _, color -> iconView.setImageResource(R.drawable.ic_search) iconView.imageTintList = ColorStateList.valueOf(color) newColor = color newIcon = null - File(requireContext().cacheDir, "websearch-tmp").takeIf { it.exists() }?.delete() + File(requireContext().cacheDir, "websearch-tmp").takeIf { it.exists() } + ?.delete() dismiss() } } @@ -207,7 +210,7 @@ class PreferencesWebSearchesFragment : PreferenceFragmentCompat() { override fun onResume() { super.onResume() (activity as AppCompatActivity).supportActionBar - ?.setTitle(R.string.preference_search_edit_websearch) + ?.setTitle(R.string.preference_search_edit_websearch) } override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { @@ -216,7 +219,8 @@ class PreferencesWebSearchesFragment : PreferenceFragmentCompat() { if (requestCode == 24 && resultCode == Activity.RESULT_OK && dataUri != null) { val stream = requireActivity().contentResolver.openInputStream(dataUri) val icon = BitmapFactory.decodeStream(stream) - val scaledIcon = icon.scale((32 * requireContext().dp).toInt(), (32 * requireContext().dp).toInt()) + val scaledIcon = + icon.scale((32 * requireContext().dp).toInt(), (32 * requireContext().dp).toInt()) val out = FileOutputStream(File(requireContext().cacheDir, "websearch-tmp")) scaledIcon.compress(Bitmap.CompressFormat.PNG, 100, out) out.close() diff --git a/applications/build.gradle.kts b/applications/build.gradle.kts index bda45315..0b774fc6 100644 --- a/applications/build.gradle.kts +++ b/applications/build.gradle.kts @@ -42,6 +42,8 @@ dependencies { implementation(libs.bundles.androidx.lifecycle) + implementation(libs.koin.android) + implementation(project(":search")) implementation(project(":base")) implementation(project(":icons")) diff --git a/applications/src/main/java/de/mm20/launcher2/applications/AppRepository.kt b/applications/src/main/java/de/mm20/launcher2/applications/AppRepository.kt index de9fffb0..fe106336 100644 --- a/applications/src/main/java/de/mm20/launcher2/applications/AppRepository.kt +++ b/applications/src/main/java/de/mm20/launcher2/applications/AppRepository.kt @@ -25,7 +25,12 @@ import de.mm20.launcher2.search.data.LauncherApp import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -class AppRepository private constructor(val context: Context) : BaseSearchableRepository() { +class AppRepository( + val context: Context, + val iconRepository: IconRepository, + hiddenItemsRepository: HiddenItemsRepository, + badgeProvider: BadgeProvider + ) : BaseSearchableRepository() { private val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps @@ -34,7 +39,7 @@ class AppRepository private constructor(val context: Context) : BaseSearchableRe private val installedApps = MutableLiveData>(emptyList()) private val installations = MutableLiveData>(mutableListOf()) - private val hiddenItemKeys = HiddenItemsRepository.getInstance(context).hiddenItemsKeys + private val hiddenItemKeys = hiddenItemsRepository.hiddenItemsKeys private val installingPackages = mutableMapOf() @@ -97,14 +102,14 @@ class AppRepository private constructor(val context: Context) : BaseSearchableRe override fun onPackagesSuspended(packageNames: Array?, user: UserHandle?) { super.onPackagesSuspended(packageNames, user) packageNames?.forEach { - BadgeProvider.getInstance(context).setBadge("app://$it", Badge(iconRes = R.drawable.ic_badge_suspended)) + badgeProvider.setBadge("app://$it", Badge(iconRes = R.drawable.ic_badge_suspended)) } } override fun onPackagesUnsuspended(packageNames: Array?, user: UserHandle?) { super.onPackagesUnsuspended(packageNames, user) packageNames?.forEach { - BadgeProvider.getInstance(context).removeBadge("app://$it") + badgeProvider.removeBadge("app://$it") } } @@ -117,7 +122,7 @@ class AppRepository private constructor(val context: Context) : BaseSearchableRe override fun onProgressChanged(sessionId: Int, progress: Float) { val session = packageInstaller.getSessionInfo(sessionId) ?: return val pkg = session.appPackageName ?: return - BadgeProvider.getInstance(context).updateBadge("app://$pkg", Badge(progress = progress)) + badgeProvider.updateBadge("app://$pkg", Badge(progress = progress)) } override fun onActiveChanged(sessionId: Int, active: Boolean) { @@ -129,9 +134,9 @@ class AppRepository private constructor(val context: Context) : BaseSearchableRe val pkg = installingPackages[sessionId] installingPackages.remove(sessionId) val key = "app://$pkg" - val badge = BadgeProvider.getInstance(context).getBadge(key)?.apply { progress = null } + val badge = badgeProvider.getBadge(key)?.apply { progress = null } ?: Badge() - BadgeProvider.getInstance(context).setBadge(key, badge) + badgeProvider.setBadge(key, badge) val inst = installations.value ?: return inst.removeAll { it.session.sessionId == sessionId @@ -144,7 +149,7 @@ class AppRepository private constructor(val context: Context) : BaseSearchableRe val inst = installations.value ?: mutableListOf() inst.removeAll { if (it.session.sessionId == sessionId) { - IconRepository.getInstance(context).removeIconFromCache(it) + iconRepository.removeIconFromCache(it) true } else false } @@ -173,7 +178,7 @@ class AppRepository private constructor(val context: Context) : BaseSearchableRe } private suspend fun updateAppsForDisplay() { - val query = SearchRepository.getInstance().currentQuery.value ?: "" + val query = searchRepository.currentQuery.value ?: "" val componentName = ComponentName.unflattenFromString(query) @@ -216,12 +221,4 @@ class AppRepository private constructor(val context: Context) : BaseSearchableRe return profiles.map { p -> launcherApps.getActivityList(packageName, p).mapNotNull { getApplication(it, p) } }.flatten() } - - companion object { - private lateinit var instance: AppRepository - fun getInstance(context: Context): AppRepository { - if (!::instance.isInitialized) instance = AppRepository(context.applicationContext) - return instance - } - } } \ No newline at end of file diff --git a/applications/src/main/java/de/mm20/launcher2/applications/AppViewModel.kt b/applications/src/main/java/de/mm20/launcher2/applications/AppViewModel.kt index 2a32b84b..98691f0c 100644 --- a/applications/src/main/java/de/mm20/launcher2/applications/AppViewModel.kt +++ b/applications/src/main/java/de/mm20/launcher2/applications/AppViewModel.kt @@ -1,13 +1,12 @@ package de.mm20.launcher2.applications -import android.app.Application as AndroidApp -import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData -import de.mm20.launcher2.applications.AppRepository +import androidx.lifecycle.ViewModel import de.mm20.launcher2.search.data.Application -class AppViewModel(app: AndroidApp): AndroidViewModel(app) { - private val repository = AppRepository.getInstance(app) - val applications: LiveData> = repository.applications +class AppViewModel( + appRepository: AppRepository +): ViewModel() { + val applications: LiveData> = appRepository.applications } diff --git a/applications/src/main/java/de/mm20/launcher2/applications/Module.kt b/applications/src/main/java/de/mm20/launcher2/applications/Module.kt new file mode 100644 index 00000000..b7017c0c --- /dev/null +++ b/applications/src/main/java/de/mm20/launcher2/applications/Module.kt @@ -0,0 +1,10 @@ +package de.mm20.launcher2.applications + +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val applicationsModule = module { + single { AppRepository(androidContext(), get(), get(), get()) } + viewModel { AppViewModel(get()) } +} \ No newline at end of file diff --git a/applications/src/main/java/de/mm20/launcher2/search/data/AppSerialization.kt b/applications/src/main/java/de/mm20/launcher2/search/data/AppSerialization.kt new file mode 100644 index 00000000..74015cd8 --- /dev/null +++ b/applications/src/main/java/de/mm20/launcher2/search/data/AppSerialization.kt @@ -0,0 +1,139 @@ +package de.mm20.launcher2.search.data + +import android.content.ComponentName +import android.content.Context +import android.content.Intent +import android.content.pm.LauncherApps +import android.content.pm.PackageManager +import android.os.Build +import android.os.Process +import android.os.UserManager +import androidx.annotation.RequiresApi +import androidx.core.content.getSystemService +import de.mm20.launcher2.badges.Badge +import de.mm20.launcher2.badges.BadgeProvider +import de.mm20.launcher2.graphics.BadgeDrawable +import de.mm20.launcher2.ktx.jsonObjectOf +import de.mm20.launcher2.search.SearchableDeserializer +import de.mm20.launcher2.search.SearchableSerializer +import kotlinx.coroutines.Dispatchers +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.launch +import kotlinx.coroutines.withContext +import org.json.JSONObject +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class LauncherAppSerializer : SearchableSerializer { + override fun serialize(searchable: Searchable): String { + searchable as LauncherApp + val json = JSONObject() + json.put("package", searchable.`package`) + json.put("activity", searchable.activity) + json.put("user", searchable.userSerialNumber) + return json.toString() + } + + override val typePrefix: String + get() = "app" +} + +class LauncherAppDeserializer(val context: Context) : SearchableDeserializer { + override fun deserialize(serialized: String): Searchable? { + val json = JSONObject(serialized) + val launcherApps = context.getSystemService()!! + val userManager = context.getSystemService()!! + val userSerial = json.optLong("user") + val user = userManager.getUserForSerialNumber(userSerial) ?: Process.myUserHandle() + val pkg = json.getString("package") + val intent = Intent().also { + it.component = ComponentName(pkg, json.getString("activity")) + } + val launcherActivityInfo = launcherApps.resolveActivity(intent, user) ?: return null + return LauncherApp(context, launcherActivityInfo) + } + +} + +class AppShortcutSerializer : SearchableSerializer { + @RequiresApi(Build.VERSION_CODES.N_MR1) + override fun serialize(searchable: Searchable): String { + searchable as AppShortcut + return jsonObjectOf( + "packagename" to searchable.launcherShortcut.`package`, + "id" to searchable.launcherShortcut.id, + "user" to searchable.userSerialNumber, + ).toString() + } + + override val typePrefix: String + get() = "shortcut" + +} + +class AppShortcutDeserializer( + val context: Context, +) : SearchableDeserializer, KoinComponent { + + private val badgeProvider: BadgeProvider by inject() + + @RequiresApi(Build.VERSION_CODES.N_MR1) + override fun deserialize(serialized: String): Searchable? { + val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps + if (!launcherApps.hasShortcutHostPermission()) return null + else { + val json = JSONObject(serialized) + val packageName = json.getString("packagename") + val id = json.getString("id") + val userSerial = json.optLong("user") + val query = LauncherApps.ShortcutQuery() + query.setPackage(packageName) + query.setQueryFlags( + LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC or + LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST or + LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED + ) + query.setShortcutIds(mutableListOf(id)) + val userManager = context.getSystemService()!! + val user = userManager.getUserForSerialNumber(userSerial) ?: Process.myUserHandle() + val shortcuts = try { + launcherApps.getShortcuts(query, user) + } catch (e: IllegalStateException) { + return null + } + val pm = context.packageManager + val appName = try { + pm.getApplicationInfo(packageName, 0).loadLabel(pm).toString() + } catch (e: PackageManager.NameNotFoundException) { + return null + } + if (shortcuts == null || shortcuts.isEmpty()) { + return null + } else { + GlobalScope.launch { + val activity = shortcuts[0].activity + withContext(Dispatchers.IO) { + val icon = try { + context.packageManager.getActivityIcon( + activity + ?: return@withContext + ) + } catch (e: PackageManager.NameNotFoundException) { + return@withContext + } + val badge = Badge(icon = BadgeDrawable(context, icon)) + badgeProvider.setBadge( + "shortcut://${activity.flattenToShortString()}", + badge + ) + } + } + return AppShortcut( + context = context, + launcherShortcut = shortcuts[0], + appName = appName + ) + } + } + } +} \ No newline at end of file diff --git a/applications/src/main/java/de/mm20/launcher2/search/data/AppShortcut.kt b/applications/src/main/java/de/mm20/launcher2/search/data/AppShortcut.kt index 13b1e40a..6c1c113d 100644 --- a/applications/src/main/java/de/mm20/launcher2/search/data/AppShortcut.kt +++ b/applications/src/main/java/de/mm20/launcher2/search/data/AppShortcut.kt @@ -39,7 +39,7 @@ class AppShortcut( - private val userSerialNumber: Long = launcherShortcut.userHandle.getSerialNumber(context) + internal val userSerialNumber: Long = launcherShortcut.userHandle.getSerialNumber(context) private val isMainProfile = launcherShortcut.userHandle == Process.myUserHandle() override val key: String @@ -58,15 +58,6 @@ class AppShortcut( } } - override fun serialize(): String { - return jsonObjectOf( - "packagename" to launcherShortcut.`package`, - "id" to launcherShortcut.id, - "user" to userSerialNumber, - ).toString() - } - - override fun getLaunchIntent(context: Context): Intent? { return launcherShortcut.intent } @@ -106,56 +97,4 @@ class AppShortcut( autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() ) } - - companion object { - fun deserialize(context: Context, serialized: String): AppShortcut? { - val launcherApps = context.getSystemService(Context.LAUNCHER_APPS_SERVICE) as LauncherApps - if (!launcherApps.hasShortcutHostPermission()) return null - else { - val json = JSONObject(serialized) - val packageName = json.getString("packagename") - val id = json.getString("id") - val userSerial = json.optLong("user") - val query = LauncherApps.ShortcutQuery() - query.setPackage(packageName) - query.setQueryFlags(LauncherApps.ShortcutQuery.FLAG_MATCH_DYNAMIC or - LauncherApps.ShortcutQuery.FLAG_MATCH_MANIFEST or - LauncherApps.ShortcutQuery.FLAG_MATCH_PINNED) - query.setShortcutIds(mutableListOf(id)) - val userManager = context.getSystemService()!! - val user = userManager.getUserForSerialNumber(userSerial) ?: Process.myUserHandle() - val shortcuts = try { - launcherApps.getShortcuts(query, user) - } catch (e: IllegalStateException) { - return null - } - val pm = context.packageManager - val appName = try { - pm.getApplicationInfo(packageName, 0).loadLabel(pm).toString() - } catch (e: PackageManager.NameNotFoundException) { - return null - } - if (shortcuts == null || shortcuts.isEmpty()) return null else { - GlobalScope.launch { - val activity = shortcuts[0].activity - withContext(Dispatchers.IO) { - val icon = try { - context.packageManager.getActivityIcon(activity - ?: return@withContext) - } catch (e: PackageManager.NameNotFoundException) { - return@withContext - } - val badge = Badge(icon = BadgeDrawable(context, icon)) - BadgeProvider.getInstance(context).setBadge("shortcut://${activity.flattenToShortString()}", badge) - } - } - return AppShortcut( - context = context, - launcherShortcut = shortcuts[0], - appName = appName - ) - } - } - } - } } \ No newline at end of file diff --git a/applications/src/main/java/de/mm20/launcher2/search/data/LauncherApp.kt b/applications/src/main/java/de/mm20/launcher2/search/data/LauncherApp.kt index 8087ad39..ce935217 100644 --- a/applications/src/main/java/de/mm20/launcher2/search/data/LauncherApp.kt +++ b/applications/src/main/java/de/mm20/launcher2/search/data/LauncherApp.kt @@ -15,6 +15,8 @@ import de.mm20.launcher2.ktx.getSerialNumber import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext import org.json.JSONObject +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject /** * An [Application] based on an [android.content.pm.LauncherActivityInfo] @@ -46,9 +48,9 @@ class LauncherApp( } appShortcuts } -) { +), KoinComponent { - private val userSerialNumber: Long = launcherActivityInfo.user.getSerialNumber(context) + internal val userSerialNumber: Long = launcherActivityInfo.user.getSerialNumber(context) private val isMainProfile = launcherActivityInfo.user == Process.myUserHandle() override val badgeKey: String = if (isMainProfile) "app://${`package`}" else "profile://$userSerialNumber" @@ -56,21 +58,14 @@ class LauncherApp( override val key: String get() = if (isMainProfile) "app://$`package`:$activity" else "app://$`package`:$activity:${userSerialNumber}" - override fun serialize(): String { - val json = JSONObject() - json.put("package", `package`) - json.put("activity", activity) - json.put("user", userSerialNumber) - return json.toString() - } - fun getUser(): UserHandle? { return launcherActivityInfo.user } override suspend fun loadIconAsync(context: Context, size: Int): LauncherIcon? { + val iconPackManager: IconPackManager by inject() return withContext(Dispatchers.IO) { - IconPackManager.getInstance(context).getIcon(context, launcherActivityInfo, size) + iconPackManager.getIcon(context, launcherActivityInfo, size) } } @@ -98,20 +93,6 @@ class LauncherApp( companion object { - fun deserialize(context: Context, serialized: String): LauncherApp? { - val json = JSONObject(serialized) - val launcherApps = context.getSystemService()!! - val userManager = context.getSystemService()!! - val userSerial = json.optLong("user") - val user = userManager.getUserForSerialNumber(userSerial) ?: Process.myUserHandle() - val pkg = json.getString("package") - val intent = Intent().also { - it.component = ComponentName(pkg, json.getString("activity")) - } - val launcherActivityInfo = launcherApps.resolveActivity(intent, user) ?: return null - return LauncherApp(context, launcherActivityInfo) - } - fun getPackageVersionName(context: Context, packageName: String): String? { return try { context.packageManager.getPackageInfo(packageName, 0).versionName diff --git a/appsearch/build.gradle.kts b/appsearch/build.gradle.kts index f211a865..e7a174aa 100644 --- a/appsearch/build.gradle.kts +++ b/appsearch/build.gradle.kts @@ -48,6 +48,8 @@ dependencies { implementation(libs.guava) + implementation(libs.koin.android) + implementation(project(":search")) implementation(project(":base")) implementation(project(":icons")) diff --git a/appsearch/src/main/java/de/mm20/launcher2/appsearch/AppSearchRepository.kt b/appsearch/src/main/java/de/mm20/launcher2/appsearch/AppSearchRepository.kt index 287aa1b0..1a2d9107 100644 --- a/appsearch/src/main/java/de/mm20/launcher2/appsearch/AppSearchRepository.kt +++ b/appsearch/src/main/java/de/mm20/launcher2/appsearch/AppSearchRepository.kt @@ -16,14 +16,17 @@ import kotlinx.coroutines.withContext import java.util.concurrent.Executors import kotlin.coroutines.suspendCoroutine -class AppSearchRepository private constructor(val context: Context) : BaseSearchableRepository() { +class AppSearchRepository( + val context: Context, + hiddenItemsRepository: HiddenItemsRepository + ) : BaseSearchableRepository() { private var session: GlobalSearchSession? = null val appSearchResults = MediatorLiveData?>() private val allAppSearchResults = MutableLiveData?>(emptyList()) - private val hiddenItemKeys = HiddenItemsRepository.getInstance(context).hiddenItemsKeys + private val hiddenItemKeys = hiddenItemsRepository.hiddenItemsKeys init { appSearchResults.addSource(hiddenItemKeys) { keys -> @@ -57,13 +60,4 @@ class AppSearchRepository private constructor(val context: Context) : BaseSearch } allAppSearchResults.value = results } - - companion object { - private lateinit var instance: AppSearchRepository - fun getInstance(context: Context): AppSearchRepository { - if (!::instance.isInitialized) instance = AppSearchRepository(context.applicationContext) - return instance - } - } - } \ No newline at end of file diff --git a/appsearch/src/main/java/de/mm20/launcher2/appsearch/AppSearchViewModel.kt b/appsearch/src/main/java/de/mm20/launcher2/appsearch/AppSearchViewModel.kt index ddecaf0e..ddc66f47 100644 --- a/appsearch/src/main/java/de/mm20/launcher2/appsearch/AppSearchViewModel.kt +++ b/appsearch/src/main/java/de/mm20/launcher2/appsearch/AppSearchViewModel.kt @@ -3,9 +3,11 @@ package de.mm20.launcher2.appsearch import android.app.Application import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel import de.mm20.launcher2.search.data.AppSearchResult -class AppSearchViewModel(app: Application) : AndroidViewModel(app) { - private val repository = AppSearchRepository.getInstance(app) - private val appSearch: LiveData?> = repository.appSearchResults +class AppSearchViewModel( + appSearchRepository: AppSearchRepository +) : ViewModel() { + private val appSearch: LiveData?> = appSearchRepository.appSearchResults } \ No newline at end of file diff --git a/appsearch/src/main/java/de/mm20/launcher2/appsearch/Module.kt b/appsearch/src/main/java/de/mm20/launcher2/appsearch/Module.kt new file mode 100644 index 00000000..045f3a70 --- /dev/null +++ b/appsearch/src/main/java/de/mm20/launcher2/appsearch/Module.kt @@ -0,0 +1,10 @@ +package de.mm20.launcher2.appsearch + +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val appSearchModule = module { + single { AppSearchRepository(androidContext(), get()) } + viewModel { AppSearchViewModel(get()) } +} \ No newline at end of file diff --git a/badges/build.gradle.kts b/badges/build.gradle.kts index 415569ba..7af5c35d 100644 --- a/badges/build.gradle.kts +++ b/badges/build.gradle.kts @@ -42,6 +42,8 @@ dependencies { implementation(libs.bundles.androidx.lifecycle) + implementation(libs.koin.android) + implementation(project(":ktx")) implementation(project(":preferences")) diff --git a/badges/src/main/java/de/mm20/launcher2/badges/BadgeProvider.kt b/badges/src/main/java/de/mm20/launcher2/badges/BadgeProvider.kt index 36d2565c..a59fdee0 100644 --- a/badges/src/main/java/de/mm20/launcher2/badges/BadgeProvider.kt +++ b/badges/src/main/java/de/mm20/launcher2/badges/BadgeProvider.kt @@ -13,7 +13,7 @@ import de.mm20.launcher2.ktx.isAtLeastApiLevel import de.mm20.launcher2.preferences.LauncherPreferences import kotlinx.coroutines.* -class BadgeProvider private constructor(val context: Context) { +class BadgeProvider(val context: Context) { private val scope = CoroutineScope(Job() + Dispatchers.Main) @@ -138,13 +138,4 @@ class BadgeProvider private constructor(val context: Context) { } } } - - companion object { - private lateinit var instance: BadgeProvider - - fun getInstance(context: Context): BadgeProvider { - if (!::instance.isInitialized) instance = BadgeProvider(context.applicationContext) - return instance - } - } } \ No newline at end of file diff --git a/badges/src/main/java/de/mm20/launcher2/badges/Module.kt b/badges/src/main/java/de/mm20/launcher2/badges/Module.kt new file mode 100644 index 00000000..b7cc9710 --- /dev/null +++ b/badges/src/main/java/de/mm20/launcher2/badges/Module.kt @@ -0,0 +1,8 @@ +package de.mm20.launcher2.badges + +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module + +val badgesModule = module { + single { BadgeProvider(androidContext()) } +} \ No newline at end of file diff --git a/calculator/build.gradle.kts b/calculator/build.gradle.kts index d8f01b4f..9934806e 100644 --- a/calculator/build.gradle.kts +++ b/calculator/build.gradle.kts @@ -44,6 +44,8 @@ dependencies { implementation(libs.mathparser) + implementation(libs.koin.android) + implementation(project(":preferences")) implementation(project(":search")) diff --git a/calculator/src/main/java/de/mm20/launcher2/calculator/CalculatorRepository.kt b/calculator/src/main/java/de/mm20/launcher2/calculator/CalculatorRepository.kt index 5df784ac..5cdd727d 100644 --- a/calculator/src/main/java/de/mm20/launcher2/calculator/CalculatorRepository.kt +++ b/calculator/src/main/java/de/mm20/launcher2/calculator/CalculatorRepository.kt @@ -6,7 +6,7 @@ import de.mm20.launcher2.search.BaseSearchableRepository import de.mm20.launcher2.search.data.Calculator import org.mariuszgromada.math.mxparser.Expression -class CalculatorRepository private constructor() : BaseSearchableRepository() { +class CalculatorRepository : BaseSearchableRepository() { val calculator = MutableLiveData() @@ -52,13 +52,4 @@ class CalculatorRepository private constructor() : BaseSearchableRepository() { } calculator.value = calc } - - companion object { - private lateinit var instance: CalculatorRepository - - fun getInstance(): CalculatorRepository { - if (!::instance.isInitialized) instance = CalculatorRepository() - return instance - } - } } \ No newline at end of file diff --git a/calculator/src/main/java/de/mm20/launcher2/calculator/CalculatorViewModel.kt b/calculator/src/main/java/de/mm20/launcher2/calculator/CalculatorViewModel.kt index 7dae9c4a..ccd93e19 100644 --- a/calculator/src/main/java/de/mm20/launcher2/calculator/CalculatorViewModel.kt +++ b/calculator/src/main/java/de/mm20/launcher2/calculator/CalculatorViewModel.kt @@ -2,6 +2,8 @@ package de.mm20.launcher2.calculator import androidx.lifecycle.ViewModel -class CalculatorViewModel: ViewModel() { - val calculator = CalculatorRepository.getInstance().calculator +class CalculatorViewModel( + calculatorRepository: CalculatorRepository +): ViewModel() { + val calculator = calculatorRepository.calculator } \ No newline at end of file diff --git a/calculator/src/main/java/de/mm20/launcher2/calculator/Module.kt b/calculator/src/main/java/de/mm20/launcher2/calculator/Module.kt new file mode 100644 index 00000000..6afdd62e --- /dev/null +++ b/calculator/src/main/java/de/mm20/launcher2/calculator/Module.kt @@ -0,0 +1,9 @@ +package de.mm20.launcher2.calculator + +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val calculatorModule = module { + single { CalculatorRepository() } + viewModel { CalculatorViewModel(get()) } +} \ No newline at end of file diff --git a/calendar/build.gradle.kts b/calendar/build.gradle.kts index f3634891..cb25bf5f 100644 --- a/calendar/build.gradle.kts +++ b/calendar/build.gradle.kts @@ -42,6 +42,8 @@ dependencies { implementation(libs.textdrawable) + implementation(libs.koin.android) + api(project(":search")) implementation(project(":preferences")) implementation(project(":ktx")) diff --git a/calendar/src/main/java/de/mm20/launcher2/calendar/CalendarRepository.kt b/calendar/src/main/java/de/mm20/launcher2/calendar/CalendarRepository.kt index 70233402..e3fe78f7 100644 --- a/calendar/src/main/java/de/mm20/launcher2/calendar/CalendarRepository.kt +++ b/calendar/src/main/java/de/mm20/launcher2/calendar/CalendarRepository.kt @@ -10,13 +10,16 @@ import de.mm20.launcher2.search.data.CalendarEvent import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -class CalendarRepository private constructor(val context: Context) : BaseSearchableRepository() { +class CalendarRepository( + val context: Context, + hiddenItemsRepository: HiddenItemsRepository +) : BaseSearchableRepository() { val calendarEvents = MediatorLiveData?>() val upcomingCalendarEvents = MutableLiveData>(emptyList()) private val allEvents = MutableLiveData?>(emptyList()) - private val hiddenItemKeys = HiddenItemsRepository.getInstance(context).hiddenItemsKeys + private val hiddenItemKeys = hiddenItemsRepository.hiddenItemsKeys init { calendarEvents.addSource(hiddenItemKeys) { keys -> @@ -40,17 +43,17 @@ class CalendarRepository private constructor(val context: Context) : BaseSearcha val end = now + 14 * 24 * 60 * 60 * 1000L val events = withContext(Dispatchers.IO) { CalendarEvent.search( - context = context, - query = "", - intervalStart = now, - intervalEnd = end, - limit = 700, - hideAllDayEvents = hideAlldayEvents, - unselectedCalendars = unselectedCalendars, - hiddenEvents = hiddenItemKeys.value?.mapNotNull { - if (it.startsWith("calendar")) it.substringAfterLast("/").toLong() - else null - } ?: emptyList() + context = context, + query = "", + intervalStart = now, + intervalEnd = end, + limit = 700, + hideAllDayEvents = hideAlldayEvents, + unselectedCalendars = unselectedCalendars, + hiddenEvents = hiddenItemKeys.value?.mapNotNull { + if (it.startsWith("calendar")) it.substringAfterLast("/").toLong() + else null + } ?: emptyList() ) } upcomingCalendarEvents.value = events @@ -69,13 +72,4 @@ class CalendarRepository private constructor(val context: Context) : BaseSearcha } allEvents.value = events } - - - companion object { - private lateinit var instance: CalendarRepository - fun getInstance(context: Context): CalendarRepository { - if (!::instance.isInitialized) instance = CalendarRepository(context.applicationContext) - return instance - } - } } \ No newline at end of file diff --git a/calendar/src/main/java/de/mm20/launcher2/calendar/CalendarSerialization.kt b/calendar/src/main/java/de/mm20/launcher2/calendar/CalendarSerialization.kt new file mode 100644 index 00000000..08a33db8 --- /dev/null +++ b/calendar/src/main/java/de/mm20/launcher2/calendar/CalendarSerialization.kt @@ -0,0 +1,103 @@ +package de.mm20.launcher2.calendar + +import android.Manifest +import android.content.ContentUris +import android.content.Context +import android.content.pm.PackageManager +import android.provider.CalendarContract +import androidx.core.content.ContextCompat +import androidx.core.database.getStringOrNull +import de.mm20.launcher2.search.SearchableDeserializer +import de.mm20.launcher2.search.SearchableSerializer +import de.mm20.launcher2.search.data.CalendarEvent +import de.mm20.launcher2.search.data.Searchable +import org.json.JSONObject +import java.util.* + +class CalendarEventSerializer: SearchableSerializer { + override fun serialize(searchable: Searchable): String { + searchable as CalendarEvent + val json = JSONObject() + json.put("id", searchable.id) + return json.toString() + } + + override val typePrefix: String + get() = "calendar" +} + +class CalendarEventDeserializer(val context: Context): SearchableDeserializer { + override fun deserialize(serialized: String): Searchable? { + if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) return null + val json = JSONObject(serialized) + val id = json.getLong("id") + val builder = CalendarContract.Instances.CONTENT_URI.buildUpon() + ContentUris.appendId(builder, System.currentTimeMillis()) + ContentUris.appendId(builder, System.currentTimeMillis() + 63072000000L) + val uri = builder.build() + val projection = arrayOf( + CalendarContract.Instances.EVENT_ID, + CalendarContract.Instances.TITLE, + CalendarContract.Instances.BEGIN, + CalendarContract.Instances.END, + CalendarContract.Instances.ALL_DAY, + CalendarContract.Instances.DISPLAY_COLOR, + CalendarContract.Instances.EVENT_LOCATION, + CalendarContract.Instances.CALENDAR_ID, + CalendarContract.Instances.DESCRIPTION + ) + val selection = CalendarContract.Instances.EVENT_ID + " = ?" + val selArgs = arrayOf(id.toString()) + val cursor = context.contentResolver.query(uri, projection, selection, selArgs, null) + ?: return null + if (cursor.moveToNext()) { + val title = cursor.getString(1) + val begin = cursor.getLong(2) + val end = cursor.getLong(3) + val allday = cursor.getInt(4) != 0 + val color = cursor.getInt(5) + val location = cursor.getString(6) + val calendar = cursor.getLong(7) + val description = cursor.getStringOrNull(8) + ?: "" + cursor.close() + val proj = arrayOf( + CalendarContract.Attendees.EVENT_ID, + CalendarContract.Attendees.ATTENDEE_NAME, + CalendarContract.Attendees.ATTENDEE_EMAIL + ) + val sel = "${CalendarContract.Attendees.EVENT_ID} = $id" + val s = "${CalendarContract.Attendees.ATTENDEE_NAME} COLLATE NOCASE ASC" + val cur = context.contentResolver.query( + CalendarContract.Attendees.CONTENT_URI, + proj, sel, null, s + ) ?: return null + val attendees = mutableListOf() + while (cur.moveToNext()) { + attendees.add(cur.getString(1).takeUnless { it.isNullOrBlank() } + ?: cur.getString(2)) + } + cur.close() + val tzOffset = if (allday) { + Calendar.getInstance().timeZone.getOffset(begin) + } else { + 0 + } + return CalendarEvent( + label = title, + id = id, + color = color, + startTime = begin - tzOffset, + endTime = end - tzOffset - if (allday) 1 else 0, + allDay = allday, + location = location ?: "", + attendees = attendees, + description = description, + calendar = calendar + ) + } + cursor.close() + return null + } + +} \ No newline at end of file diff --git a/calendar/src/main/java/de/mm20/launcher2/calendar/CalendarViewModel.kt b/calendar/src/main/java/de/mm20/launcher2/calendar/CalendarViewModel.kt index f137f669..d156e7b7 100644 --- a/calendar/src/main/java/de/mm20/launcher2/calendar/CalendarViewModel.kt +++ b/calendar/src/main/java/de/mm20/launcher2/calendar/CalendarViewModel.kt @@ -1,11 +1,12 @@ package de.mm20.launcher2.calendar -import android.app.Application -import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel import de.mm20.launcher2.search.data.CalendarEvent -class CalendarViewModel(app:Application): AndroidViewModel(app) { - val calendarEvents: LiveData?> = CalendarRepository.getInstance(app).calendarEvents - val upcomingCalendarEvents: LiveData> = CalendarRepository.getInstance(app).upcomingCalendarEvents +class CalendarViewModel( + calendarRepository: CalendarRepository +): ViewModel() { + val calendarEvents: LiveData?> = calendarRepository.calendarEvents + val upcomingCalendarEvents: LiveData> = calendarRepository.upcomingCalendarEvents } \ No newline at end of file diff --git a/calendar/src/main/java/de/mm20/launcher2/calendar/Module.kt b/calendar/src/main/java/de/mm20/launcher2/calendar/Module.kt new file mode 100644 index 00000000..8458f5de --- /dev/null +++ b/calendar/src/main/java/de/mm20/launcher2/calendar/Module.kt @@ -0,0 +1,10 @@ +package de.mm20.launcher2.calendar + +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val calendarModule = module { + single { CalendarRepository(androidContext(), get()) } + viewModel { CalendarViewModel(get()) } +} \ No newline at end of file diff --git a/calendar/src/main/java/de/mm20/launcher2/search/data/CalendarEvent.kt b/calendar/src/main/java/de/mm20/launcher2/search/data/CalendarEvent.kt index c0ef3681..464b871c 100644 --- a/calendar/src/main/java/de/mm20/launcher2/search/data/CalendarEvent.kt +++ b/calendar/src/main/java/de/mm20/launcher2/search/data/CalendarEvent.kt @@ -41,12 +41,6 @@ class CalendarEvent( val calendar: Long ) : Searchable() { - override fun serialize(): String { - val json = JSONObject() - json.put("id", id) - return json.toString() - } - override val key: String get() = "calendar://$id" @@ -175,79 +169,6 @@ class CalendarEvent( return results } - fun deserialize(context: Context, serialized: String): CalendarEvent? { - if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CALENDAR) != PackageManager.PERMISSION_GRANTED) return null - val json = JSONObject(serialized) - val id = json.getLong("id") - val builder = CalendarContract.Instances.CONTENT_URI.buildUpon() - ContentUris.appendId(builder, System.currentTimeMillis()) - ContentUris.appendId(builder, System.currentTimeMillis() + 63072000000L) - val uri = builder.build() - val projection = arrayOf( - CalendarContract.Instances.EVENT_ID, - CalendarContract.Instances.TITLE, - CalendarContract.Instances.BEGIN, - CalendarContract.Instances.END, - CalendarContract.Instances.ALL_DAY, - CalendarContract.Instances.DISPLAY_COLOR, - CalendarContract.Instances.EVENT_LOCATION, - CalendarContract.Instances.CALENDAR_ID, - CalendarContract.Instances.DESCRIPTION - ) - val selection = CalendarContract.Instances.EVENT_ID + " = ?" - val selArgs = arrayOf(id.toString()) - val cursor = context.contentResolver.query(uri, projection, selection, selArgs, null) - ?: return null - if (cursor.moveToNext()) { - val title = cursor.getString(1) - val begin = cursor.getLong(2) - val end = cursor.getLong(3) - val allday = cursor.getInt(4) != 0 - val color = cursor.getInt(5) - val location = cursor.getString(6) - val calendar = cursor.getLong(7) - val description = cursor.getStringOrNull(8) - ?: "" - cursor.close() - val proj = arrayOf( - CalendarContract.Attendees.EVENT_ID, - CalendarContract.Attendees.ATTENDEE_NAME, - CalendarContract.Attendees.ATTENDEE_EMAIL - ) - val sel = "${CalendarContract.Attendees.EVENT_ID} = $id" - val s = "${CalendarContract.Attendees.ATTENDEE_NAME} COLLATE NOCASE ASC" - val cur = context.contentResolver.query( - CalendarContract.Attendees.CONTENT_URI, - proj, sel, null, s - ) ?: return null - val attendees = mutableListOf() - while (cur.moveToNext()) { - attendees.add(cur.getString(1).takeUnless { it.isNullOrBlank() } - ?: cur.getString(2)) - } - cur.close() - val tzOffset = if (allday) { - Calendar.getInstance().timeZone.getOffset(begin) - } else { - 0 - } - return CalendarEvent( - label = title, - id = id, - color = color, - startTime = begin - tzOffset, - endTime = end - tzOffset - if (allday) 1 else 0, - allDay = allday, - location = location ?: "", - attendees = attendees, - description = description, - calendar = calendar - ) - } - cursor.close() - return null - } - fun getCalendars(context: Context): List { val calendars = mutableListOf() val uri = CalendarContract.Calendars.CONTENT_URI diff --git a/contacts/build.gradle.kts b/contacts/build.gradle.kts index 19f51ff6..b39689bd 100644 --- a/contacts/build.gradle.kts +++ b/contacts/build.gradle.kts @@ -42,6 +42,8 @@ dependencies { implementation(libs.textdrawable) + implementation(libs.koin.android) + implementation(project(":search")) implementation(project(":preferences")) implementation(project(":ktx")) diff --git a/contacts/src/main/java/de/mm20/launcher2/contacts/ContactRepository.kt b/contacts/src/main/java/de/mm20/launcher2/contacts/ContactRepository.kt index 7ab42335..1421060f 100644 --- a/contacts/src/main/java/de/mm20/launcher2/contacts/ContactRepository.kt +++ b/contacts/src/main/java/de/mm20/launcher2/contacts/ContactRepository.kt @@ -9,12 +9,15 @@ import de.mm20.launcher2.search.data.Contact import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.withContext -class ContactRepository private constructor(val context: Context) : BaseSearchableRepository() { +class ContactRepository( + val context: Context, + hiddenItemsRepository: HiddenItemsRepository +) : BaseSearchableRepository() { val contacts = MediatorLiveData?>() private val allContacts = MutableLiveData?>(emptyList()) - private val hiddenItemKeys = HiddenItemsRepository.getInstance(context).hiddenItemsKeys + private val hiddenItemKeys = hiddenItemsRepository.hiddenItemsKeys init { contacts.addSource(hiddenItemKeys) { keys -> @@ -35,13 +38,4 @@ class ContactRepository private constructor(val context: Context) : BaseSearchab } allContacts.value = results } - - companion object { - private lateinit var instance: ContactRepository - - fun getInstance(context: Context): ContactRepository { - if (!::instance.isInitialized) instance = ContactRepository(context.applicationContext) - return instance - } - } } \ No newline at end of file diff --git a/contacts/src/main/java/de/mm20/launcher2/contacts/ContactSerialization.kt b/contacts/src/main/java/de/mm20/launcher2/contacts/ContactSerialization.kt new file mode 100644 index 00000000..90da5ee3 --- /dev/null +++ b/contacts/src/main/java/de/mm20/launcher2/contacts/ContactSerialization.kt @@ -0,0 +1,51 @@ +package de.mm20.launcher2.contacts + +import android.Manifest +import android.content.Context +import android.content.pm.PackageManager +import android.provider.ContactsContract +import androidx.core.content.ContextCompat +import de.mm20.launcher2.ktx.jsonObjectOf +import de.mm20.launcher2.search.SearchableDeserializer +import de.mm20.launcher2.search.SearchableSerializer +import de.mm20.launcher2.search.data.Contact +import de.mm20.launcher2.search.data.Searchable +import org.json.JSONObject + +class ContactSerializer : SearchableSerializer { + override fun serialize(searchable: Searchable): String { + searchable as Contact + return jsonObjectOf( + "id" to searchable.id + ).toString() + } + + override val typePrefix: String + get() = "contact" +} + +class ContactDeserializer(val context: Context) : SearchableDeserializer { + override fun deserialize(serialized: String): Searchable? { + if (ContextCompat.checkSelfPermission( + context, + Manifest.permission.READ_CONTACTS + ) != PackageManager.PERMISSION_GRANTED + ) return null + val id = JSONObject(serialized).getLong("id") + val rawContactsCursor = context.contentResolver.query( + ContactsContract.RawContacts.CONTENT_URI, + arrayOf(ContactsContract.RawContacts._ID), + "${ContactsContract.RawContacts.CONTACT_ID} = ?", + arrayOf(id.toString()), + null + ) ?: return null + val rawContacts = mutableSetOf() + while (rawContactsCursor.moveToNext()) { + rawContacts.add(rawContactsCursor.getLong(0)) + } + rawContactsCursor.close() + if (rawContacts.isEmpty()) return null + + return Contact.contactById(context, id, rawContacts) + } +} \ No newline at end of file diff --git a/contacts/src/main/java/de/mm20/launcher2/contacts/ContactViewModel.kt b/contacts/src/main/java/de/mm20/launcher2/contacts/ContactViewModel.kt index 72d35fa8..7154d28e 100644 --- a/contacts/src/main/java/de/mm20/launcher2/contacts/ContactViewModel.kt +++ b/contacts/src/main/java/de/mm20/launcher2/contacts/ContactViewModel.kt @@ -1,10 +1,11 @@ package de.mm20.launcher2.contacts -import android.app.Application -import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel import de.mm20.launcher2.search.data.Contact -class ContactViewModel(app: Application) : AndroidViewModel(app) { - val contacts: LiveData?> = ContactRepository.getInstance(app).contacts +class ContactViewModel( + contactRepository: ContactRepository +) : ViewModel() { + val contacts: LiveData?> = contactRepository.contacts } \ No newline at end of file diff --git a/contacts/src/main/java/de/mm20/launcher2/contacts/Module.kt b/contacts/src/main/java/de/mm20/launcher2/contacts/Module.kt new file mode 100644 index 00000000..20661c31 --- /dev/null +++ b/contacts/src/main/java/de/mm20/launcher2/contacts/Module.kt @@ -0,0 +1,10 @@ +package de.mm20.launcher2.contacts + +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val contactsModule = module { + single { ContactRepository(androidContext(), get()) } + viewModel { ContactViewModel(get()) } +} \ No newline at end of file diff --git a/contacts/src/main/java/de/mm20/launcher2/search/data/Contact.kt b/contacts/src/main/java/de/mm20/launcher2/search/data/Contact.kt index d1637fc8..dad9e930 100644 --- a/contacts/src/main/java/de/mm20/launcher2/search/data/Contact.kt +++ b/contacts/src/main/java/de/mm20/launcher2/search/data/Contact.kt @@ -39,12 +39,6 @@ class Contact( return phones.union(emails).joinToString(separator = ", ") } - override fun serialize(): String { - return jsonObjectOf( - "id" to id - ).toString() - } - override fun getPlaceholderIcon(context: Context): LauncherIcon { val iconText = if (firstName.isNotEmpty()) firstName[0].toString() else "" + if (lastName.isNotEmpty()) lastName[0].toString() else "" return LauncherIcon( @@ -96,7 +90,7 @@ class Contact( return results.sortedBy { it } } - private fun contactById(context: Context, id: Long, rawIds: Set): Contact? { + internal fun contactById(context: Context, id: Long, rawIds: Set): Contact? { val s = "(" + rawIds.joinToString(separator = " OR ", transform = { "${ContactsContract.Data.RAW_CONTACT_ID} = $it" }) + ")" + " AND (${ContactsContract.Data.MIMETYPE} = \"${ContactsContract.CommonDataKinds.Email.CONTENT_ITEM_TYPE}\"" + @@ -187,24 +181,5 @@ class Contact( ) } - fun deserialize(context: Context, serialized: String): Contact? { - if (ContextCompat.checkSelfPermission(context, Manifest.permission.READ_CONTACTS) != PackageManager.PERMISSION_GRANTED) return null - val id = JSONObject(serialized).getLong("id") - val rawContactsCursor = context.contentResolver.query( - ContactsContract.RawContacts.CONTENT_URI, - arrayOf(ContactsContract.RawContacts._ID), - "${ContactsContract.RawContacts.CONTACT_ID} = ?", - arrayOf(id.toString()), - null - ) ?: return null - val rawContacts = mutableSetOf() - while (rawContactsCursor.moveToNext()) { - rawContacts.add(rawContactsCursor.getLong(0)) - } - rawContactsCursor.close() - if (rawContacts.isEmpty()) return null - - return contactById(context, id, rawContacts) - } } } \ No newline at end of file diff --git a/currencies/src/main/java/de/mm20/launcher2/currencies/CurrencyRepository.kt b/currencies/src/main/java/de/mm20/launcher2/currencies/CurrencyRepository.kt index 864ac1d3..d442c970 100644 --- a/currencies/src/main/java/de/mm20/launcher2/currencies/CurrencyRepository.kt +++ b/currencies/src/main/java/de/mm20/launcher2/currencies/CurrencyRepository.kt @@ -98,12 +98,4 @@ class CurrencyRepository(val context: Context) { AppDatabase.getInstance(context).currencyDao().getLastUpdate(symbol) } } - - companion object { - private lateinit var instance: CurrencyRepository - fun getInstance(context: Context): CurrencyRepository { - if (!::instance.isInitialized) instance = CurrencyRepository(context.applicationContext) - return instance - } - } } \ No newline at end of file diff --git a/favorites/build.gradle.kts b/favorites/build.gradle.kts index 88c08a8c..b354df0c 100644 --- a/favorites/build.gradle.kts +++ b/favorites/build.gradle.kts @@ -40,6 +40,8 @@ dependencies { implementation(libs.androidx.core) implementation(libs.androidx.appcompat) + implementation(libs.koin.android) + implementation(project(":search")) implementation(project(":calendar")) implementation(project(":database")) diff --git a/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesItem.kt b/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesItem.kt index fa5914b4..8dd6444f 100644 --- a/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesItem.kt +++ b/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesItem.kt @@ -2,34 +2,38 @@ package de.mm20.launcher2.favorites import android.content.Context import de.mm20.launcher2.database.entities.FavoritesItemEntity +import de.mm20.launcher2.search.SearchableSerializer import de.mm20.launcher2.search.data.Searchable +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject +import org.koin.core.parameter.parametersOf data class FavoritesItem( - val key: String, - /** - * null if searchable could not be deserialized (i.e. the app has been uninstalled) - */ - val searchable: Searchable?, - var launchCount: Int, - var pinPosition: Int, - var hidden: Boolean -){ - constructor(context: Context, entity: FavoritesItemEntity) : this( - key = entity.key, - searchable = SearchableDeserializer(context).deserialize(entity.serializedSearchable), - launchCount = entity.launchCount, - pinPosition = entity.pinPosition, - hidden = entity.hidden - ) - + val key: String, + /** + * null if searchable could not be deserialized (i.e. the app has been uninstalled) + */ + val searchable: Searchable?, + var launchCount: Int, + var pinPosition: Int, + var hidden: Boolean +) : KoinComponent { + private val serializer: SearchableSerializer by inject { parametersOf(searchable) } fun toDatabaseEntity(): FavoritesItemEntity { + return FavoritesItemEntity( - key = key, - serializedSearchable = searchable?.let { "${SearchableDeserializer.getTypePrefix(it)}#${it.serialize()}" } ?: "", - hidden = hidden, - pinPosition = pinPosition, - launchCount = launchCount + key = key, + serializedSearchable = searchable?.let { + "${serializer.typePrefix}#${ + serializer.serialize( + it + ) + }" + } ?: "", + hidden = hidden, + pinPosition = pinPosition, + launchCount = launchCount ) } } \ No newline at end of file diff --git a/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesRepository.kt b/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesRepository.kt index 0bd8861c..a3781c74 100644 --- a/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesRepository.kt +++ b/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesRepository.kt @@ -10,13 +10,16 @@ import de.mm20.launcher2.database.entities.FavoritesItemEntity import de.mm20.launcher2.ktx.ceilToInt import de.mm20.launcher2.preferences.LauncherPreferences import de.mm20.launcher2.search.BaseSearchableRepository +import de.mm20.launcher2.search.SearchableDeserializer import de.mm20.launcher2.search.data.CalendarEvent import de.mm20.launcher2.search.data.Searchable import kotlinx.coroutines.* +import org.koin.core.component.inject +import org.koin.core.parameter.parametersOf import kotlin.math.max import kotlin.math.min -class FavoritesRepository private constructor(private val context: Context) : BaseSearchableRepository() { +class FavoritesRepository(private val context: Context) : BaseSearchableRepository() { private val scope = CoroutineScope(Job() + Dispatchers.Main) @@ -30,6 +33,17 @@ class FavoritesRepository private constructor(private val context: Context) : Ba val pinnedCalendarEvents = MediatorLiveData>() + private fun fromDatabaseEntity(entity: FavoritesItemEntity): FavoritesItem { + val deserializer: SearchableDeserializer by inject { parametersOf(entity.serializedSearchable) } + return FavoritesItem( + key = entity.key, + searchable = deserializer.deserialize(entity.serializedSearchable.substringAfter("#")), + launchCount = entity.launchCount, + pinPosition = entity.pinPosition, + hidden = entity.hidden + ) + } + private val reloadFavorites: (String) -> Unit = { scope.launch { if(!LauncherPreferences.instance.searchShowFavorites) { @@ -41,7 +55,7 @@ class FavoritesRepository private constructor(private val context: Context) : Ba val dao = AppDatabase.getInstance(context).searchDao() val favItems = pinnedFavorites.value ?: emptyList() favs.addAll(favItems.mapNotNull { - val item = FavoritesItem(context, it) + val item = fromDatabaseEntity(it) if (item.searchable == null) { dao.deleteByKey(item.key) } @@ -52,7 +66,7 @@ class FavoritesRepository private constructor(private val context: Context) : Ba if(favItems.size < columns) favCount += columns val autoFavs = dao.getAutoFavorites(favCount - favs.size) favs.addAll(autoFavs.mapNotNull { - val item = FavoritesItem(context, it) + val item = fromDatabaseEntity(it) if (item.searchable == null) { dao.deleteByKey(item.key) } @@ -68,7 +82,7 @@ class FavoritesRepository private constructor(private val context: Context) : Ba init { val hidden = AppDatabase.getInstance(context).searchDao().getHiddenItems() hiddenItems.addSource(hidden) { h -> - hiddenItems.value = h.mapNotNull { FavoritesItem(context, it).searchable } + hiddenItems.value = h.mapNotNull { fromDatabaseEntity(it).searchable } } favorites.addSource(pinnedFavorites) { reloadFavorites("") @@ -77,7 +91,7 @@ class FavoritesRepository private constructor(private val context: Context) : Ba scope.launch { val dao = AppDatabase.getInstance(context).searchDao() pinnedCalendarEvents.value = it.filter { it.key.startsWith("calendar://") }.mapNotNull { - val item = FavoritesItem(context, it) + val item = fromDatabaseEntity(it) if (item.searchable == null) { withContext(Dispatchers.IO) { dao.deleteByKey(item.key) } } @@ -187,7 +201,7 @@ class FavoritesRepository private constructor(private val context: Context) : Ba suspend fun getAllFavoriteItems(): List { return withContext(Dispatchers.IO) { AppDatabase.getInstance(context).searchDao().getAllFavoriteItems().mapNotNull { - FavoritesItem(context, it).takeIf { it.searchable != null } + fromDatabaseEntity(it).takeIf { it.searchable != null } } } } @@ -208,11 +222,4 @@ class FavoritesRepository private constructor(private val context: Context) : Ba return favs } - companion object { - private lateinit var instance: FavoritesRepository - fun getInstance(context: Context): FavoritesRepository { - if (!::instance.isInitialized) instance = FavoritesRepository(context.applicationContext) - return instance - } - } } \ No newline at end of file diff --git a/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesViewModel.kt b/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesViewModel.kt index 18adeb51..7a3b471e 100644 --- a/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesViewModel.kt +++ b/favorites/src/main/java/de/mm20/launcher2/favorites/FavoritesViewModel.kt @@ -1,58 +1,54 @@ package de.mm20.launcher2.favorites -import android.app.Application -import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData -import androidx.lifecycle.MediatorLiveData +import androidx.lifecycle.ViewModel import de.mm20.launcher2.search.data.CalendarEvent import de.mm20.launcher2.search.data.Searchable -import kotlinx.coroutines.Dispatchers -import kotlinx.coroutines.withContext -class FavoritesViewModel(app: Application) : AndroidViewModel(app) { - - val repository = FavoritesRepository.getInstance(app) +class FavoritesViewModel( + private val favoritesRepository: FavoritesRepository +) : ViewModel() { fun getTopFavorites(count: Int): LiveData> { - return repository.getTopFavorites(count) + return favoritesRepository.getTopFavorites(count) } fun getFavorites(columns: Int): LiveData> { - return repository.getFavorites(columns) + return favoritesRepository.getFavorites(columns) } fun pinItem(searchable: Searchable) { - repository.pinItem(searchable) + favoritesRepository.pinItem(searchable) } fun unpinItem(searchable: Searchable) { - repository.unpinItem(searchable) + favoritesRepository.unpinItem(searchable) } fun isPinned(searchable: Searchable): LiveData { - return repository.isPinned(searchable) + return favoritesRepository.isPinned(searchable) } fun isHidden(searchable: Searchable): LiveData { - return repository.isHidden(searchable) + return favoritesRepository.isHidden(searchable) } fun hideItem(searchable: Searchable) { - repository.hideItem(searchable) + favoritesRepository.hideItem(searchable) } fun unhideItem(searchable: Searchable) { - repository.unhideItem(searchable) + favoritesRepository.unhideItem(searchable) } suspend fun getAllFavoriteItems(): List { - return repository.getAllFavoriteItems() + return favoritesRepository.getAllFavoriteItems() } fun saveFavorites(favorites: MutableList) { - repository.saveFavorites(favorites) + favoritesRepository.saveFavorites(favorites) } - val hiddenItems: LiveData> = repository.hiddenItems - val pinnedCalendarEvents: LiveData> = repository.pinnedCalendarEvents + val hiddenItems: LiveData> = this.favoritesRepository.hiddenItems + val pinnedCalendarEvents: LiveData> = this.favoritesRepository.pinnedCalendarEvents } \ No newline at end of file diff --git a/favorites/src/main/java/de/mm20/launcher2/favorites/Module.kt b/favorites/src/main/java/de/mm20/launcher2/favorites/Module.kt new file mode 100644 index 00000000..6c824dd3 --- /dev/null +++ b/favorites/src/main/java/de/mm20/launcher2/favorites/Module.kt @@ -0,0 +1,97 @@ +package de.mm20.launcher2.favorites + +import de.mm20.launcher2.calendar.CalendarEventDeserializer +import de.mm20.launcher2.calendar.CalendarEventSerializer +import de.mm20.launcher2.contacts.ContactDeserializer +import de.mm20.launcher2.contacts.ContactSerializer +import de.mm20.launcher2.files.* +import de.mm20.launcher2.search.NullDeserializer +import de.mm20.launcher2.search.data.* +import de.mm20.launcher2.websites.WebsiteDeserializer +import de.mm20.launcher2.websites.WebsiteSerializer +import de.mm20.launcher2.wikipedia.WikipediaDeserializer +import de.mm20.launcher2.wikipedia.WikipediaSerializer +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val favoritesModule = module { + factory { (searchable: Searchable) -> + if (searchable is LauncherApp) { + return@factory LauncherAppSerializer() + } + if (searchable is AppShortcut) { + return@factory AppShortcutSerializer() + } + if (searchable is CalendarEvent) { + return@factory CalendarEventSerializer() + } + if (searchable is Contact) { + return@factory ContactSerializer() + } + if (searchable is Wikipedia) { + return@factory WikipediaSerializer() + } + if (searchable is GDriveFile) { + return@factory GDriveFileSerializer() + } + if (searchable is OneDriveFile) { + return@factory OneDriveFileSerializer() + } + if (searchable is OwncloudFile) { + return@factory OwncloudFileSerializer() + } + if (searchable is NextcloudFile) { + return@factory NextcloudFileSerializer() + } + if (searchable is File) { + return@factory FileSerializer() + } + if (searchable is Website) { + return@factory WebsiteSerializer() + } + throw IllegalArgumentException("No known serializer exists for type ${searchable.javaClass.canonicalName}") + } + + factory { (serialized: String) -> + val type = serialized.substringBefore("#") + if (type == "app") { + return@factory LauncherAppDeserializer(androidContext()) + } + if (type == "shortcut") { + return@factory AppShortcutDeserializer(androidContext()) + } + if (type == "calendar") { + return@factory CalendarEventDeserializer(androidContext()) + } + if (type == "contact") { + return@factory ContactDeserializer(androidContext()) + } + if (type == "wikipedia") { + return@factory WikipediaDeserializer() + } + if (type == "gdrive") { + return@factory GDriveFileDeserializer() + } + if (type == "onedrive") { + return@factory OneDriveFileDeserializer() + } + if (type == "nextcloud") { + return@factory NextcloudFileDeserializer() + } + if (type == "owncloud") { + return@factory OwncloudFileDeserializer() + } + if (type == "file") { + return@factory FileDeserializer(androidContext()) + } + if (type == "website") { + return@factory WebsiteDeserializer() + } + return@factory NullDeserializer() + } + + single { FavoritesRepository(androidContext()) } + + viewModel { FavoritesViewModel(get()) } +} \ No newline at end of file diff --git a/favorites/src/main/java/de/mm20/launcher2/favorites/SearchableDeserializer.kt b/favorites/src/main/java/de/mm20/launcher2/favorites/SearchableDeserializer.kt deleted file mode 100644 index 1363003c..00000000 --- a/favorites/src/main/java/de/mm20/launcher2/favorites/SearchableDeserializer.kt +++ /dev/null @@ -1,47 +0,0 @@ -package de.mm20.launcher2.favorites - -import android.content.Context -import android.util.Log -import de.mm20.launcher2.search.data.* - -class SearchableDeserializer(val context: Context) { - fun deserialize(serialized: String?): Searchable? { - val type = serialized?.substringBefore("#") ?: return null - val data = serialized.substringAfter("#") - return when (type) { - "app" -> LauncherApp.deserialize(context, data) - "shortcut" -> AppShortcut.deserialize(context, data) - "calculator" -> null - "calendar" -> CalendarEvent.deserialize(context, data) - "contact" -> Contact.deserialize(context, data) - "gdrive" -> GDriveFile.deserialize(data) - "owncloud" -> OwncloudFile.deserialize(data) - "nextcloud" -> NextcloudFile.deserialize(data) - "file" -> File.deserialize(context, data) - "onedrive" -> OneDriveFile.deserialize(data) - "websearch" -> null - "website" -> Website.deserialize(data) - "wikipedia" -> Wikipedia.deserialize(data) - else -> null - } - } - - companion object { - fun getTypePrefix(searchable: Searchable): String { - return when(searchable) { - is Application -> "app" - is AppShortcut -> "shortcut" - is CalendarEvent -> "calendar" - is Contact -> "contact" - is GDriveFile -> "gdrive" - is OneDriveFile -> "onedrive" - is NextcloudFile -> "nextcloud" - is OwncloudFile -> "owncloud" - is File -> "file" - is Website -> "website" - is Wikipedia -> "wikipedia" - else -> "" - } - } - } -} diff --git a/files/build.gradle.kts b/files/build.gradle.kts index f0e66d51..a1635bc5 100644 --- a/files/build.gradle.kts +++ b/files/build.gradle.kts @@ -43,6 +43,8 @@ dependencies { implementation(libs.bundles.androidx.lifecycle) + implementation(libs.koin.android) + implementation(project(":search")) implementation(project(":hiddenitems")) implementation(project(":preferences")) diff --git a/files/src/main/java/de/mm20/launcher2/files/FileSerialization.kt b/files/src/main/java/de/mm20/launcher2/files/FileSerialization.kt new file mode 100644 index 00000000..bde44d72 --- /dev/null +++ b/files/src/main/java/de/mm20/launcher2/files/FileSerialization.kt @@ -0,0 +1,291 @@ +package de.mm20.launcher2.files + +import android.content.Context +import android.provider.MediaStore +import androidx.core.database.getStringOrNull +import de.mm20.launcher2.ktx.jsonObjectOf +import de.mm20.launcher2.permissions.PermissionsManager +import de.mm20.launcher2.search.SearchableDeserializer +import de.mm20.launcher2.search.SearchableSerializer +import de.mm20.launcher2.search.data.* +import org.json.JSONObject + +class FileSerializer : SearchableSerializer { + override fun serialize(searchable: Searchable): String { + searchable as File + return jsonObjectOf( + "id" to searchable.id + ).toString() + } + + override val typePrefix: String + get() = "file" +} + +class FileDeserializer( + val context: Context +) : SearchableDeserializer { + override fun deserialize(serialized: String): Searchable? { + if (!PermissionsManager.checkPermission( + context, + PermissionsManager.EXTERNAL_STORAGE + ) + ) return null + val json = JSONObject(serialized) + val uri = MediaStore.Files.getContentUri("external") + val proj = arrayOf( + MediaStore.Files.FileColumns._ID, + MediaStore.Files.FileColumns.SIZE, + MediaStore.Files.FileColumns.DATA, + MediaStore.Files.FileColumns.MIME_TYPE + ) + val sel = "${MediaStore.Files.FileColumns._ID} = ?" + val selArgs = arrayOf(json.getLong("id").toString()) + val cursor = context.contentResolver.query(uri, proj, sel, selArgs, null) ?: return null + if (cursor.moveToNext()) { + val path = cursor.getString(2) + if (!java.io.File(path).exists()) return null + val directory = java.io.File(path).isDirectory + val id = cursor.getLong(0) + val mimeType = cursor.getStringOrNull(3) + ?: if (directory) "inode/directory" else File.getMimetypeByFileExtension( + path.substringAfterLast( + '.' + ) + ) + val size = cursor.getLong(1) + cursor.close() + return File( + path = path, + mimeType = mimeType, + size = size, + isDirectory = directory, + id = id, + metaData = File.getMetaData(context, mimeType, path) + ) + } + cursor.close() + return null + } +} + +class GDriveFileSerializer : SearchableSerializer { + override fun serialize(searchable: Searchable): String { + searchable as GDriveFile + return jsonObjectOf( + "id" to searchable.fileId, + "label" to searchable.label, + "path" to searchable.path, + "mimeType" to searchable.mimeType, + "size" to searchable.size, + "directory" to searchable.isDirectory, + "color" to searchable.directoryColor, + "uri" to searchable.viewUri + ).apply { + for ((k, v) in searchable.metaData) { + put( + when (k) { + R.string.file_meta_owner -> "owner" + R.string.file_meta_dimensions -> "dimensions" + else -> "other" + }, v + ) + } + }.toString() + } + + override val typePrefix: String + get() = "gdrive" +} + +class GDriveFileDeserializer : SearchableDeserializer { + override fun deserialize(serialized: String): Searchable? { + val json = JSONObject(serialized) + val id = json.getString("id") + val label = json.getString("label") + val path = json.getString("path") + val mimeType = json.getString("mimeType") + val size = json.getLong("size") + val directory = json.getBoolean("directory") + val color = json.optString("color") + val uri = json.getString("uri") + val owner = json.optString("owner") + val dimensions = json.optString("dimensions") + val metaData = mutableListOf>() + owner.takeIf { it.isNotEmpty() }?.let { metaData.add(R.string.file_meta_owner to it) } + dimensions.takeIf { it.isNotEmpty() } + ?.let { metaData.add(R.string.file_meta_dimensions to it) } + return GDriveFile( + fileId = id, + label = label, + path = path, + mimeType = mimeType, + size = size, + directoryColor = color, + isDirectory = directory, + viewUri = uri, + metaData = metaData + ) + } +} + +class OneDriveFileSerializer : SearchableSerializer { + override fun serialize(searchable: Searchable): String { + searchable as OneDriveFile + return jsonObjectOf( + "id" to searchable.fileId, + "label" to searchable.label, + "mimeType" to searchable.mimeType, + "size" to searchable.size, + "directory" to searchable.isDirectory, + "webUrl" to searchable.webUrl + ).apply { + for ((k, v) in searchable.metaData) { + put( + when (k) { + R.string.file_meta_owner -> "owner" + R.string.file_meta_dimensions -> "dimensions" + else -> "other" + }, v + ) + } + }.toString() + } + + override val typePrefix: String + get() = "onedrive" +} + +class OneDriveFileDeserializer : SearchableDeserializer { + override fun deserialize(serialized: String): Searchable? { + val json = JSONObject(serialized) + val fileId = json.getString("id") + val label = json.getString("label") + val mimeType = json.getString("mimeType") + val size = json.getLong("size") + val isDirectory = json.getBoolean("directory") + val webUrl = json.getString("webUrl") + val owner = json.optString("owner") + val dimensions = json.optString("dimensions") + val metaData = mutableListOf>() + owner.takeIf { it.isNotEmpty() }?.let { metaData.add(R.string.file_meta_owner to it) } + dimensions.takeIf { it.isNotEmpty() } + ?.let { metaData.add(R.string.file_meta_dimensions to it) } + return OneDriveFile( + fileId = fileId, + label = label, + path = "", + mimeType = mimeType, + size = size, + isDirectory = isDirectory, + metaData = metaData, + webUrl = webUrl + ) + } +} + +class NextcloudFileSerializer : SearchableSerializer { + override fun serialize(searchable: Searchable): String { + searchable as NextcloudFile + return jsonObjectOf( + "id" to searchable.id, + "label" to searchable.label, + "path" to searchable.path, + "mimeType" to searchable.mimeType, + "size" to searchable.size, + "isDirectory" to searchable.isDirectory, + "server" to searchable.server + ).apply { + for ((k, v) in searchable.metaData) { + put( + when (k) { + R.string.file_meta_owner -> "owner" + else -> "other" + }, v + ) + } + }.toString() + } + + override val typePrefix: String + get() = "nextcloud" +} + +class NextcloudFileDeserializer : SearchableDeserializer { + override fun deserialize(serialized: String): Searchable? { + val json = JSONObject(serialized) + val id = json.getLong("id") + val label = json.getString("label") + val path = json.getString("path") + val mimeType = json.getString("mimeType") + val size = json.getLong("size") + val isDirectory = json.getBoolean("isDirectory") + val server = json.getString("server") + val owner = json.optString("owner").takeIf { it.isNotEmpty() } + + return NextcloudFile( + fileId = id, + label = label, + path = path, + mimeType = mimeType, + size = size, + isDirectory = isDirectory, + server = server, + metaData = owner?.let { listOf(R.string.file_meta_owner to it) } ?: emptyList() + + ) + } +} + +class OwncloudFileSerializer : SearchableSerializer { + override fun serialize(searchable: Searchable): String { + searchable as OwncloudFile + return jsonObjectOf( + "id" to searchable.id, + "label" to searchable.label, + "path" to searchable.path, + "mimeType" to searchable.mimeType, + "size" to searchable.size, + "isDirectory" to searchable.isDirectory, + "server" to searchable.server + ).apply { + for ((k, v) in searchable.metaData) { + put( + when (k) { + R.string.file_meta_owner -> "owner" + else -> "other" + }, v + ) + } + }.toString() + } + + override val typePrefix: String + get() = "owncloud" +} + +class OwncloudFileDeserializer : SearchableDeserializer { + override fun deserialize(serialized: String): Searchable? { + val json = JSONObject(serialized) + val id = json.getLong("id") + val label = json.getString("label") + val path = json.getString("path") + val mimeType = json.getString("mimeType") + val size = json.getLong("size") + val isDirectory = json.getBoolean("isDirectory") + val server = json.getString("server") + val owner = json.optString("owner").takeIf { it.isNotEmpty() } + + return OwncloudFile( + fileId = id, + label = label, + path = path, + mimeType = mimeType, + size = size, + isDirectory = isDirectory, + server = server, + metaData = owner?.let { listOf(R.string.file_meta_owner to it) } ?: emptyList() + + ) + } +} \ No newline at end of file diff --git a/files/src/main/java/de/mm20/launcher2/files/FilesRepository.kt b/files/src/main/java/de/mm20/launcher2/files/FilesRepository.kt index 56bc12c8..38c4953b 100644 --- a/files/src/main/java/de/mm20/launcher2/files/FilesRepository.kt +++ b/files/src/main/java/de/mm20/launcher2/files/FilesRepository.kt @@ -10,12 +10,15 @@ import de.mm20.launcher2.search.BaseSearchableRepository import de.mm20.launcher2.search.data.* import kotlinx.coroutines.* -class FilesRepository private constructor(val context: Context) : BaseSearchableRepository() { +class FilesRepository( + val context: Context, + hiddenItemsRepository: HiddenItemsRepository +) : BaseSearchableRepository() { val files = MediatorLiveData?>() private val allFiles = MutableLiveData?>(emptyList()) - private val hiddenItemKeys = HiddenItemsRepository.getInstance(context).hiddenItemsKeys + private val hiddenItemKeys = hiddenItemsRepository.hiddenItemsKeys private val nextcloudClient by lazy { NextcloudApiHelper(context) @@ -46,10 +49,10 @@ class FilesRepository private constructor(val context: Context) : BaseSearchable val cloudFiles = withContext(Dispatchers.IO) { delay(300) listOf( - async { OneDriveFile.search(context, query) }, - async { GDriveFile.search(context, query) }, - async { NextcloudFile.search(context, query, nextcloudClient) }, - async { OwncloudFile.search(context, query, owncloudClient) } + async { OneDriveFile.search(context, query) }, + async { GDriveFile.search(context, query) }, + async { NextcloudFile.search(context, query, nextcloudClient) }, + async { OwncloudFile.search(context, query, owncloudClient) } ).awaitAll().flatten() } yield() @@ -59,12 +62,4 @@ class FilesRepository private constructor(val context: Context) : BaseSearchable fun removeFile(file: File) { allFiles.value = allFiles.value?.filter { it != file } } - - companion object { - private lateinit var instance: FilesRepository - fun getInstance(context: Context): FilesRepository { - if (!::instance.isInitialized) instance = FilesRepository(context.applicationContext) - return instance - } - } } \ No newline at end of file diff --git a/files/src/main/java/de/mm20/launcher2/files/FilesViewModel.kt b/files/src/main/java/de/mm20/launcher2/files/FilesViewModel.kt index ef3e91b9..a2931b80 100644 --- a/files/src/main/java/de/mm20/launcher2/files/FilesViewModel.kt +++ b/files/src/main/java/de/mm20/launcher2/files/FilesViewModel.kt @@ -1,16 +1,16 @@ package de.mm20.launcher2.files -import android.app.Application -import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel import de.mm20.launcher2.search.data.File -class FilesViewModel(app: Application): AndroidViewModel(app) { +class FilesViewModel( + private val filesRepository: FilesRepository +): ViewModel() { - private val repository = FilesRepository.getInstance(app) - val files = repository.files + val files = filesRepository.files fun removeFile(file: File) { - repository.removeFile(file) + filesRepository.removeFile(file) } } \ No newline at end of file diff --git a/files/src/main/java/de/mm20/launcher2/files/Module.kt b/files/src/main/java/de/mm20/launcher2/files/Module.kt new file mode 100644 index 00000000..1d0b446c --- /dev/null +++ b/files/src/main/java/de/mm20/launcher2/files/Module.kt @@ -0,0 +1,10 @@ +package de.mm20.launcher2.files + +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val filesModule = module { + single { FilesRepository(androidContext(), get()) } + viewModel { FilesViewModel(get()) } +} \ No newline at end of file diff --git a/files/src/main/java/de/mm20/launcher2/search/data/File.kt b/files/src/main/java/de/mm20/launcher2/search/data/File.kt index cc70ef43..49dd3043 100644 --- a/files/src/main/java/de/mm20/launcher2/search/data/File.kt +++ b/files/src/main/java/de/mm20/launcher2/search/data/File.kt @@ -158,12 +158,6 @@ open class File( .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_GRANT_READ_URI_PERMISSION) } - override fun serialize(): String { - return jsonObjectOf( - "id" to id - ).toString() - } - fun getFileType(context: Context): String { if (isDirectory) return context.getString(R.string.file_type_directory) val resource = when (mimeType) { @@ -261,7 +255,7 @@ open class File( return results.sortedBy { it } } - private fun getMimetypeByFileExtension(extension: String): String { + internal fun getMimetypeByFileExtension(extension: String): String { return when (extension) { "apk" -> "application/vnd.android.package-archive" "zip" -> "application/zip" @@ -287,7 +281,7 @@ open class File( } - private fun getMetaData(context: Context, mimeType: String, path: String): List> { + internal fun getMetaData(context: Context, mimeType: String, path: String): List> { val metaData = mutableListOf>() when { mimeType.startsWith("audio/") -> { @@ -365,37 +359,5 @@ open class File( } return metaData } - - fun deserialize(context: Context, serialized: String): File? { - if (!PermissionsManager.checkPermission(context, PermissionsManager.EXTERNAL_STORAGE)) return null - val json = JSONObject(serialized) - val uri = MediaStore.Files.getContentUri("external") - val proj = arrayOf(MediaStore.Files.FileColumns._ID, - MediaStore.Files.FileColumns.SIZE, - MediaStore.Files.FileColumns.DATA, - MediaStore.Files.FileColumns.MIME_TYPE) - val sel = "${MediaStore.Files.FileColumns._ID} = ?" - val selArgs = arrayOf(json.getLong("id").toString()) - val cursor = context.contentResolver.query(uri, proj, sel, selArgs, null) ?: return null - if (cursor.moveToNext()) { - val path = cursor.getString(2) - if (!JavaIOFile(path).exists()) return null - val directory = JavaIOFile(path).isDirectory - val id = cursor.getLong(0) - val mimeType = cursor.getStringOrNull(3) - ?: if (directory) "inode/directory" else getMimetypeByFileExtension(path.substringAfterLast('.')) - val size = cursor.getLong(1) - cursor.close() - return File( - path = path, - mimeType = mimeType, - size = size, - isDirectory = directory, - id = id, - metaData = getMetaData(context, mimeType, path)) - } - cursor.close() - return null - } } } \ No newline at end of file diff --git a/files/src/main/java/de/mm20/launcher2/search/data/GDriveFile.kt b/files/src/main/java/de/mm20/launcher2/search/data/GDriveFile.kt index c3b079af..0db7b7a4 100644 --- a/files/src/main/java/de/mm20/launcher2/search/data/GDriveFile.kt +++ b/files/src/main/java/de/mm20/launcher2/search/data/GDriveFile.kt @@ -8,20 +8,18 @@ import de.mm20.launcher2.gservices.DriveFileMeta import de.mm20.launcher2.gservices.GoogleApiHelper import de.mm20.launcher2.helper.NetworkUtils import de.mm20.launcher2.icons.LauncherIcon -import de.mm20.launcher2.ktx.jsonObjectOf import de.mm20.launcher2.preferences.LauncherPreferences -import org.json.JSONObject class GDriveFile( - val fileId: String, - override val label: String, - path: String, - mimeType: String, - size: Long, - isDirectory: Boolean, - metaData: List>, - val directoryColor: String?, - val viewUri: String + val fileId: String, + override val label: String, + path: String, + mimeType: String, + size: Long, + isDirectory: Boolean, + metaData: List>, + val directoryColor: String?, + val viewUri: String ) : File(0, path, mimeType, size, isDirectory, metaData) { override val key: String = "gdrive://$fileId" @@ -29,27 +27,6 @@ class GDriveFile( override val badgeKey: String get() = "gdrive://" - override fun serialize(): String { - return jsonObjectOf( - "id" to fileId, - "label" to label, - "path" to path, - "mimeType" to mimeType, - "size" to size, - "directory" to isDirectory, - "color" to directoryColor, - "uri" to viewUri - ).apply { - for ((k, v) in metaData) { - put(when (k) { - R.string.file_meta_owner -> "owner" - R.string.file_meta_dimensions -> "dimensions" - else -> "other" - }, v) - } - }.toString() - } - override val isStoredInCloud = true override fun getLaunchIntent(context: Context): Intent? { @@ -72,15 +49,15 @@ class GDriveFile( val driveFiles = GoogleApiHelper.getInstance(context).queryGDriveFiles(query) return driveFiles.map { GDriveFile( - fileId = it.fileId, - label = it.label, - size = it.size, - mimeType = it.mimeType, - isDirectory = it.isDirectory, - path = "", - directoryColor = it.directoryColor, - viewUri = it.viewUri, - metaData = getMetadata(it.metadata) + fileId = it.fileId, + label = it.label, + size = it.size, + mimeType = it.mimeType, + isDirectory = it.isDirectory, + path = "", + directoryColor = it.directoryColor, + viewUri = it.viewUri, + metaData = getMetadata(it.metadata) ) }.sorted() } @@ -94,33 +71,5 @@ class GDriveFile( if (width != null && height != null) metaData.add(R.string.file_meta_dimensions to "${width}x$height") return metaData } - - fun deserialize(serialized: String): GDriveFile? { - val json = JSONObject(serialized) - val id = json.getString("id") - val label = json.getString("label") - val path = json.getString("path") - val mimeType = json.getString("mimeType") - val size = json.getLong("size") - val directory = json.getBoolean("directory") - val color = json.optString("color") - val uri = json.getString("uri") - val owner = json.optString("owner") - val dimensions = json.optString("dimensions") - val metaData = mutableListOf>() - owner.takeIf { it.isNotEmpty() }?.let { metaData.add(R.string.file_meta_owner to it) } - dimensions.takeIf { it.isNotEmpty() }?.let { metaData.add(R.string.file_meta_dimensions to it) } - return GDriveFile( - fileId = id, - label = label, - path = path, - mimeType = mimeType, - size = size, - directoryColor = color, - isDirectory = directory, - viewUri = uri, - metaData = metaData - ) - } } } \ No newline at end of file diff --git a/files/src/main/java/de/mm20/launcher2/search/data/NextcloudFile.kt b/files/src/main/java/de/mm20/launcher2/search/data/NextcloudFile.kt index a42789b8..177418a5 100644 --- a/files/src/main/java/de/mm20/launcher2/search/data/NextcloudFile.kt +++ b/files/src/main/java/de/mm20/launcher2/search/data/NextcloudFile.kt @@ -34,25 +34,6 @@ class NextcloudFile( } } - override fun serialize(): String { - return jsonObjectOf( - "id" to id, - "label" to label, - "path" to path, - "mimeType" to mimeType, - "size" to size, - "isDirectory" to isDirectory, - "server" to server - ).apply { - for ((k, v) in metaData) { - put(when (k) { - R.string.file_meta_owner -> "owner" - else -> "other" - }, v) - } - }.toString() - } - companion object { suspend fun search(context: Context, query: String, nextcloudClient: NextcloudApiHelper) : List { if (!LauncherPreferences.instance.searchNextcloud) return emptyList() @@ -73,29 +54,5 @@ class NextcloudFile( } } - fun deserialize(serialized: String): NextcloudFile? { - val json = JSONObject(serialized) - val id = json.getLong("id") - val label = json.getString("label") - val path = json.getString("path") - val mimeType = json.getString("mimeType") - val size = json.getLong("size") - val isDirectory = json.getBoolean("isDirectory") - val server = json.getString("server") - val owner = json.optString("owner").takeIf { it.isNotEmpty() } - - return NextcloudFile( - fileId = id, - label = label, - path = path, - mimeType = mimeType, - size = size, - isDirectory = isDirectory, - server = server, - metaData = owner?.let { listOf(R.string.file_meta_owner to it) } ?: emptyList() - - ) - } - } } \ No newline at end of file diff --git a/files/src/main/java/de/mm20/launcher2/search/data/OneDriveFile.kt b/files/src/main/java/de/mm20/launcher2/search/data/OneDriveFile.kt index 4405c98c..b8d59582 100644 --- a/files/src/main/java/de/mm20/launcher2/search/data/OneDriveFile.kt +++ b/files/src/main/java/de/mm20/launcher2/search/data/OneDriveFile.kt @@ -6,10 +6,8 @@ import android.net.Uri import de.mm20.launcher2.msservices.DriveItem import de.mm20.launcher2.files.R import de.mm20.launcher2.msservices.MicrosoftGraphApiHelper -import de.mm20.launcher2.ktx.jsonObjectOf import de.mm20.launcher2.icons.LauncherIcon import de.mm20.launcher2.preferences.LauncherPreferences -import org.json.JSONObject class OneDriveFile( val fileId: String, @@ -39,25 +37,6 @@ class OneDriveFile( } } - override fun serialize(): String { - return jsonObjectOf( - "id" to fileId, - "label" to label, - "mimeType" to mimeType, - "size" to size, - "directory" to isDirectory, - "webUrl" to webUrl - ).apply { - for ((k, v) in metaData) { - put(when (k) { - R.string.file_meta_owner -> "owner" - R.string.file_meta_dimensions -> "dimensions" - else -> "other" - }, v) - } - }.toString() - } - companion object { suspend fun search(context: Context, query: String): List { if (query.length < 4) return emptyList() @@ -79,31 +58,6 @@ class OneDriveFile( return files.sorted() } - fun deserialize(serialized: String): OneDriveFile? { - val json = JSONObject(serialized) - val fileId = json.getString("id") - val label = json.getString("label") - val mimeType = json.getString("mimeType") - val size = json.getLong("size") - val isDirectory = json.getBoolean("directory") - val webUrl = json.getString("webUrl") - val owner = json.optString("owner") - val dimensions = json.optString("dimensions") - val metaData = mutableListOf>() - owner.takeIf { it.isNotEmpty() }?.let { metaData.add(R.string.file_meta_owner to it) } - dimensions.takeIf { it.isNotEmpty() }?.let { metaData.add(R.string.file_meta_dimensions to it) } - return OneDriveFile( - fileId = fileId, - label = label, - path = "", - mimeType = mimeType, - size = size, - isDirectory = isDirectory, - metaData = metaData, - webUrl = webUrl - ) - } - private fun getMetaData(driveItem: DriveItem): List> { val metaData = mutableListOf>() driveItem.meta.owner?.let { diff --git a/files/src/main/java/de/mm20/launcher2/search/data/OwncloudFile.kt b/files/src/main/java/de/mm20/launcher2/search/data/OwncloudFile.kt index a9c96111..8e47a2c6 100644 --- a/files/src/main/java/de/mm20/launcher2/search/data/OwncloudFile.kt +++ b/files/src/main/java/de/mm20/launcher2/search/data/OwncloudFile.kt @@ -34,25 +34,6 @@ class OwncloudFile( } } - override fun serialize(): String { - return jsonObjectOf( - "id" to id, - "label" to label, - "path" to path, - "mimeType" to mimeType, - "size" to size, - "isDirectory" to isDirectory, - "server" to server - ).apply { - for ((k, v) in metaData) { - put(when (k) { - R.string.file_meta_owner -> "owner" - else -> "other" - }, v) - } - }.toString() - } - companion object { suspend fun search(context: Context, query: String, owncloudClient: OwncloudClient) : List { if (!LauncherPreferences.instance.searchOwncloud) return emptyList() @@ -73,29 +54,5 @@ class OwncloudFile( } } - fun deserialize(serialized: String): OwncloudFile? { - val json = JSONObject(serialized) - val id = json.getLong("id") - val label = json.getString("label") - val path = json.getString("path") - val mimeType = json.getString("mimeType") - val size = json.getLong("size") - val isDirectory = json.getBoolean("isDirectory") - val server = json.getString("server") - val owner = json.optString("owner").takeIf { it.isNotEmpty() } - - return OwncloudFile( - fileId = id, - label = label, - path = path, - mimeType = mimeType, - size = size, - isDirectory = isDirectory, - server = server, - metaData = owner?.let { listOf(R.string.file_meta_owner to it) } ?: emptyList() - - ) - } - } } \ No newline at end of file diff --git a/hiddenitems/build.gradle.kts b/hiddenitems/build.gradle.kts index 2d9849de..f0083fc8 100644 --- a/hiddenitems/build.gradle.kts +++ b/hiddenitems/build.gradle.kts @@ -40,6 +40,8 @@ dependencies { implementation(libs.androidx.core) implementation(libs.androidx.appcompat) + implementation(libs.koin.android) + implementation(project(":database")) implementation(project(":search")) } \ No newline at end of file diff --git a/hiddenitems/src/main/java/de/mm20/launcher2/hiddenitems/HiddenItemsRepository.kt b/hiddenitems/src/main/java/de/mm20/launcher2/hiddenitems/HiddenItemsRepository.kt index 3fe5ef6e..995afad1 100644 --- a/hiddenitems/src/main/java/de/mm20/launcher2/hiddenitems/HiddenItemsRepository.kt +++ b/hiddenitems/src/main/java/de/mm20/launcher2/hiddenitems/HiddenItemsRepository.kt @@ -10,20 +10,11 @@ import de.mm20.launcher2.search.data.Searchable * A low level repository for hidden items. This can only be used to retrieve keys and to check * whether an item is hidden. To retrieve actual Searchable objects, use FavoritesRepository. */ -class HiddenItemsRepository private constructor(val context: Context) { +class HiddenItemsRepository(val context: Context) { val hiddenItemsKeys : LiveData> = AppDatabase.getInstance(context).searchDao().getHiddenItemKeys() fun isHidden(item: Searchable): LiveData { return AppDatabase.getInstance(context).searchDao().isHidden(item.key) } - - companion object { - private lateinit var instance: HiddenItemsRepository - - fun getInstance(context: Context): HiddenItemsRepository { - if(!Companion::instance.isInitialized) instance = HiddenItemsRepository(context.applicationContext) - return instance - } - } } \ No newline at end of file diff --git a/hiddenitems/src/main/java/de/mm20/launcher2/hiddenitems/HiddenItemsViewModel.kt b/hiddenitems/src/main/java/de/mm20/launcher2/hiddenitems/HiddenItemsViewModel.kt index e0b4c6cc..1beee155 100644 --- a/hiddenitems/src/main/java/de/mm20/launcher2/hiddenitems/HiddenItemsViewModel.kt +++ b/hiddenitems/src/main/java/de/mm20/launcher2/hiddenitems/HiddenItemsViewModel.kt @@ -1,9 +1,10 @@ package de.mm20.launcher2.hiddenitems -import android.app.Application -import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel -class HiddenItemsViewModel(app: Application): AndroidViewModel(app) { - val hiddenItemsKeys = HiddenItemsRepository.getInstance(app).hiddenItemsKeys +class HiddenItemsViewModel( + hiddenItemsRepository: HiddenItemsRepository +): ViewModel() { + val hiddenItemsKeys = hiddenItemsRepository.hiddenItemsKeys } \ No newline at end of file diff --git a/hiddenitems/src/main/java/de/mm20/launcher2/hiddenitems/Module.kt b/hiddenitems/src/main/java/de/mm20/launcher2/hiddenitems/Module.kt new file mode 100644 index 00000000..d1c23441 --- /dev/null +++ b/hiddenitems/src/main/java/de/mm20/launcher2/hiddenitems/Module.kt @@ -0,0 +1,10 @@ +package de.mm20.launcher2.hiddenitems + +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val hiddenItemsModule = module { + single { HiddenItemsRepository(androidContext()) } + viewModel { HiddenItemsViewModel(get()) } +} \ No newline at end of file diff --git a/icons/build.gradle.kts b/icons/build.gradle.kts index 7578f80d..92c00620 100644 --- a/icons/build.gradle.kts +++ b/icons/build.gradle.kts @@ -43,6 +43,8 @@ dependencies { implementation(libs.bundles.androidx.lifecycle) + implementation(libs.koin.android) + implementation(project(":database")) implementation(project(":preferences")) implementation(project(":ktx")) diff --git a/icons/src/main/java/de/mm20/launcher2/icons/CalendarDynamicLauncherIcon.kt b/icons/src/main/java/de/mm20/launcher2/icons/CalendarDynamicLauncherIcon.kt index b538f392..5583e991 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/CalendarDynamicLauncherIcon.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/CalendarDynamicLauncherIcon.kt @@ -30,11 +30,6 @@ class CalendarDynamicLauncherIcon( null ) { - init { - DynamicIconController.getInstance(context).registerIcon(this) - update(context) - } - var currentDay = 0 override fun update(context: Context) { val calendar = Calendar.getInstance() diff --git a/icons/src/main/java/de/mm20/launcher2/icons/ClockDynamicLauncherIcon.kt b/icons/src/main/java/de/mm20/launcher2/icons/ClockDynamicLauncherIcon.kt index 9aaec4c5..50fead08 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/ClockDynamicLauncherIcon.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/ClockDynamicLauncherIcon.kt @@ -7,6 +7,8 @@ import android.graphics.drawable.LayerDrawable import android.graphics.drawable.RotateDrawable import android.os.Build import androidx.annotation.RequiresApi +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import java.util.* import kotlin.math.roundToInt @@ -32,6 +34,7 @@ class ClockDynamicLauncherIcon( null ) { + init { foreground.also { it.setDrawable(secondLayer, ColorDrawable(0)) @@ -40,8 +43,6 @@ class ClockDynamicLauncherIcon( (it.getDrawable(minuteLayer) as? RotateDrawable)?.fromDegrees = 0f (it.getDrawable(minuteLayer) as? RotateDrawable)?.toDegrees = 360f } - DynamicIconController.getInstance(context).registerIcon(this) - update(context) } override fun update(context: Context) { diff --git a/icons/src/main/java/de/mm20/launcher2/icons/DynamicIconController.kt b/icons/src/main/java/de/mm20/launcher2/icons/DynamicIconController.kt index 31d23cb1..9f8b791b 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/DynamicIconController.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/DynamicIconController.kt @@ -52,15 +52,7 @@ class DynamicIconController(val context: Context): LifecycleObserver { } fun registerIcon(icon: DynamicLauncherIcon) { + icon.update(context) registeredIcons.add(WeakReference(icon)) } - - companion object { - private lateinit var instance: DynamicIconController - - fun getInstance(context: Context): DynamicIconController { - if(!::instance.isInitialized) instance = DynamicIconController(context.applicationContext) - return instance - } - } } \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/DynamicLauncherIcon.kt b/icons/src/main/java/de/mm20/launcher2/icons/DynamicLauncherIcon.kt index bfb0db60..4e3504e0 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/DynamicLauncherIcon.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/DynamicLauncherIcon.kt @@ -2,6 +2,8 @@ package de.mm20.launcher2.icons import android.content.Context import android.graphics.drawable.Drawable +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject abstract class DynamicLauncherIcon( foreground: Drawable, @@ -18,5 +20,6 @@ abstract class DynamicLauncherIcon( backgroundScale, autoGenerateBackgroundMode ) { + abstract fun update(context: Context) } \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt b/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt index 71eadcb1..3adbb15b 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/IconPackManager.kt @@ -14,14 +14,11 @@ import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.ColorDrawable import android.graphics.drawable.LayerDrawable import android.os.Build -import android.os.UserHandle -import android.util.DisplayMetrics import android.util.Log import androidx.core.content.res.ResourcesCompat import androidx.core.graphics.drawable.toBitmap import de.mm20.launcher2.crashreporter.CrashReporter import de.mm20.launcher2.database.AppDatabase -import de.mm20.launcher2.ktx.dp import de.mm20.launcher2.ktx.obtainTypedArrayOrNull import de.mm20.launcher2.ktx.randomElementOrNull import de.mm20.launcher2.preferences.IconShape @@ -35,18 +32,21 @@ import java.io.InputStreamReader import kotlin.math.roundToInt -class IconPackManager private constructor(val context: Context) { +class IconPackManager( + val context: Context, + val dynamicIconController: DynamicIconController +) { var selectedIconPack: String get() { return context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE) - .getString(KEY_ICON_PACK, "")!! + .getString(KEY_ICON_PACK, "")!! } set(value) { Log.d("MM20", "Selected icon pack: $value") context.getSharedPreferences(PREFERENCE_NAME, Context.MODE_PRIVATE) - .edit() - .putString(KEY_ICON_PACK, value) - .apply() + .edit() + .putString(KEY_ICON_PACK, value) + .apply() } @@ -59,7 +59,11 @@ class IconPackManager private constructor(val context: Context) { return getFromPack(context, activity, size) ?: generateIcon(context, activity, size) } - private fun getFromPack(context: Context, activity: LauncherActivityInfo, size: Int): LauncherIcon? { + private fun getFromPack( + context: Context, + activity: LauncherActivityInfo, + size: Int + ): LauncherIcon? { val res = try { context.packageManager.getResourcesForApplication(selectedIconPack) } catch (e: PackageManager.NameNotFoundException) { @@ -69,36 +73,42 @@ class IconPackManager private constructor(val context: Context) { val iconDao = AppDatabase.getInstance(context).iconDao() val component = ComponentName(activity.applicationInfo.packageName, activity.name) val icon = iconDao.getIcon(component.flattenToString(), selectedIconPack) - ?: return generateIcon(context, activity, size) + ?: return generateIcon(context, activity, size) if (icon.type == "calendar") { - return getIconPackCalendarIcon(context, icon.iconPack, icon.drawable ?: return null) + return getIconPackCalendarIcon(context, icon.iconPack, icon.drawable ?: return null)?.also { + dynamicIconController.registerIcon(it) + } } val drawableName = icon.drawable val resId = res.getIdentifier(drawableName, "drawable", selectedIconPack).takeIf { it != 0 } - ?: return generateIcon(context, activity, size) + ?: return generateIcon(context, activity, size) val drawable = ResourcesCompat.getDrawable(res, resId, context.theme) ?: return null return when { Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && drawable is AdaptiveIconDrawable -> { LauncherIcon( - foreground = drawable.foreground, - background = drawable.background, - foregroundScale = 1.5f, - backgroundScale = 1.5f + foreground = drawable.foreground, + background = drawable.background, + foregroundScale = 1.5f, + backgroundScale = 1.5f ) } else -> { LauncherIcon( - foreground = drawable, - foregroundScale = getScale(), - autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() + foreground = drawable, + foregroundScale = getScale(), + autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() ) } } } - private fun generateIcon(context: Context, activity: LauncherActivityInfo, size: Int): LauncherIcon? { + private fun generateIcon( + context: Context, + activity: LauncherActivityInfo, + size: Int + ): LauncherIcon? { val back = getIconBack() val upon = getIconUpon() val mask = getIconMask() @@ -124,10 +134,12 @@ class IconPackManager private constructor(val context: Context) { val icon = drawable.toBitmap(width = size, height = size) inBounds = Rect(0, 0, icon.width, icon.height) - outBounds = Rect((bitmap.width * (1 - scale) * 0.5).roundToInt(), - (bitmap.height * (1 - scale) * 0.5).roundToInt(), - (bitmap.width - bitmap.width * (1 - scale) * 0.5).roundToInt(), - (bitmap.height - bitmap.height * (1 - scale) * 0.5).roundToInt()) + outBounds = Rect( + (bitmap.width * (1 - scale) * 0.5).roundToInt(), + (bitmap.height * (1 - scale) * 0.5).roundToInt(), + (bitmap.width - bitmap.width * (1 - scale) * 0.5).roundToInt(), + (bitmap.height - bitmap.height * (1 - scale) * 0.5).roundToInt() + ) canvas.drawBitmap(icon, inBounds, outBounds, paint) val pack = selectedIconPack @@ -170,33 +182,39 @@ class IconPackManager private constructor(val context: Context) { } return LauncherIcon( - foreground = BitmapDrawable(context.resources, bitmap), - foregroundScale = getScale(), - autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() + foreground = BitmapDrawable(context.resources, bitmap), + foregroundScale = getScale(), + autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() ) } private fun getDefaultIcon(context: Context, activity: LauncherActivityInfo): LauncherIcon? { if (activity.applicationInfo.packageName == GOOGLE_DESK_CLOCK_PACKAGE_NAME) { - getGoogleDeskClockIcon(context)?.let { return it } + getGoogleDeskClockIcon(context)?.let { + dynamicIconController.registerIcon(it) + return it + } + } + getCalendarIcon(context, activity)?.let { + dynamicIconController.registerIcon(it) + return it } - getCalendarIcon(context, activity)?.let { return it } try { val icon = activity.getIcon(context.resources.displayMetrics.densityDpi) ?: return null when { Build.VERSION.SDK_INT >= Build.VERSION_CODES.O && icon is AdaptiveIconDrawable -> { return LauncherIcon( - foreground = icon.foreground ?: return null, - background = icon.background, - foregroundScale = 1.5f, - backgroundScale = 1.5f + foreground = icon.foreground ?: return null, + background = icon.background, + foregroundScale = 1.5f, + backgroundScale = 1.5f ) } else -> { return LauncherIcon( - foreground = icon, - foregroundScale = getScale(), - autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() + foreground = icon, + foregroundScale = getScale(), + autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() ) } } @@ -205,7 +223,11 @@ class IconPackManager private constructor(val context: Context) { } } - private fun getIconPackCalendarIcon(context: Context, iconPack: String, baseIconName: String): CalendarDynamicLauncherIcon? { + private fun getIconPackCalendarIcon( + context: Context, + iconPack: String, + baseIconName: String + ): CalendarDynamicLauncherIcon? { val resources = try { context.packageManager.getResourcesForApplication(iconPack) } catch (e: PackageManager.NameNotFoundException) { @@ -218,18 +240,21 @@ class IconPackManager private constructor(val context: Context) { id }.toIntArray() return CalendarDynamicLauncherIcon( - context = context, - background = ColorDrawable(0), - foreground = ColorDrawable(0), - foregroundScale = 1.5f, - backgroundScale = 1.5f, - packageName = iconPack, - drawableIds = drawableIds, - autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() + context = context, + background = ColorDrawable(0), + foreground = ColorDrawable(0), + foregroundScale = 1.5f, + backgroundScale = 1.5f, + packageName = iconPack, + drawableIds = drawableIds, + autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() ) } - private fun getCalendarIcon(context: Context, activity: LauncherActivityInfo): CalendarDynamicLauncherIcon? { + private fun getCalendarIcon( + context: Context, + activity: LauncherActivityInfo + ): CalendarDynamicLauncherIcon? { val component = ComponentName(activity.applicationInfo.packageName, activity.name) val pm = context.packageManager val ai = try { @@ -240,7 +265,7 @@ class IconPackManager private constructor(val context: Context) { val resources = pm.getResourcesForActivity(component) var arrayId = ai.metaData?.getInt("com.teslacoilsw.launcher.calendarIconArray") ?: 0 if (arrayId == 0) arrayId = ai.metaData?.getInt("com.google.android.calendar.dynamic_icons") - ?: return null + ?: return null if (arrayId == 0) return null val typedArray = resources.obtainTypedArrayOrNull(arrayId) ?: return null if (typedArray.length() != 31) { @@ -253,43 +278,49 @@ class IconPackManager private constructor(val context: Context) { } typedArray.recycle() return CalendarDynamicLauncherIcon( - context = context, - background = ColorDrawable(0), - foreground = ColorDrawable(0), - foregroundScale = 1.5f, - backgroundScale = 1.5f, - packageName = component.packageName, - drawableIds = drawableIds, - autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() + context = context, + background = ColorDrawable(0), + foreground = ColorDrawable(0), + foregroundScale = 1.5f, + backgroundScale = 1.5f, + packageName = component.packageName, + drawableIds = drawableIds, + autoGenerateBackgroundMode = LauncherPreferences.instance.legacyIconBg.toInt() ) } private fun getGoogleDeskClockIcon(context: Context): ClockDynamicLauncherIcon? { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.O) return null val pm = context.packageManager - val appInfo = pm.getApplicationInfo(GOOGLE_DESK_CLOCK_PACKAGE_NAME, PackageManager.GET_META_DATA) + val appInfo = + pm.getApplicationInfo(GOOGLE_DESK_CLOCK_PACKAGE_NAME, PackageManager.GET_META_DATA) ?: return null - val drawable = appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.LEVEL_PER_TICK_ICON_ROUND") + val drawable = + appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.LEVEL_PER_TICK_ICON_ROUND") val resources = pm.getResourcesForApplication(appInfo) val baseIcon = try { - ResourcesCompat.getDrawable(resources, drawable, null) as? AdaptiveIconDrawable ?: return null + ResourcesCompat.getDrawable(resources, drawable, null) as? AdaptiveIconDrawable + ?: return null } catch (e: Resources.NotFoundException) { return null } val foreground = baseIcon.foreground as? LayerDrawable ?: return null - val hourLayer = appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.HOUR_LAYER_INDEX") - val minuteLayer = appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.MINUTE_LAYER_INDEX") - val secondLayer = appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.SECOND_LAYER_INDEX") + val hourLayer = + appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.HOUR_LAYER_INDEX") + val minuteLayer = + appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.MINUTE_LAYER_INDEX") + val secondLayer = + appInfo.metaData.getInt("com.google.android.apps.nexuslauncher.SECOND_LAYER_INDEX") return ClockDynamicLauncherIcon( - context = context, - background = baseIcon.background, - backgroundScale = 1.5f, - foreground = foreground, - foregroundScale = 1.5f, - badgeNumber = 0f, - hourLayer = hourLayer, - minuteLayer = minuteLayer, - secondLayer = secondLayer + context = context, + background = baseIcon.background, + backgroundScale = 1.5f, + foreground = foreground, + foregroundScale = 1.5f, + badgeNumber = 0f, + hourLayer = hourLayer, + minuteLayer = minuteLayer, + secondLayer = secondLayer ) } @@ -335,12 +366,6 @@ class IconPackManager private constructor(val context: Context) { companion object { const val GOOGLE_DESK_CLOCK_PACKAGE_NAME = "com.google.android.deskclock" const val GOOGLE_CALENDAR_PACKAGE_NAME = "com.google.android.calendar" - - private lateinit var instance: IconPackManager - fun getInstance(context: Context): IconPackManager { - if (!::instance.isInitialized) instance = IconPackManager(context.applicationContext) - return instance - } } @Synchronized @@ -363,9 +388,9 @@ class UpdateIconPacksWorker(val context: Context) { try { val packInfo = context.packageManager.getPackageInfo(pack, 0) val iconPack = IconPack( - name = packInfo.applicationInfo.loadLabel(context.packageManager).toString(), - packageName = pack, - version = packInfo.versionName + name = packInfo.applicationInfo.loadLabel(context.packageManager).toString(), + packageName = pack, + version = packInfo.versionName ) //if (iconDao.isInstalled(iconPack)) continue installIconPack(iconPack) @@ -384,7 +409,9 @@ class UpdateIconPacksWorker(val context: Context) { intent = Intent("com.novalauncher.THEME") val novaPacks = pm.queryIntentActivities(intent, 0) novaPacks.forEach { - if (packs.none { p -> p.activityInfo.packageName == it.activityInfo.packageName }) packs.add(it) + if (packs.none { p -> p.activityInfo.packageName == it.activityInfo.packageName }) packs.add( + it + ) } packs.sortWith(ResolveInfo.DisplayNameComparator(pm)) return packs @@ -401,7 +428,10 @@ class UpdateIconPacksWorker(val context: Context) { else { val rawId = res.getIdentifier("appfilter", "raw", pkgName) if (rawId == 0) { - Log.e("MM20", "Icon pack $pkgName has no appfilter.xml, neither in xml nor in raw") + Log.e( + "MM20", + "Icon pack $pkgName has no appfilter.xml, neither in xml nor in raw" + ) return } parser = XmlPullParserFactory.newInstance().newPullParser() @@ -417,33 +447,43 @@ class UpdateIconPacksWorker(val context: Context) { when (parser.name) { "item" -> { val component = parser.getAttributeValue(null, "component") - ?: continue@loop + ?: continue@loop val drawable = parser.getAttributeValue(null, "drawable") - ?: continue@loop + ?: continue@loop if (component.length <= 14) continue@loop - val componentName = ComponentName.unflattenFromString(component.substring(14, component.lastIndex)) - ?: continue@loop + val componentName = ComponentName.unflattenFromString( + component.substring( + 14, + component.lastIndex + ) + ) + ?: continue@loop val icon = Icon( - componentName = componentName, - drawable = drawable, - iconPack = pkgName, - type = "app" + componentName = componentName, + drawable = drawable, + iconPack = pkgName, + type = "app" ) icons.add(icon) } "calendar" -> { val component = parser.getAttributeValue(null, "component") - ?: continue@loop + ?: continue@loop val drawable = parser.getAttributeValue(null, "prefix") ?: continue@loop if (component.length < 14) continue@loop - val componentName = ComponentName.unflattenFromString(component.substring(14, component.lastIndex)) - ?: continue@loop + val componentName = ComponentName.unflattenFromString( + component.substring( + 14, + component.lastIndex + ) + ) + ?: continue@loop val icon = Icon( - componentName = componentName, - drawable = drawable, - iconPack = pkgName, - type = "calendar" + componentName = componentName, + drawable = drawable, + iconPack = pkgName, + type = "calendar" ) icons.add(icon) } @@ -452,10 +492,10 @@ class UpdateIconPacksWorker(val context: Context) { if (parser.getAttributeName(i).startsWith("img")) { val drawable = parser.getAttributeValue(i) val icon = Icon( - componentName = null, - drawable = drawable, - iconPack = pkgName, - type = "iconback" + componentName = null, + drawable = drawable, + iconPack = pkgName, + type = "iconback" ) icons.add(icon) } @@ -466,10 +506,10 @@ class UpdateIconPacksWorker(val context: Context) { if (parser.getAttributeName(i).startsWith("img")) { val drawable = parser.getAttributeValue(i) val icon = Icon( - componentName = null, - drawable = drawable, - iconPack = pkgName, - type = "iconupon" + componentName = null, + drawable = drawable, + iconPack = pkgName, + type = "iconupon" ) icons.add(icon) } @@ -480,10 +520,10 @@ class UpdateIconPacksWorker(val context: Context) { if (parser.getAttributeName(i).startsWith("img")) { val drawable = parser.getAttributeValue(i) val icon = Icon( - componentName = null, - drawable = drawable, - iconPack = pkgName, - type = "iconmask" + componentName = null, + drawable = drawable, + iconPack = pkgName, + type = "iconmask" ) icons.add(icon) } @@ -491,13 +531,15 @@ class UpdateIconPacksWorker(val context: Context) { } "scale" -> { val scale = parser.getAttributeValue(null, "factor")?.toFloatOrNull() - ?: continue@loop + ?: continue@loop iconPack.scale = scale } } } - iconDao.installIconPack(iconPack.toDatabaseEntity(), icons.map { it.toDatabaseEntity() }) + iconDao.installIconPack( + iconPack.toDatabaseEntity(), + icons.map { it.toDatabaseEntity() }) (parser as? XmlResourceParser)?.close() inStream?.close() diff --git a/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt b/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt index 90c2fcf1..cf6c8a7e 100644 --- a/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt +++ b/icons/src/main/java/de/mm20/launcher2/icons/IconRepository.kt @@ -1,17 +1,41 @@ package de.mm20.launcher2.icons +import android.content.BroadcastReceiver import android.content.Context -import android.util.Log +import android.content.Intent +import android.content.IntentFilter import android.util.LruCache import de.mm20.launcher2.search.data.Searchable import kotlinx.coroutines.* import kotlinx.coroutines.flow.Flow import kotlinx.coroutines.flow.flow -class IconRepository private constructor(val context: Context) { +class IconRepository( + val context: Context, + val iconPackManager: IconPackManager +) { + + private val appReceiver = object : BroadcastReceiver() { + override fun onReceive(context: Context?, intent: Intent?) { + requestIconPackListUpdate() + } + } private val scope = CoroutineScope(Job() + Dispatchers.Main) + + init { + requestIconPackListUpdate() + context.registerReceiver(appReceiver, IntentFilter().apply { + addAction(Intent.ACTION_PACKAGE_REPLACED) + addAction(Intent.ACTION_PACKAGE_ADDED) + addAction(Intent.ACTION_PACKAGE_REMOVED) + addAction(Intent.ACTION_MY_PACKAGE_REPLACED) + addAction(Intent.ACTION_PACKAGE_CHANGED) + addDataScheme("package") + }) + } + private val cache = LruCache(200) fun getIcon(searchable: Searchable, size: Int): Flow = flow { @@ -43,7 +67,7 @@ class IconRepository private constructor(val context: Context) { fun requestIconPackListUpdate() { scope.launch { - IconPackManager.getInstance(context).updateIconPacks() + iconPackManager.updateIconPacks() } } @@ -54,14 +78,4 @@ class IconRepository private constructor(val context: Context) { fun clearCache() { cache.evictAll() } - - - companion object { - private lateinit var instance: IconRepository - - fun getInstance(context: Context): IconRepository { - if (!::instance.isInitialized) instance = IconRepository(context.applicationContext) - return instance - } - } } \ No newline at end of file diff --git a/icons/src/main/java/de/mm20/launcher2/icons/Module.kt b/icons/src/main/java/de/mm20/launcher2/icons/Module.kt new file mode 100644 index 00000000..d3d487b3 --- /dev/null +++ b/icons/src/main/java/de/mm20/launcher2/icons/Module.kt @@ -0,0 +1,10 @@ +package de.mm20.launcher2.icons + +import org.koin.android.ext.koin.androidContext +import org.koin.dsl.module + +val iconsModule = module { + single { DynamicIconController(androidContext()) } + single { IconPackManager(androidContext(), get()) } + single { IconRepository(androidContext(), get()) } +} \ No newline at end of file diff --git a/music/build.gradle.kts b/music/build.gradle.kts index 4b1e5bc1..d4ec15dd 100644 --- a/music/build.gradle.kts +++ b/music/build.gradle.kts @@ -41,6 +41,8 @@ dependencies { implementation(libs.androidx.appcompat) implementation(libs.androidx.media2) + implementation(libs.koin.android) + implementation(project(":ktx")) } \ No newline at end of file diff --git a/music/src/main/java/de/mm20/launcher2/music/Module.kt b/music/src/main/java/de/mm20/launcher2/music/Module.kt new file mode 100644 index 00000000..99180f40 --- /dev/null +++ b/music/src/main/java/de/mm20/launcher2/music/Module.kt @@ -0,0 +1,10 @@ +package de.mm20.launcher2.music + +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val musicModule = module { + single { MusicRepository(androidContext()) } + viewModel { MusicViewModel(get()) } +} \ No newline at end of file diff --git a/music/src/main/java/de/mm20/launcher2/music/MusicRepository.kt b/music/src/main/java/de/mm20/launcher2/music/MusicRepository.kt index 615acd66..019fb92d 100644 --- a/music/src/main/java/de/mm20/launcher2/music/MusicRepository.kt +++ b/music/src/main/java/de/mm20/launcher2/music/MusicRepository.kt @@ -24,7 +24,7 @@ import kotlinx.coroutines.* import java.io.File import java.util.concurrent.Executors -class MusicRepository private constructor(val context: Context) { +class MusicRepository(val context: Context) { private val scope = CoroutineScope(Job() + Dispatchers.Main) @@ -245,12 +245,6 @@ class MusicRepository private constructor(val context: Context) { } companion object { - private lateinit var instance: MusicRepository - - fun getInstance(context: Context): MusicRepository { - if (!::instance.isInitialized) instance = MusicRepository(context.applicationContext) - return instance - } private const val PREFS = "music" private const val PREFS_KEY_TITLE = "title" diff --git a/music/src/main/java/de/mm20/launcher2/music/MusicViewModel.kt b/music/src/main/java/de/mm20/launcher2/music/MusicViewModel.kt index cc3be95b..3285c1d9 100644 --- a/music/src/main/java/de/mm20/launcher2/music/MusicViewModel.kt +++ b/music/src/main/java/de/mm20/launcher2/music/MusicViewModel.kt @@ -1,16 +1,14 @@ package de.mm20.launcher2.music -import android.app.Application import android.app.PendingIntent import android.content.Context import android.graphics.Bitmap -import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData -import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel -class MusicViewModel(app: Application) : AndroidViewModel(app) { - - val musicRepository = MusicRepository.getInstance(app) +class MusicViewModel( + val musicRepository: MusicRepository +) : ViewModel() { val title: LiveData = musicRepository.title val artist: LiveData = musicRepository.artist diff --git a/notifications/build.gradle.kts b/notifications/build.gradle.kts index 4436c2e9..e098856a 100644 --- a/notifications/build.gradle.kts +++ b/notifications/build.gradle.kts @@ -43,6 +43,8 @@ dependencies { implementation(libs.bundles.androidx.lifecycle) + implementation(libs.koin.android) + implementation(project(":music")) implementation(project(":preferences")) implementation(project(":badges")) diff --git a/notifications/src/main/java/de/mm20/launcher2/notifications/NotificationService.kt b/notifications/src/main/java/de/mm20/launcher2/notifications/NotificationService.kt index d4b74808..9c7533e0 100644 --- a/notifications/src/main/java/de/mm20/launcher2/notifications/NotificationService.kt +++ b/notifications/src/main/java/de/mm20/launcher2/notifications/NotificationService.kt @@ -13,10 +13,15 @@ import de.mm20.launcher2.badges.Badge import de.mm20.launcher2.badges.BadgeProvider import de.mm20.launcher2.music.MusicRepository import de.mm20.launcher2.preferences.LauncherPreferences +import org.koin.android.ext.android.inject import java.lang.ref.WeakReference class NotificationService : NotificationListenerService() { + private val musicRepository: MusicRepository by inject() + + private val badgeProvider: BadgeProvider by inject() + override fun onStartCommand(intent: Intent?, flags: Int, startId: Int): Int { return Service.START_STICKY } @@ -30,7 +35,7 @@ class NotificationService : NotificationListenerService() { if (packageManager.queryIntentActivities(intent, 0).none { it.activityInfo.packageName == n.packageName }) continue val token = n.notification.extras[NotificationCompat.EXTRA_MEDIA_SESSION] as? MediaSession.Token ?: continue - MusicRepository.getInstance(this).setMediaSession(MediaSessionCompat.Token.fromToken(token)) + musicRepository.setMediaSession(MediaSessionCompat.Token.fromToken(token)) } if (LauncherPreferences.instance.notificationBadges) { generateBadges() @@ -46,16 +51,16 @@ class NotificationService : NotificationListenerService() { } fun generateBadges() { - BadgeProvider.getInstance(this).removeNotificationBadges() + badgeProvider.removeNotificationBadges() getNotifications().forEach { val pkg = it.packageName - val badge = BadgeProvider.getInstance(this).getBadge("app://$pkg") ?: Badge() + val badge = badgeProvider.getBadge("app://$pkg") ?: Badge() badge.number = activeNotifications.filter { it.packageName == pkg }.sumBy { it.notification.number } - BadgeProvider.getInstance(this).setBadge("app://$pkg", badge) + badgeProvider.setBadge("app://$pkg", badge) } } @@ -73,15 +78,15 @@ class NotificationService : NotificationListenerService() { if (packageManager.queryIntentActivities(intent, 0).none { it.activityInfo.packageName == sbn.packageName }) return val token = sbn.notification.extras[NotificationCompat.EXTRA_MEDIA_SESSION] as? MediaSession.Token ?: return - MusicRepository.getInstance(this).setMediaSession(MediaSessionCompat.Token.fromToken(token)) + musicRepository.setMediaSession(MediaSessionCompat.Token.fromToken(token)) } if (LauncherPreferences.instance.notificationBadges) { val pkg = sbn.packageName - val badge = BadgeProvider.getInstance(this).getBadge("app://$pkg") ?: Badge() + val badge = badgeProvider.getBadge("app://$pkg") ?: Badge() badge.number = activeNotifications.filter { it.packageName == pkg }.sumBy { it.notification.number } - BadgeProvider.getInstance(this).setBadge("app://$pkg", badge) + badgeProvider.setBadge("app://$pkg", badge) } } @@ -91,20 +96,20 @@ class NotificationService : NotificationListenerService() { if (LauncherPreferences.instance.notificationBadges) { val pkg = sbn.packageName if (getNotifications().any { it.packageName == pkg && it.id != sbn.id }) { - val badge = BadgeProvider.getInstance(this).getBadge("app://$pkg") ?: Badge() + val badge = badgeProvider.getBadge("app://$pkg") ?: Badge() badge.number = activeNotifications.filter { it.packageName == pkg }.sumBy { it.notification.number } - BadgeProvider.getInstance(this).setBadge("app://$pkg", badge) + badgeProvider.setBadge("app://$pkg", badge) } else { - BadgeProvider.getInstance(this).removeBadge("app://$pkg") + badgeProvider.removeBadge("app://$pkg") } } } override fun onListenerDisconnected() { super.onListenerDisconnected() - BadgeProvider.getInstance(this).removeNotificationBadges() + badgeProvider.removeNotificationBadges() } companion object { diff --git a/search/build.gradle.kts b/search/build.gradle.kts index 63663477..155585cd 100644 --- a/search/build.gradle.kts +++ b/search/build.gradle.kts @@ -42,6 +42,8 @@ dependencies { implementation(libs.bundles.androidx.lifecycle) + implementation(libs.koin.android) + implementation(project(":base")) implementation(project(":database")) } \ No newline at end of file diff --git a/search/src/main/java/de/mm20/launcher2/search/BaseSearchableRepository.kt b/search/src/main/java/de/mm20/launcher2/search/BaseSearchableRepository.kt index b72784b5..cce2cba3 100644 --- a/search/src/main/java/de/mm20/launcher2/search/BaseSearchableRepository.kt +++ b/search/src/main/java/de/mm20/launcher2/search/BaseSearchableRepository.kt @@ -1,13 +1,16 @@ package de.mm20.launcher2.search import kotlinx.coroutines.* +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import kotlin.coroutines.CoroutineContext import kotlin.coroutines.EmptyCoroutineContext -abstract class BaseSearchableRepository { +abstract class BaseSearchableRepository: KoinComponent { private val scope = CoroutineScope(Job() + Dispatchers.Main) - private val searchQuery = SearchRepository.getInstance().currentQuery + val searchRepository: SearchRepository by inject() + private val searchQuery = searchRepository.currentQuery init { searchQuery.observeForever { @@ -33,10 +36,10 @@ abstract class BaseSearchableRepository { onCancel() searchJob?.takeIf { !it.isCompleted || !it.isCancelled }?.cancelAndJoin() searchJob = scope.launch { - SearchRepository.getInstance().startSearch() + searchRepository.startSearch() search(query) }.also { - it.invokeOnCompletion { SearchRepository.getInstance().endSearch() } + it.invokeOnCompletion { searchRepository.endSearch() } } } } diff --git a/search/src/main/java/de/mm20/launcher2/search/Module.kt b/search/src/main/java/de/mm20/launcher2/search/Module.kt new file mode 100644 index 00000000..77a0c4f0 --- /dev/null +++ b/search/src/main/java/de/mm20/launcher2/search/Module.kt @@ -0,0 +1,12 @@ +package de.mm20.launcher2.search + +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val searchModule = module { + single { SearchRepository() } + viewModel { SearchViewModel(get()) } + single { WebsearchRepository(androidContext()) } + viewModel { WebsearchViewModel(get()) } +} \ No newline at end of file diff --git a/search/src/main/java/de/mm20/launcher2/search/SearchRepository.kt b/search/src/main/java/de/mm20/launcher2/search/SearchRepository.kt index dc545ba5..5aa38405 100644 --- a/search/src/main/java/de/mm20/launcher2/search/SearchRepository.kt +++ b/search/src/main/java/de/mm20/launcher2/search/SearchRepository.kt @@ -2,7 +2,7 @@ package de.mm20.launcher2.search import androidx.lifecycle.MutableLiveData -class SearchRepository private constructor() { +class SearchRepository { val isSearching = MutableLiveData(false) val currentQuery = MutableLiveData() @@ -27,12 +27,4 @@ class SearchRepository private constructor() { runningSearches-- } } - - companion object { - private lateinit var instance: SearchRepository - fun getInstance(): SearchRepository { - if (!::instance.isInitialized) instance = SearchRepository() - return instance - } - } } \ No newline at end of file diff --git a/search/src/main/java/de/mm20/launcher2/search/SearchViewModel.kt b/search/src/main/java/de/mm20/launcher2/search/SearchViewModel.kt index 35606ace..c9cf4973 100644 --- a/search/src/main/java/de/mm20/launcher2/search/SearchViewModel.kt +++ b/search/src/main/java/de/mm20/launcher2/search/SearchViewModel.kt @@ -1,16 +1,16 @@ package de.mm20.launcher2.search -import android.app.Application -import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel -class SearchViewModel(app: Application) : AndroidViewModel(app) { - private val repository = SearchRepository.getInstance() +class SearchViewModel( + private val searchRepository: SearchRepository +) : ViewModel() { - val isSearching: LiveData = repository.isSearching + val isSearching: LiveData = searchRepository.isSearching fun search(query: String) { - repository.currentQuery.value = query + searchRepository.currentQuery.value = query } } \ No newline at end of file diff --git a/search/src/main/java/de/mm20/launcher2/search/SearchableDeserializer.kt b/search/src/main/java/de/mm20/launcher2/search/SearchableDeserializer.kt new file mode 100644 index 00000000..8e8059be --- /dev/null +++ b/search/src/main/java/de/mm20/launcher2/search/SearchableDeserializer.kt @@ -0,0 +1,14 @@ +package de.mm20.launcher2.search + +import de.mm20.launcher2.search.data.Searchable + +interface SearchableDeserializer { + fun deserialize(serialized: String): Searchable? +} + +class NullDeserializer: SearchableDeserializer { + override fun deserialize(serialized: String): Searchable? { + return null + } + +} \ No newline at end of file diff --git a/search/src/main/java/de/mm20/launcher2/search/SearchableSerializer.kt b/search/src/main/java/de/mm20/launcher2/search/SearchableSerializer.kt new file mode 100644 index 00000000..660f3c94 --- /dev/null +++ b/search/src/main/java/de/mm20/launcher2/search/SearchableSerializer.kt @@ -0,0 +1,8 @@ +package de.mm20.launcher2.search + +import de.mm20.launcher2.search.data.Searchable + +interface SearchableSerializer { + fun serialize(searchable: Searchable): String + val typePrefix: String +} \ No newline at end of file diff --git a/search/src/main/java/de/mm20/launcher2/search/WebsearchRepository.kt b/search/src/main/java/de/mm20/launcher2/search/WebsearchRepository.kt index eb567244..c9b7e059 100644 --- a/search/src/main/java/de/mm20/launcher2/search/WebsearchRepository.kt +++ b/search/src/main/java/de/mm20/launcher2/search/WebsearchRepository.kt @@ -11,7 +11,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.delay import kotlinx.coroutines.withContext -class WebsearchRepository private constructor(val context: Context) : BaseSearchableRepository() { +class WebsearchRepository(val context: Context) : BaseSearchableRepository() { val websearches = MutableLiveData>(emptyList()) @@ -52,13 +52,4 @@ class WebsearchRepository private constructor(val context: Context) : BaseSearch } } } - - companion object { - private lateinit var instance: WebsearchRepository - - fun getInstance(context: Context): WebsearchRepository { - if (!::instance.isInitialized) instance = WebsearchRepository(context.applicationContext) - return instance - } - } } \ No newline at end of file diff --git a/search/src/main/java/de/mm20/launcher2/search/WebsearchViewModel.kt b/search/src/main/java/de/mm20/launcher2/search/WebsearchViewModel.kt index 5895f2e5..6c7229d5 100644 --- a/search/src/main/java/de/mm20/launcher2/search/WebsearchViewModel.kt +++ b/search/src/main/java/de/mm20/launcher2/search/WebsearchViewModel.kt @@ -1,22 +1,22 @@ package de.mm20.launcher2.search -import android.app.Application -import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel import de.mm20.launcher2.search.data.Websearch -class WebsearchViewModel(app:Application): AndroidViewModel(app) { +class WebsearchViewModel( + private val websearchRepository: WebsearchRepository +): ViewModel() { - private val repository = WebsearchRepository.getInstance(app) fun insertWebsearch(websearch: Websearch) { - return repository.insertWebsearch(websearch) + return websearchRepository.insertWebsearch(websearch) } fun deleteWebsearch(websearch: Websearch) { - repository.deleteWebsearch(websearch) + websearchRepository.deleteWebsearch(websearch) } - val websearches = repository.websearches - val allWebsearches = repository.allWebsearches + val websearches = websearchRepository.websearches + val allWebsearches = websearchRepository.allWebsearches } \ No newline at end of file diff --git a/settings.gradle.kts b/settings.gradle.kts index 1e890964..dc65e913 100644 --- a/settings.gradle.kts +++ b/settings.gradle.kts @@ -391,15 +391,9 @@ dependencyResolutionManagement { alias("koin.android") .to("io.insert-koin", "koin-android") .versionRef("koin") - alias("koin.androidviewmodel") - .to("io.insert-koin", "koin-android-viewmodel") + alias("koin.androidxcompose") + .to("io.insert-koin", "koin-androidx-compose") .versionRef("koin") - bundle( - "koin", listOf( - "koin.android", - "koin.androidviewmodel" - ) - ) } } } diff --git a/ui/build.gradle.kts b/ui/build.gradle.kts index 345461a8..b3c16b74 100644 --- a/ui/build.gradle.kts +++ b/ui/build.gradle.kts @@ -93,6 +93,9 @@ dependencies { implementation(libs.jsoup) + implementation(libs.koin.android) + implementation(libs.koin.androidxcompose) + implementation(project(":base")) implementation(project(":i18n")) implementation(project(":compat")) diff --git a/ui/src/main/java/de/mm20/launcher2/ui/component/DefaultSwipeActions.kt b/ui/src/main/java/de/mm20/launcher2/ui/component/DefaultSwipeActions.kt index b0db0326..10ceb787 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/component/DefaultSwipeActions.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/component/DefaultSwipeActions.kt @@ -29,6 +29,7 @@ import de.mm20.launcher2.favorites.FavoritesViewModel import de.mm20.launcher2.search.data.Searchable import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.theme.divider +import org.koin.androidx.compose.getViewModel import kotlin.math.roundToInt @OptIn(ExperimentalMaterialApi::class, ExperimentalAnimationApi::class) @@ -39,7 +40,7 @@ fun DefaultSwipeActions( enabled: Boolean = true, content: @Composable RowScope.() -> Unit ) { - val viewModel: FavoritesViewModel = viewModel() + val viewModel: FavoritesViewModel = getViewModel() val isPinned by viewModel.isPinned(item).observeAsState() val isHidden by viewModel.isHidden(item).observeAsState() diff --git a/ui/src/main/java/de/mm20/launcher2/ui/component/SearchBar.kt b/ui/src/main/java/de/mm20/launcher2/ui/component/SearchBar.kt index f8e3a8d3..0b76f831 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/component/SearchBar.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/component/SearchBar.kt @@ -19,16 +19,15 @@ import androidx.compose.ui.graphics.SolidColor import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.TextStyle -import androidx.compose.ui.text.input.TextFieldValue import androidx.compose.ui.unit.dp import androidx.compose.ui.unit.sp -import androidx.lifecycle.viewmodel.compose.viewModel import com.google.accompanist.pager.ExperimentalPagerApi import com.google.accompanist.pager.PagerState import de.mm20.launcher2.search.SearchViewModel import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.locals.LocalNavController import de.mm20.launcher2.ui.locals.LocalWindowSize +import org.koin.androidx.compose.getViewModel /** * Search bar @@ -47,7 +46,7 @@ fun SearchBar( ) { var searchQuery by remember { mutableStateOf("") } - val viewModel: SearchViewModel = viewModel() + val viewModel: SearchViewModel = getViewModel() LaunchedEffect(searchQuery) { viewModel.search(searchQuery) diff --git a/ui/src/main/java/de/mm20/launcher2/ui/component/Toolbar.kt b/ui/src/main/java/de/mm20/launcher2/ui/component/Toolbar.kt index 0b3bf3e4..05a849b1 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/component/Toolbar.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/component/Toolbar.kt @@ -16,6 +16,7 @@ import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.favorites.FavoritesViewModel import de.mm20.launcher2.search.data.Searchable import de.mm20.launcher2.ui.R +import org.koin.androidx.compose.getViewModel import kotlin.math.min @Composable @@ -162,7 +163,7 @@ data class ToggleToolbarAction( @Composable fun favoritesToolbarAction(item: Searchable): ToggleToolbarAction { - val viewModel = viewModel() + val viewModel: FavoritesViewModel = getViewModel() val isPinned by viewModel.isPinned(item).observeAsState(false) return ToggleToolbarAction( @@ -183,7 +184,7 @@ fun favoritesToolbarAction(item: Searchable): ToggleToolbarAction { @Composable fun hideToolbarAction(item: Searchable): ToggleToolbarAction { - val viewModel = viewModel() + val viewModel: FavoritesViewModel = getViewModel() val isHidden by viewModel.isHidden(item).observeAsState(false) return ToggleToolbarAction( diff --git a/ui/src/main/java/de/mm20/launcher2/ui/component/WidgetColumn.kt b/ui/src/main/java/de/mm20/launcher2/ui/component/WidgetColumn.kt index f329b96e..270f7040 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/component/WidgetColumn.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/component/WidgetColumn.kt @@ -27,6 +27,7 @@ import de.mm20.launcher2.ui.locals.LocalWindowSize import de.mm20.launcher2.ui.widget.WidgetCard import de.mm20.launcher2.widgets.Widget import de.mm20.launcher2.widgets.WidgetViewModel +import org.koin.androidx.compose.getViewModel @OptIn(ExperimentalAnimationApi::class, ExperimentalComposeUiApi::class, ExperimentalAnimationGraphicsApi::class ) @@ -39,7 +40,7 @@ fun WidgetColumn( var widgets by remember { mutableStateOf(listOf()) } - val viewModel: WidgetViewModel = viewModel() + val viewModel: WidgetViewModel = getViewModel() var editMode by remember { mutableStateOf(false) } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/activity/LauncherActivity.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/activity/LauncherActivity.kt index 123ee1e5..aa37015d 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/activity/LauncherActivity.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/activity/LauncherActivity.kt @@ -68,6 +68,8 @@ import de.mm20.launcher2.widgets.WidgetType import de.mm20.launcher2.widgets.WidgetViewModel import kotlinx.android.synthetic.main.activity_launcher.* import kotlinx.coroutines.* +import org.koin.android.ext.android.inject +import org.koin.androidx.viewmodel.ext.android.viewModel import java.util.* import kotlin.math.roundToInt @@ -84,8 +86,9 @@ class LauncherActivity : AppCompatActivity() { private lateinit var overlayView: ViewGroupOverlay - private lateinit var searchViewModel: SearchViewModel - private lateinit var widgetViewModel: WidgetViewModel + private val searchViewModel: SearchViewModel by viewModel() + private val widgetViewModel: WidgetViewModel by viewModel() + private val favoritesViewModel: FavoritesViewModel by viewModel() private val preferences = LauncherPreferences.instance @@ -205,8 +208,6 @@ class LauncherActivity : AppCompatActivity() { overlayView = rootView.overlay - searchViewModel = ViewModelProvider(this)[SearchViewModel::class.java] - widgetViewModel = ViewModelProvider(this)[WidgetViewModel::class.java] scrollContainer.layoutTransition.enableTransitionType(LayoutTransition.CHANGING) @@ -302,8 +303,7 @@ class LauncherActivity : AppCompatActivity() { } hiddenItemsGrid.columnCount = resources.getInteger(R.integer.config_columnCount) - val hiddenItems = - ViewModelProvider(this)[FavoritesViewModel::class.java].hiddenItems + val hiddenItems = favoritesViewModel.hiddenItems hiddenItems.observe(this) { hiddenItemsGrid.submitItems(it) } @@ -359,7 +359,9 @@ class LauncherActivity : AppCompatActivity() { } } - lifecycle.addObserver(DynamicIconController.getInstance(this)) + val dynamicIconController: DynamicIconController by inject() + + lifecycle.addObserver(dynamicIconController) lifecycleScope.launch { widgets.addAll(widgetViewModel.getWidgets()) @@ -399,10 +401,9 @@ class LauncherActivity : AppCompatActivity() { } private fun addWidget() { - val viewModel = ViewModelProvider(this)[WidgetViewModel::class.java] val usedWidgets = widgets.filter { it.type == WidgetType.INTERNAL }.map { it.data } val internalWidgets = - viewModel.getInternalWidgets().filter { !usedWidgets.contains(it.data) } + widgetViewModel.getInternalWidgets().filter { !usedWidgets.contains(it.data) } if (internalWidgets.isNotEmpty()) { MaterialDialog(this).show { val widgetList = @@ -652,11 +653,11 @@ class LauncherActivity : AppCompatActivity() { ViewModelProvider(this).get(WeatherViewModel::class.java).requestUpdate(this) } PermissionsManager.CALENDAR -> { - ViewModelProvider(this)[WidgetViewModel::class.java].requestCalendarUpdate() + widgetViewModel.requestCalendarUpdate() } PermissionsManager.ALL -> { ViewModelProvider(this).get(WeatherViewModel::class.java).requestUpdate(this) - ViewModelProvider(this)[WidgetViewModel::class.java].requestCalendarUpdate() + widgetViewModel.requestCalendarUpdate() search(searchBar.getSearchQuery()) } } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ApplicationView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ApplicationView.kt index 80ce31ef..680145e8 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ApplicationView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ApplicationView.kt @@ -11,6 +11,7 @@ import de.mm20.launcher2.applications.AppViewModel import de.mm20.launcher2.search.data.Application import de.mm20.launcher2.ui.R import kotlinx.android.synthetic.main.view_application.view.* +import org.koin.androidx.viewmodel.ext.android.viewModel class ApplicationView : FrameLayout { @@ -25,7 +26,8 @@ class ApplicationView : FrameLayout { layoutTransition = LayoutTransition() layoutTransition.enableTransitionType(LayoutTransition.CHANGING) applicationCard.layoutTransition.enableTransitionType(LayoutTransition.CHANGING) - applications = ViewModelProvider(context as AppCompatActivity).get(AppViewModel::class.java).applications + val viewModel: AppViewModel by (context as AppCompatActivity).viewModel() + applications = viewModel.applications applications.observe(context as AppCompatActivity, Observer> { visibility = if (it.isEmpty()) View.GONE else View.VISIBLE applicationGrid.submitItems(it) diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalculatorView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalculatorView.kt index 9ef920c9..d1d25390 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalculatorView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalculatorView.kt @@ -12,6 +12,7 @@ import de.mm20.launcher2.ui.R import de.mm20.launcher2.calculator.CalculatorViewModel import de.mm20.launcher2.search.data.Calculator import kotlinx.android.synthetic.main.view_calculator.view.* +import org.koin.androidx.viewmodel.ext.android.viewModel import kotlin.math.round class CalculatorView : FrameLayout { @@ -24,7 +25,8 @@ class CalculatorView : FrameLayout { init { View.inflate(context, R.layout.view_calculator, this) - calculator = ViewModelProvider(context as AppCompatActivity).get(CalculatorViewModel::class.java).calculator + val viewModel: CalculatorViewModel by (context as AppCompatActivity).viewModel() + calculator = viewModel.calculator calculator.observe(context as AppCompatActivity, Observer { if (it == null) visibility = View.GONE else { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalendarView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalendarView.kt index c4df3a0d..a8a089d1 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalendarView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/CalendarView.kt @@ -16,6 +16,7 @@ import de.mm20.launcher2.preferences.LauncherPreferences import de.mm20.launcher2.search.data.CalendarEvent import de.mm20.launcher2.search.data.MissingPermission import de.mm20.launcher2.ui.legacy.search.SearchListView +import org.koin.androidx.viewmodel.ext.android.viewModel class CalendarView : FrameLayout { @@ -32,7 +33,8 @@ class CalendarView : FrameLayout { val card = findViewById(R.id.card) card.layoutTransition.enableTransitionType(LayoutTransition.CHANGING) val list = findViewById(R.id.list) - calendarEvents = ViewModelProvider(context as AppCompatActivity).get(CalendarViewModel::class.java).calendarEvents + val viewModel: CalendarViewModel by (context as AppCompatActivity).viewModel() + calendarEvents = viewModel.calendarEvents calendarEvents.observe(context as AppCompatActivity, { if (it == null) { visibility = View.GONE diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ContactView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ContactView.kt index 2a4037e6..c4f2b91d 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ContactView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/ContactView.kt @@ -16,6 +16,7 @@ import de.mm20.launcher2.search.data.Contact import de.mm20.launcher2.search.data.MissingPermission import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.legacy.search.SearchListView +import org.koin.androidx.viewmodel.ext.android.viewModel class ContactView : FrameLayout { private val contacts: LiveData?> @@ -30,7 +31,8 @@ class ContactView : FrameLayout { layoutTransition.enableTransitionType(LayoutTransition.CHANGING) val card = findViewById(R.id.card) card.layoutTransition.enableTransitionType(LayoutTransition.CHANGING) - contacts = ViewModelProvider(context as AppCompatActivity).get(ContactViewModel::class.java).contacts + val viewModel: ContactViewModel by (context as AppCompatActivity).viewModel() + contacts = viewModel.contacts val list = findViewById(R.id.list) contacts.observe(context as AppCompatActivity, { if (it == null) { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/EditFavoritesRow.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/EditFavoritesRow.kt index 23022f9f..a724513b 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/EditFavoritesRow.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/EditFavoritesRow.kt @@ -12,15 +12,20 @@ import de.mm20.launcher2.ui.R import kotlinx.android.synthetic.main.edit_favorites_row.view.* import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject class EditFavoritesRow @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0, val favoritesItem: FavoritesItem -) : LinearLayout(context, attrs, defStyleAttr) { +) : LinearLayout(context, attrs, defStyleAttr), KoinComponent { + + val iconRepository: IconRepository by inject() + init { View.inflate(context, R.layout.edit_favorites_row, this) label.text = favoritesItem.searchable?.label lifecycleScope.launch { - IconRepository.getInstance(context).getIcon(favoritesItem.searchable!!, (48*dp).toInt()).collect{ + iconRepository.getIcon(favoritesItem.searchable!!, (48*dp).toInt()).collect{ icon.icon = it } } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/EditFavoritesView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/EditFavoritesView.kt index 1ccf0231..3afa7594 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/EditFavoritesView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/EditFavoritesView.kt @@ -21,10 +21,14 @@ import kotlinx.android.synthetic.main.dialog_edit_favorites.view.* import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext +import org.koin.androidx.viewmodel.ext.android.viewModel class EditFavoritesView @JvmOverloads constructor( context: Context, attrs: AttributeSet? = null ) : FrameLayout(context, attrs) { + + val viewModel : FavoritesViewModel by (context as AppCompatActivity).viewModel() + init { View.inflate(context, R.layout.dialog_edit_favorites, this) lifecycleScope.launch { @@ -35,7 +39,6 @@ class EditFavoritesView @JvmOverloads constructor( private lateinit var favorites: MutableList suspend fun initView() { - val viewModel = ViewModelProvider(context as AppCompatActivity)[FavoritesViewModel::class.java] favorites = withContext(Dispatchers.IO) { viewModel.getAllFavoriteItems().toMutableList() } @@ -117,7 +120,6 @@ class EditFavoritesView @JvmOverloads constructor( } fun save() { - val viewModel = ViewModelProvider(context as AppCompatActivity)[FavoritesViewModel::class.java] viewModel.saveFavorites(favorites) } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FavoritesView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FavoritesView.kt index 8802bb3f..718db58d 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FavoritesView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FavoritesView.kt @@ -11,6 +11,7 @@ import de.mm20.launcher2.favorites.FavoritesViewModel import de.mm20.launcher2.search.data.Searchable import de.mm20.launcher2.ui.R import kotlinx.android.synthetic.main.view_favorites.view.* +import org.koin.androidx.viewmodel.ext.android.viewModel class FavoritesView : FrameLayout { @@ -23,7 +24,7 @@ class FavoritesView : FrameLayout { init { View.inflate(context, R.layout.view_favorites, this) - val viewModel = ViewModelProvider(context as AppCompatActivity)[FavoritesViewModel::class.java] + val viewModel: FavoritesViewModel by (context as AppCompatActivity).viewModel() favorites = viewModel.getFavorites(context.resources.getInteger(R.integer.config_columnCount)) favorites.observe(context as AppCompatActivity, Observer { visibility = if (it?.isEmpty() == true) View.GONE else View.VISIBLE diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FileView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FileView.kt index 4c63ab10..d20971c5 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FileView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/FileView.kt @@ -15,6 +15,7 @@ import de.mm20.launcher2.search.data.File import de.mm20.launcher2.search.data.MissingPermission import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.legacy.search.SearchListView +import org.koin.androidx.viewmodel.ext.android.viewModel class FileView : FrameLayout { private val files: LiveData?> @@ -30,7 +31,8 @@ class FileView : FrameLayout { val card = findViewById(R.id.card) card.layoutTransition.enableTransitionType(LayoutTransition.CHANGING) val list = findViewById(R.id.list) - files = ViewModelProvider(context as AppCompatActivity).get(FilesViewModel::class.java).files + val viewModel: FilesViewModel by (context as AppCompatActivity).viewModel() + files = viewModel.files files.observe(context as AppCompatActivity, { if (it == null) { visibility = View.GONE diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/SearchBar.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/SearchBar.kt index 32a50e1c..b1fb4342 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/SearchBar.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/SearchBar.kt @@ -26,6 +26,7 @@ import de.mm20.launcher2.transition.ChangingLayoutTransition import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.legacy.view.LauncherCardView import kotlinx.android.synthetic.main.view_search_bar.view.* +import org.koin.androidx.viewmodel.ext.android.viewModel class SearchBar @JvmOverloads constructor( context: Context, @@ -70,7 +71,9 @@ class SearchBar @JvmOverloads constructor( }) - ViewModelProvider(context as AppCompatActivity)[SearchViewModel::class.java].isSearching.observe(context, Observer { + val viewModel = (context as AppCompatActivity).viewModel().value + + viewModel.isSearching.observe(context, Observer { searchProgressBar.visibility = if (it) View.VISIBLE else View.GONE }) diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/UnitConverterView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/UnitConverterView.kt index 4ff01e21..fbbc1ddd 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/UnitConverterView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/UnitConverterView.kt @@ -15,6 +15,7 @@ import androidx.browser.customtabs.CustomTabsIntent import androidx.lifecycle.LiveData import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider +import androidx.lifecycle.viewmodel.compose.viewModel import androidx.recyclerview.widget.DividerItemDecoration import androidx.recyclerview.widget.LinearLayoutManager import com.afollestad.materialdialogs.MaterialDialog @@ -31,6 +32,7 @@ import de.mm20.launcher2.ui.R import de.mm20.launcher2.unitconverter.UnitConverterViewModel import de.mm20.launcher2.unitconverter.UnitValue import kotlinx.android.synthetic.main.view_unitconverter.view.* +import org.koin.androidx.viewmodel.ext.android.viewModel import java.text.DateFormat import java.util.* import kotlin.math.min @@ -46,7 +48,8 @@ class UnitConverterView : FrameLayout { init { View.inflate(context, R.layout.view_unitconverter, this) - unitConverter = ViewModelProvider(context as AppCompatActivity).get(UnitConverterViewModel::class.java).unitConverter + val unitConverterViewModel by (context as AppCompatActivity).viewModel() + unitConverter = unitConverterViewModel.unitConverter unitConverter.observe(context as AppCompatActivity, Observer { if (it == null) visibility = View.GONE else { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WebSearchView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WebSearchView.kt index 2be82b18..7e4cfa00 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WebSearchView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WebSearchView.kt @@ -21,6 +21,7 @@ import de.mm20.launcher2.search.WebsearchViewModel import de.mm20.launcher2.search.data.Websearch import de.mm20.launcher2.ui.R import kotlinx.android.synthetic.main.view_websearch.view.* +import org.koin.androidx.viewmodel.ext.android.viewModel class WebSearchView : FrameLayout { constructor(context: Context) : super(context) @@ -31,7 +32,7 @@ class WebSearchView : FrameLayout { init { View.inflate(context, R.layout.view_websearch, this) - val viewModel = ViewModelProvider(context as AppCompatActivity)[WebsearchViewModel::class.java] + val viewModel: WebsearchViewModel by (context as AppCompatActivity).viewModel() websearches = viewModel.websearches websearches.observe(context as AppCompatActivity, Observer { updateWebsearches(it) diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WebsiteView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WebsiteView.kt index 7442413a..218b8b1f 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WebsiteView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WebsiteView.kt @@ -14,6 +14,7 @@ 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.websites.WebsiteViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel class WebsiteView : FrameLayout { @@ -30,7 +31,8 @@ class WebsiteView : FrameLayout { val card = findViewById(R.id.card) websiteView.layoutParams = params card.addView(websiteView) - website = ViewModelProvider(context as AppCompatActivity)[WebsiteViewModel::class.java].website + val viewModel: WebsiteViewModel by (context as AppCompatActivity).viewModel() + website = viewModel.website website.observe(context as AppCompatActivity, Observer { visibility = if (it == null) View.GONE else View.VISIBLE card.setOnClickListener { _ -> diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WikipediaView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WikipediaView.kt index 02548977..c99bd1fd 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WikipediaView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/component/WikipediaView.kt @@ -14,6 +14,7 @@ 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.wikipedia.WikipediaViewModel +import org.koin.androidx.viewmodel.ext.android.viewModel class WikipediaView : FrameLayout { @@ -30,7 +31,8 @@ class WikipediaView : FrameLayout { val card = findViewById(R.id.card) websiteView.layoutParams = params card.addView(websiteView) - wikipedia = ViewModelProvider(context as AppCompatActivity)[WikipediaViewModel::class.java].wikipedia + val viewModel: WikipediaViewModel by (context as AppCompatActivity).viewModel() + wikipedia = viewModel.wikipedia wikipedia.observe(context as AppCompatActivity, Observer { visibility = if (it == null) View.GONE else View.VISIBLE card.setOnClickListener { _ -> 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 index 63f40843..4da59941 100644 --- 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 @@ -18,9 +18,13 @@ import de.mm20.launcher2.preferences.AppStartAnimation import de.mm20.launcher2.preferences.LauncherPreferences 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 { +object ActivityStarter: KoinComponent { + + val favoritesRepository: FavoritesRepository by inject() private var initialized = false private lateinit var overlayView: WeakReference @@ -141,7 +145,7 @@ object ActivityStarter { if (item != null) { if (item.launch(context, bundle)) { - FavoritesRepository.getInstance(context).incrementLaunchCount(item) + favoritesRepository.incrementLaunchCount(item) return true } return false 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 index 3a2810b2..2be9344e 100644 --- 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 @@ -27,6 +27,7 @@ import androidx.core.content.ContextCompat import androidx.core.content.FileProvider import androidx.core.content.getSystemService import androidx.core.graphics.alpha +import androidx.lifecycle.LifecycleOwner import androidx.lifecycle.Observer import androidx.lifecycle.ViewModelProvider import androidx.transition.Scene @@ -52,10 +53,16 @@ import de.mm20.launcher2.ui.legacy.searchable.SearchableView import de.mm20.launcher2.ui.legacy.view.* import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import java.util.concurrent.Executors import kotlin.math.roundToInt -class ApplicationDetailRepresentation : Representation { +class ApplicationDetailRepresentation : Representation, KoinComponent { + + private val iconRepository: IconRepository by inject() + private val badgeProvider: BadgeProvider by inject() override fun getScene( rootView: SearchableView, @@ -71,12 +78,11 @@ class ApplicationDetailRepresentation : Representation { setOnLongClickListener(null) findViewById(R.id.appName).text = application.label findViewById(R.id.icon).apply { - badge = BadgeProvider.getInstance(context).getLiveBadge(application.badgeKey) + badge = badgeProvider.getLiveBadge(application.badgeKey) shape = LauncherIconView.getDefaultShape(context) - icon = IconRepository.getInstance(context).getIconIfCached(application) + icon = iconRepository.getIconIfCached(application) lifecycleScope.launch { - IconRepository.getInstance(context) - .getIcon(application, (84 * rootView.dp).toInt()).collect { + iconRepository.getIcon(application, (84 * rootView.dp).toInt()).collect { icon = it } } @@ -264,8 +270,7 @@ class ApplicationDetailRepresentation : Representation { if (launcherApps.hasShortcutHostPermission()) { val shortcuts = app.shortcuts - val viewModel = - ViewModelProvider(context as AppCompatActivity)[FavoritesViewModel::class.java] + val viewModel : FavoritesViewModel by (context as AppCompatActivity).viewModel() for (si in shortcuts) { val view = Chip(context) @@ -292,7 +297,7 @@ class ApplicationDetailRepresentation : Representation { ) val isPinned = viewModel.isPinned(si) - isPinned.observe(context, Observer { + isPinned.observe(context as LifecycleOwner, Observer { view.isCloseIconVisible = isPinned.value == true }) 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 index f45702ca..caaec6e0 100644 --- 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 @@ -3,20 +3,30 @@ package de.mm20.launcher2.ui.legacy.search import android.widget.TextView import androidx.appcompat.app.AppCompatActivity import androidx.transition.Scene -import de.mm20.launcher2.ui.R import de.mm20.launcher2.badges.BadgeProvider -import de.mm20.launcher2.ktx.dp import de.mm20.launcher2.icons.IconRepository +import de.mm20.launcher2.ktx.dp import de.mm20.launcher2.ktx.lifecycleScope import de.mm20.launcher2.legacy.helper.ActivityStarter import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.legacy.searchable.SearchableView import de.mm20.launcher2.ui.legacy.view.LauncherIconView import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject -class BasicGridRepresentation : Representation { - override fun getScene(rootView: SearchableView, searchable: Searchable, previousRepresentation: Int?): Scene { +class BasicGridRepresentation : Representation, KoinComponent { + + private val iconRepository: IconRepository by inject() + private val badgeProvider: BadgeProvider by inject() + + 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 { @@ -30,16 +40,21 @@ class BasicGridRepresentation : Representation { .alpha(1f) .start()*/ findViewById(R.id.icon).apply { - badge = BadgeProvider.getInstance(context).getLiveBadge(searchable.badgeKey) + badge = badgeProvider.getLiveBadge(searchable.badgeKey) shape = LauncherIconView.getDefaultShape(context) setOnClickListener { - if (!ActivityStarter.start(context, rootView.findViewById(R.id.card), item = searchable)) { + if (!ActivityStarter.start( + context, + rootView.findViewById(R.id.card), + item = searchable + ) + ) { rootView.representation = SearchableView.REPRESENTATION_FULL } } - icon = IconRepository.getInstance(context).getIconIfCached(searchable) + icon = iconRepository.getIconIfCached(searchable) lifecycleScope.launch { - IconRepository.getInstance(context).getIcon(searchable, (84 * rootView.dp).toInt()).collect { + iconRepository.getIcon(searchable, (84 * rootView.dp).toInt()).collect { icon = it } } 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 index 4dd3af8e..768b68e3 100644 --- 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 @@ -27,9 +27,15 @@ import de.mm20.launcher2.ui.legacy.searchable.SearchableView import de.mm20.launcher2.ui.legacy.view.* import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import java.net.URLEncoder -class ContactDetailRepresentation : Representation { +class ContactDetailRepresentation : Representation, KoinComponent { + + val iconRepository: IconRepository by inject() + val badgeProvider: BadgeProvider by inject() + override fun getScene( rootView: SearchableView, searchable: Searchable, @@ -42,12 +48,11 @@ class ContactDetailRepresentation : Representation { scene.setEnterAction { with(rootView) { findViewById(R.id.icon).apply { - badge = BadgeProvider.getInstance(context).getLiveBadge(contact.badgeKey) + badge = badgeProvider.getLiveBadge(contact.badgeKey) shape = LauncherIconView.getDefaultShape(context) - icon = IconRepository.getInstance(context).getIconIfCached(contact) + icon = iconRepository.getIconIfCached(contact) lifecycleScope.launch { - IconRepository.getInstance(context) - .getIcon(contact, (84 * rootView.dp).toInt()).collect { + iconRepository.getIcon(contact, (84 * rootView.dp).toInt()).collect { icon = it } } 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 index d17c2df6..73fa79ad 100644 --- 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 @@ -17,8 +17,14 @@ import de.mm20.launcher2.ui.legacy.view.LauncherIconView import de.mm20.launcher2.ui.legacy.view.SwipeCardView import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class ContactListRepresentation : Representation, KoinComponent { + + val iconRepository: IconRepository by inject() + val badgeProvider: BadgeProvider by inject() -class ContactListRepresentation : Representation { override fun getScene(rootView: SearchableView, searchable: Searchable, previousRepresentation: Int?): Scene { val contact = searchable as Contact val context = rootView.context as AppCompatActivity @@ -26,11 +32,11 @@ class ContactListRepresentation : Representation { scene.setEnterAction { with(rootView) { findViewById(R.id.icon).apply { - badge = BadgeProvider.getInstance(context).getLiveBadge(contact.badgeKey) + badge = badgeProvider.getLiveBadge(contact.badgeKey) shape = LauncherIconView.getDefaultShape(context) - icon = IconRepository.getInstance(context).getIconIfCached(contact) + icon = iconRepository.getIconIfCached(contact) lifecycleScope.launch { - IconRepository.getInstance(context).getIcon(contact, (84 * rootView.dp).toInt()).collect { + iconRepository.getIcon(contact, (84 * rootView.dp).toInt()).collect { icon = it } } 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 index ad76973a..bd170166 100644 --- 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 @@ -22,9 +22,16 @@ import de.mm20.launcher2.ui.legacy.searchable.SearchableView import de.mm20.launcher2.ui.legacy.view.* import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch +import org.koin.androidx.viewmodel.ext.android.viewModel +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject import java.text.DecimalFormat -class FileDetailRepresentation : Representation { +class FileDetailRepresentation : Representation, KoinComponent { + + val iconRepository: IconRepository by inject() + val badgeProvider: BadgeProvider by inject() + override fun getScene(rootView: SearchableView, searchable: Searchable, previousRepresentation: Int?): Scene { val file = searchable as File val context = rootView.context as AppCompatActivity @@ -34,11 +41,11 @@ class FileDetailRepresentation : Representation { findViewById(R.id.fileLabel).text = file.label findViewById(R.id.fileInfo).text = getInfo(context, file) findViewById(R.id.icon).apply { - badge = BadgeProvider.getInstance(context).getLiveBadge(file.badgeKey) + badge = badgeProvider.getLiveBadge(file.badgeKey) shape = LauncherIconView.getDefaultShape(context) - icon = IconRepository.getInstance(context).getIconIfCached(file) + icon = iconRepository.getIconIfCached(file) lifecycleScope.launch { - IconRepository.getInstance(context).getIcon(file, (84 * rootView.dp).toInt()).collect { + iconRepository.getIcon(file, (84 * rootView.dp).toInt()).collect { icon = it } } @@ -100,7 +107,7 @@ class FileDetailRepresentation : Representation { "${MediaStore.Files.FileColumns._ID} = ?", arrayOf(file.id.toString())) it.dismiss() - val fileViewModel = ViewModelProvider(context as AppCompatActivity)[FilesViewModel::class.java] + val fileViewModel: FilesViewModel by (context as AppCompatActivity).viewModel() fileViewModel.removeFile(file) } negativeButton(android.R.string.no) { 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 index 0f8eaebd..189385f9 100644 --- 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 @@ -19,8 +19,14 @@ import de.mm20.launcher2.ui.legacy.view.LauncherIconView import de.mm20.launcher2.ui.legacy.view.SwipeCardView import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class FileListRepresentation : Representation, KoinComponent { + + val iconRepository: IconRepository by inject() + val badgeProvider: BadgeProvider by inject() -class FileListRepresentation : Representation { override fun getScene(rootView: SearchableView, searchable: Searchable, previousRepresentation: Int?): Scene { val file = searchable as File val context = rootView.context as AppCompatActivity @@ -30,11 +36,11 @@ class FileListRepresentation : Representation { findViewById(R.id.fileLabel).text = file.label findViewById(R.id.fileInfo).text = getFileType(context, file) findViewById(R.id.icon).apply { - badge = BadgeProvider.getInstance(context).getLiveBadge(file.badgeKey) + badge = badgeProvider.getLiveBadge(file.badgeKey) shape = LauncherIconView.getDefaultShape(context) - icon = IconRepository.getInstance(context).getIconIfCached(file) + icon = iconRepository.getIconIfCached(file) lifecycleScope.launch { - IconRepository.getInstance(context).getIcon(file, (84 * rootView.dp).toInt()).collect { + iconRepository.getIcon(file, (84 * rootView.dp).toInt()).collect { icon = it } } 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 index d5866f6e..f31dbd3a 100644 --- 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 @@ -19,9 +19,15 @@ import de.mm20.launcher2.ui.legacy.view.ToolbarAction import de.mm20.launcher2.ui.legacy.view.ToolbarView import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject @RequiresApi(Build.VERSION_CODES.N_MR1) -class AppShortcutDetailRepresentation: Representation { +class AppShortcutDetailRepresentation: Representation, KoinComponent { + + val iconRepository: IconRepository by inject() + val badgeProvider: BadgeProvider by inject() + override fun getScene(rootView: SearchableView, searchable: Searchable, previousRepresentation: Int?): Scene { val appShortcut = searchable as AppShortcut val context = rootView.context as AppCompatActivity @@ -32,11 +38,11 @@ class AppShortcutDetailRepresentation: Representation { setOnLongClickListener(null) findViewById(R.id.appName).text = appShortcut.label findViewById(R.id.icon).apply { - badge = BadgeProvider.getInstance(context).getLiveBadge(appShortcut.badgeKey) + badge = badgeProvider.getLiveBadge(appShortcut.badgeKey) shape = LauncherIconView.getDefaultShape(context) - icon = IconRepository.getInstance(context).getIconIfCached(appShortcut) + icon = iconRepository.getIconIfCached(appShortcut) lifecycleScope.launch { - IconRepository.getInstance(context).getIcon(appShortcut, (84 * rootView.dp).toInt()).collect { + iconRepository.getIcon(appShortcut, (84 * rootView.dp).toInt()).collect { icon = it } } 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 index 1f06be11..70791ef8 100644 --- 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 @@ -24,8 +24,14 @@ import de.mm20.launcher2.ui.legacy.view.ToolbarAction import de.mm20.launcher2.ui.legacy.view.ToolbarView import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject + +class WebsiteDetailRepresentation : Representation, KoinComponent { + + val iconRepository: IconRepository by inject() + val badgeProvider: BadgeProvider by inject() -class WebsiteDetailRepresentation : Representation { override fun getScene(rootView: SearchableView, searchable: Searchable, previousRepresentation: Int?): Scene { val website = searchable as Website val context = rootView.context as AppCompatActivity @@ -60,11 +66,11 @@ class WebsiteDetailRepresentation : Representation { label.transitionName = null websiteFavIcon.transitionName = "icon" websiteFavIcon.apply { - badge = BadgeProvider.getInstance(context).getLiveBadge(website.badgeKey) + badge = badgeProvider.getLiveBadge(website.badgeKey) shape = LauncherIconView.getDefaultShape(context) - icon = IconRepository.getInstance(context).getIconIfCached(website) + icon = iconRepository.getIconIfCached(website) lifecycleScope.launch { - IconRepository.getInstance(context).getIcon(website, (84 * rootView.dp).toInt()).collect { + iconRepository.getIcon(website, (84 * rootView.dp).toInt()).collect { icon = it } } 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 index 738918be..737e7ca0 100644 --- 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 @@ -10,8 +10,8 @@ import androidx.appcompat.app.AppCompatActivity import androidx.transition.Scene import com.bumptech.glide.Glide import de.mm20.launcher2.badges.BadgeProvider -import de.mm20.launcher2.ktx.dp import de.mm20.launcher2.icons.IconRepository +import de.mm20.launcher2.ktx.dp import de.mm20.launcher2.ktx.lifecycleScope import de.mm20.launcher2.legacy.helper.ActivityStarter import de.mm20.launcher2.search.data.Searchable @@ -24,9 +24,20 @@ import de.mm20.launcher2.ui.legacy.view.ToolbarAction import de.mm20.launcher2.ui.legacy.view.ToolbarView import kotlinx.coroutines.flow.collect import kotlinx.coroutines.launch +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject -class WebsiteListRepresentation : Representation { - override fun getScene(rootView: SearchableView, searchable: Searchable, previousRepresentation: Int?): Scene { +class WebsiteListRepresentation : Representation, KoinComponent { + + val iconRepository: IconRepository by inject() + + val badgeProvider: BadgeProvider by inject() + + 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) @@ -60,11 +71,11 @@ class WebsiteListRepresentation : Representation { label.transitionName = null websiteFavIcon.transitionName = "icon" websiteFavIcon.apply { - badge = BadgeProvider.getInstance(context).getLiveBadge(website.badgeKey) + badge = badgeProvider.getLiveBadge(website.badgeKey) shape = LauncherIconView.getDefaultShape(context) - icon = IconRepository.getInstance(context).getIconIfCached(website) + icon = iconRepository.getIconIfCached(website) lifecycleScope.launch { - IconRepository.getInstance(context).getIcon(website, (84 * rootView.dp).toInt()).collect { + iconRepository.getIcon(website, (84 * rootView.dp).toInt()).collect { icon = it } } @@ -101,7 +112,10 @@ class WebsiteListRepresentation : Representation { private fun share(context: Context, website: Website) { val shareIntent = Intent(Intent.ACTION_SEND) - shareIntent.putExtra(Intent.EXTRA_TEXT, "${website.label}\n\n${website.description}\n\n${website.url}") + shareIntent.putExtra( + Intent.EXTRA_TEXT, + "${website.label}\n\n${website.description}\n\n${website.url}" + ) shareIntent.type = "text/plain" context.startActivity(Intent.createChooser(shareIntent, null)) } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/SwipeCardView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/SwipeCardView.kt index 38274f98..d518de61 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/SwipeCardView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/SwipeCardView.kt @@ -22,6 +22,7 @@ import de.mm20.launcher2.preferences.LauncherPreferences import de.mm20.launcher2.search.data.Searchable import de.mm20.launcher2.transition.ChangingLayoutTransition import de.mm20.launcher2.ui.R +import org.koin.androidx.viewmodel.ext.android.viewModel import kotlin.math.abs class SwipeCardView @JvmOverloads constructor( @@ -376,10 +377,10 @@ class FavoriteSwipeAction(val context: Context, val searchable: Searchable) : ContextCompat.getColor(context, R.color.amber), { false } ) { - val pinned = - ViewModelProvider(context as AppCompatActivity)[FavoritesViewModel::class.java].isPinned( - searchable - ) + val viewModel: FavoritesViewModel by (context as AppCompatActivity).viewModel() + + private val pinned = viewModel.isPinned(searchable) + init { pinned.observe(context as LifecycleOwner) { @@ -391,7 +392,7 @@ class FavoriteSwipeAction(val context: Context, val searchable: Searchable) : if (pinned) { icon = R.drawable.ic_star_outline action = { - ViewModelProvider(context as AppCompatActivity)[FavoritesViewModel::class.java].unpinItem( + viewModel.unpinItem( searchable ) false @@ -399,7 +400,7 @@ class FavoriteSwipeAction(val context: Context, val searchable: Searchable) : } else { icon = R.drawable.ic_star_solid action = { - ViewModelProvider(context as AppCompatActivity)[FavoritesViewModel::class.java].pinItem( + viewModel.pinItem( searchable ) false @@ -413,10 +414,8 @@ class HideSwipeAction(val context: Context, val searchable: Searchable) : SwipeC ContextCompat.getColor(context, R.color.blue), { false } ) { - val hidden = - ViewModelProvider(context as AppCompatActivity)[FavoritesViewModel::class.java].isHidden( - searchable - ) + val viewModel: FavoritesViewModel by (context as AppCompatActivity).viewModel() + private val hidden = viewModel.isHidden(searchable) init { hidden.observe(context as LifecycleOwner) { @@ -428,7 +427,7 @@ class HideSwipeAction(val context: Context, val searchable: Searchable) : SwipeC if (hidden) { icon = R.drawable.ic_visibility action = { - ViewModelProvider(context as AppCompatActivity)[FavoritesViewModel::class.java].unhideItem( + viewModel.unhideItem( searchable ) true @@ -436,7 +435,7 @@ class HideSwipeAction(val context: Context, val searchable: Searchable) : SwipeC } else { icon = R.drawable.ic_visibility_off action = { - ViewModelProvider(context as AppCompatActivity)[FavoritesViewModel::class.java].hideItem( + viewModel.hideItem( searchable ) true diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/ToolbarView.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/ToolbarView.kt index cccf81aa..75358fea 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/ToolbarView.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/view/ToolbarView.kt @@ -11,16 +11,20 @@ import androidx.appcompat.widget.PopupMenu import androidx.appcompat.widget.TooltipCompat import androidx.core.view.setPadding import androidx.lifecycle.Observer -import androidx.lifecycle.ViewModelProvider -import de.mm20.launcher2.ui.R import de.mm20.launcher2.favorites.FavoritesViewModel import de.mm20.launcher2.ktx.dp import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.ui.R +import org.koin.androidx.viewmodel.ext.android.viewModel class ToolbarView : LinearLayout { constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) - constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(context, attrs, defStyleRes) + constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super( + context, + attrs, + defStyleRes + ) private val slots = context.resources.getInteger(R.integer.config_toolbarSlots) @@ -53,7 +57,8 @@ class ToolbarView : LinearLayout { overflowMenuIcon.isClickable = true overflowMenuIcon.isFocusable = true overflowMenuIcon.setPadding((12 * dp).toInt()) - overflowMenuIcon.layoutParams = LayoutParams((48 * dp).toInt(), (48 * dp).toInt()) + overflowMenuIcon.layoutParams = + LayoutParams((48 * dp).toInt(), (48 * dp).toInt()) overflowMenuIcon.setImageResource(R.drawable.ic_more_vert) removeViewAt(leftActions.size - 1) addView(overflowMenuIcon, leftActions.size - 1) @@ -88,7 +93,8 @@ class ToolbarView : LinearLayout { overflowMenuIcon.isClickable = true overflowMenuIcon.isFocusable = true overflowMenuIcon.setPadding((12 * dp).toInt()) - overflowMenuIcon.layoutParams = LayoutParams((48 * dp).toInt(), (48 * dp).toInt()) + overflowMenuIcon.layoutParams = + LayoutParams((48 * dp).toInt(), (48 * dp).toInt()) overflowMenuIcon.setImageResource(R.drawable.ic_more_vert) removeViewAt(childCount - 1) addView(overflowMenuIcon) @@ -147,15 +153,16 @@ class ToolbarView : LinearLayout { } TooltipCompat.setTooltipText(imageView, action.title) - val submenu = if (action.subActions.isEmpty()) null else PopupMenu(context, imageView).apply { - for ((i, subAction) in action.subActions.withIndex()) { - menu.add(0, i, 0, subAction.title) + val submenu = + if (action.subActions.isEmpty()) null else PopupMenu(context, imageView).apply { + for ((i, subAction) in action.subActions.withIndex()) { + menu.add(0, i, 0, subAction.title) + } + setOnMenuItemClickListener { + action.subActions[it.itemId].clickAction.invoke() + true + } } - setOnMenuItemClickListener { - action.subActions[it.itemId].clickAction.invoke() - true - } - } imageView.setOnClickListener { _ -> if (submenu != null) { @@ -213,13 +220,12 @@ open class ToolbarSubaction(val title: String, var clickAction: (() -> Unit)) { } -class FavoriteToolbarAction(val context: Context, val item: Searchable) - : ToolbarAction( - R.drawable.ic_star_outline, - context.getString(R.string.favorites_menu_pin) +class FavoriteToolbarAction(val context: Context, val item: Searchable) : ToolbarAction( + R.drawable.ic_star_outline, + context.getString(R.string.favorites_menu_pin) ) { - private val viewModel = ViewModelProvider(context as AppCompatActivity)[FavoritesViewModel::class.java] + private val viewModel: FavoritesViewModel by (context as AppCompatActivity).viewModel() private val isPinned = viewModel.isPinned(item) init { @@ -243,13 +249,12 @@ class FavoriteToolbarAction(val context: Context, val item: Searchable) } } -class VisibilityToolbarAction(val context: Context, val item: Searchable) - : ToolbarAction( - R.drawable.ic_visibility, - context.getString(R.string.menu_hide) +class VisibilityToolbarAction(val context: Context, val item: Searchable) : ToolbarAction( + R.drawable.ic_visibility, + context.getString(R.string.menu_hide) ) { - private val viewModel = ViewModelProvider(context as AppCompatActivity)[FavoritesViewModel::class.java] + private val viewModel: FavoritesViewModel by (context as AppCompatActivity).viewModel() private val isHidden = viewModel.isHidden(item) init { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/widget/CalendarWidget.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/widget/CalendarWidget.kt index 3830d2d8..8e500117 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/widget/CalendarWidget.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/widget/CalendarWidget.kt @@ -23,6 +23,7 @@ import de.mm20.launcher2.search.data.MissingPermission import de.mm20.launcher2.search.data.Searchable import de.mm20.launcher2.ui.R import kotlinx.android.synthetic.main.view_calendar_widget.view.* +import org.koin.androidx.viewmodel.ext.android.viewModel import java.util.* import kotlin.math.max import kotlin.math.min @@ -119,8 +120,8 @@ class CalendarWidget : LauncherWidget { selectedDay = availableDays[i - 1] } - val viewModel = ViewModelProvider(context as AppCompatActivity)[CalendarViewModel::class.java] - val favViewModel = ViewModelProvider(context as AppCompatActivity)[FavoritesViewModel::class.java] + val viewModel: CalendarViewModel by (context as AppCompatActivity).viewModel() + val favViewModel: FavoritesViewModel by (context as AppCompatActivity).viewModel() calendarEvents = viewModel.upcomingCalendarEvents pinnedCalendarEvents = favViewModel.pinnedCalendarEvents diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/widget/MusicWidget.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/widget/MusicWidget.kt index 3bc60cab..e188d9f1 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/widget/MusicWidget.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/widget/MusicWidget.kt @@ -22,11 +22,13 @@ import de.mm20.launcher2.ktx.dp import de.mm20.launcher2.legacy.helper.ActivityStarter import de.mm20.launcher2.music.MusicViewModel import de.mm20.launcher2.music.PlaybackState +import de.mm20.launcher2.search.SearchViewModel import de.mm20.launcher2.ui.LegacyLauncherTheme import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.widget.MusicWidget import de.mm20.launcher2.ui.widget.WeatherWidget import kotlinx.android.synthetic.main.compact_music.view.* +import org.koin.androidx.viewmodel.ext.android.viewModel class MusicWidget : LauncherWidget { @@ -44,7 +46,7 @@ class MusicWidget : LauncherWidget { override val name: String get() = context.getString(R.string.widget_name_music) - private val viewModel = ViewModelProvider(context as AppCompatActivity)[MusicViewModel::class.java] + private val viewModel: MusicViewModel by (context as AppCompatActivity).viewModel() constructor(context: Context) : super(context) constructor(context: Context, attrs: AttributeSet?) : super(context, attrs) @@ -79,7 +81,7 @@ class MusicWidget : LauncherWidget { class MusicCompactView : FrameLayout, CompactView { - private val viewModel = ViewModelProvider(context as AppCompatActivity)[MusicViewModel::class.java] + private val viewModel: MusicViewModel by (context as AppCompatActivity).viewModel() override fun setTranslucent(translucent: Boolean) { if (translucent) { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/legacy/widget/SmartWidget.kt b/ui/src/main/java/de/mm20/launcher2/ui/legacy/widget/SmartWidget.kt index 28e4b9a7..850b3ffe 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/legacy/widget/SmartWidget.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/legacy/widget/SmartWidget.kt @@ -217,63 +217,3 @@ class DateCompactView : TextClock, CompactView { } } -class RecommendedAppsCompactView @JvmOverloads constructor( - context: Context, attrs: AttributeSet? = null, defStyleAttr: Int = 0 -) : LinearLayout(context, attrs, defStyleAttr), CompactView { - - val viewModel = ViewModelProvider(context as AppCompatActivity)[FavoritesViewModel::class.java] - val items = viewModel.getTopFavorites(4) - - init { - clipChildren = false - clipToPadding = false - layoutTransition = LayoutTransition() - items.observe(context as LifecycleOwner, Observer { - removeAllViews() - for (s in it) { - val searchableView = LauncherIconView(context) - searchableView.icon = s.getPlaceholderIcon(context) - lifecycleScope.launch { - IconRepository.getInstance(context).getIcon(s, (48 * dp).toInt()).collect { - searchableView.icon = it - } - } - val frameLayout = FrameLayout(context) - frameLayout.clipChildren = false - frameLayout.clipToPadding = false - searchableView.layoutParams = FrameLayout.LayoutParams( - (48 * dp).toInt(), - (48 * dp).toInt() - ).also { - it.gravity = Gravity.CENTER - } - searchableView.badge = BadgeProvider.getInstance(context).getLiveBadge(s.badgeKey) - searchableView.elevation = 2 * dp - searchableView.setOnClickListener { - if(!ActivityStarter.start(context, searchableView, s)) { - searchableView.performLongClick() - } - } - searchableView.setOnLongClickListener { - SearchableBottomSheet(s).show((context as AppCompatActivity).supportFragmentManager, null) - return@setOnLongClickListener true - } - frameLayout.addView(searchableView) - frameLayout.layoutParams = LayoutParams( - 0, - (48 * dp).toInt() - ).also { - it.weight = 1f - } - addView(frameLayout) - } - }) - } - - override fun setTranslucent(translucent: Boolean) { - - } - - override var goToParent: (() -> Unit)? = null - -} \ No newline at end of file diff --git a/ui/src/main/java/de/mm20/launcher2/ui/search/ApplicationResults.kt b/ui/src/main/java/de/mm20/launcher2/ui/search/ApplicationResults.kt index 5687d6b5..03d1cab0 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/search/ApplicationResults.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/search/ApplicationResults.kt @@ -7,10 +7,12 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.applications.AppViewModel +import org.koin.androidx.compose.getViewModel @Composable fun applicationResults(): LazyListScope.(listState: LazyListState) -> Unit { - val apps by viewModel().applications.observeAsState(emptyList()) + val viewModel: AppViewModel = getViewModel() + val apps by viewModel.applications.observeAsState(emptyList()) return { SearchableGrid(items = apps, listState = it) } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/search/CalculatorItem.kt b/ui/src/main/java/de/mm20/launcher2/ui/search/CalculatorItem.kt index 29432c5b..a7ccf381 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/search/CalculatorItem.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/search/CalculatorItem.kt @@ -16,10 +16,12 @@ import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.calculator.CalculatorViewModel import de.mm20.launcher2.ui.component.SectionDivider +import org.koin.androidx.compose.getViewModel @Composable fun calculatorItem(): LazyListScope.() -> Unit { - val calculator by viewModel().calculator.observeAsState() + val viewModel: CalculatorViewModel = getViewModel() + val calculator by viewModel.calculator.observeAsState() return { calculator?.let { item { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/search/FavoriteResults.kt b/ui/src/main/java/de/mm20/launcher2/ui/search/FavoriteResults.kt index 882edc5a..f6b37fbf 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/search/FavoriteResults.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/search/FavoriteResults.kt @@ -7,10 +7,13 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.favorites.FavoritesViewModel +import org.koin.androidx.compose.getViewModel @Composable fun favoriteResults(): LazyListScope.(listState: LazyListState) -> Unit { - val favorites by viewModel().getFavorites(5).observeAsState(emptyList()) + val viewModel: FavoritesViewModel = getViewModel() + + val favorites by viewModel.getFavorites(5).observeAsState(emptyList()) return { SearchableGrid(items = favorites, listState = it) } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/search/FileResults.kt b/ui/src/main/java/de/mm20/launcher2/ui/search/FileResults.kt index 7787e863..b58d3428 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/search/FileResults.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/search/FileResults.kt @@ -6,10 +6,12 @@ import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.files.FilesViewModel +import org.koin.androidx.compose.getViewModel @Composable fun fileResults(): LazyListScope.() -> Unit { - val files by viewModel().files.observeAsState(emptyList()) + val viewModel: FilesViewModel = getViewModel() + val files by viewModel.files.observeAsState(emptyList()) return { files?.let { SearchableList(items = it) } } diff --git a/ui/src/main/java/de/mm20/launcher2/ui/search/WikipediaResult.kt b/ui/src/main/java/de/mm20/launcher2/ui/search/WikipediaResult.kt index d9cfdb16..6462368b 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/search/WikipediaResult.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/search/WikipediaResult.kt @@ -6,13 +6,13 @@ import androidx.compose.runtime.Composable import androidx.compose.runtime.getValue import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.ui.component.SectionDivider import de.mm20.launcher2.wikipedia.WikipediaViewModel +import org.koin.androidx.compose.getViewModel @Composable fun wikipediaResult(): LazyListScope.() -> Unit { - val viewModel = viewModel() + val viewModel: WikipediaViewModel = getViewModel() val wikipedia by viewModel.wikipedia.observeAsState() return { wikipedia?.let { diff --git a/ui/src/main/java/de/mm20/launcher2/ui/widget/CalendarWidget.kt b/ui/src/main/java/de/mm20/launcher2/ui/widget/CalendarWidget.kt index c040b2ad..9e111f27 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/widget/CalendarWidget.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/widget/CalendarWidget.kt @@ -28,6 +28,7 @@ import de.mm20.launcher2.ui.LauncherTheme import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.pluralResource import de.mm20.launcher2.ui.searchable.DeprecatedSearchableList +import org.koin.androidx.compose.getViewModel import java.util.* import kotlin.math.max import kotlin.math.min @@ -35,7 +36,7 @@ import kotlin.math.min @Composable fun CalendarWidget() { - val viewModel: CalendarViewModel = viewModel() + val viewModel: CalendarViewModel = getViewModel() val favViewModel: FavoritesViewModel = viewModel() val events by viewModel.upcomingCalendarEvents.observeAsState() diff --git a/ui/src/main/java/de/mm20/launcher2/ui/widget/MusicWidget.kt b/ui/src/main/java/de/mm20/launcher2/ui/widget/MusicWidget.kt index 1d958bf0..8fd29cec 100644 --- a/ui/src/main/java/de/mm20/launcher2/ui/widget/MusicWidget.kt +++ b/ui/src/main/java/de/mm20/launcher2/ui/widget/MusicWidget.kt @@ -21,18 +21,16 @@ import androidx.compose.runtime.livedata.observeAsState import androidx.compose.ui.Alignment import androidx.compose.ui.ExperimentalComposeUiApi import androidx.compose.ui.Modifier -import androidx.compose.ui.draw.rotate import androidx.compose.ui.graphics.asImageBitmap import androidx.compose.ui.platform.LocalContext import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp -import androidx.lifecycle.viewmodel.compose.viewModel import de.mm20.launcher2.music.MusicViewModel import de.mm20.launcher2.music.PlaybackState import de.mm20.launcher2.ui.R import de.mm20.launcher2.ui.ktx.conditional import de.mm20.launcher2.ui.locals.LocalColorScheme -import de.mm20.launcher2.ui.orange +import org.koin.androidx.compose.getViewModel @OptIn( ExperimentalFoundationApi::class, ExperimentalComposeUiApi::class, @@ -41,7 +39,7 @@ import de.mm20.launcher2.ui.orange @Composable fun MusicWidget() { - val viewModel: MusicViewModel = viewModel() + val viewModel: MusicViewModel = getViewModel() val albumArt by viewModel.albumArt.observeAsState() val title by viewModel.title.observeAsState() diff --git a/unitconverter/build.gradle.kts b/unitconverter/build.gradle.kts index 2232fa87..cb88959e 100644 --- a/unitconverter/build.gradle.kts +++ b/unitconverter/build.gradle.kts @@ -42,6 +42,8 @@ dependencies { implementation(libs.bundles.androidx.lifecycle) + implementation(libs.koin.android) + implementation(project(":preferences")) implementation(project(":currencies")) implementation(project(":search")) diff --git a/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/Module.kt b/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/Module.kt new file mode 100644 index 00000000..19e17408 --- /dev/null +++ b/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/Module.kt @@ -0,0 +1,12 @@ +package de.mm20.launcher2.unitconverter + +import de.mm20.launcher2.currencies.CurrencyRepository +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val unitConverterModule = module { + single { CurrencyRepository(androidContext()) } + single { UnitConverterRepository(androidContext()) } + viewModel { UnitConverterViewModel(get()) } +} \ No newline at end of file diff --git a/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/UnitConverterRepository.kt b/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/UnitConverterRepository.kt index 30a6f168..20dacb04 100644 --- a/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/UnitConverterRepository.kt +++ b/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/UnitConverterRepository.kt @@ -6,25 +6,11 @@ import de.mm20.launcher2.currencies.CurrencyRepository import de.mm20.launcher2.search.BaseSearchableRepository import de.mm20.launcher2.search.data.UnitConverter -class UnitConverterRepository private constructor(val context: Context) : BaseSearchableRepository() { - - init { - CurrencyRepository.getInstance(context) - } +class UnitConverterRepository(val context: Context) : BaseSearchableRepository() { val unitConverter = MutableLiveData() override suspend fun search(query: String) { unitConverter.value = UnitConverter.search(context, query) } - - companion object { - private lateinit var instance : UnitConverterRepository - - fun getInstance(context: Context) : UnitConverterRepository { - if(!::instance.isInitialized) instance = UnitConverterRepository(context.applicationContext) - return instance - } - } - } \ No newline at end of file diff --git a/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/UnitConverterViewModel.kt b/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/UnitConverterViewModel.kt index 508d218d..669f5c5a 100644 --- a/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/UnitConverterViewModel.kt +++ b/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/UnitConverterViewModel.kt @@ -1,8 +1,9 @@ package de.mm20.launcher2.unitconverter -import android.app.Application -import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel -class UnitConverterViewModel(app: Application): AndroidViewModel(app) { - val unitConverter = UnitConverterRepository.getInstance(app).unitConverter +class UnitConverterViewModel( + unitConverterRepository: UnitConverterRepository +): ViewModel() { + val unitConverter = unitConverterRepository.unitConverter } \ No newline at end of file diff --git a/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/converters/CurrencyConverter.kt b/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/converters/CurrencyConverter.kt index 480bf916..8b138007 100644 --- a/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/converters/CurrencyConverter.kt +++ b/unitconverter/src/main/java/de/mm20/launcher2/unitconverter/converters/CurrencyConverter.kt @@ -3,7 +3,6 @@ package de.mm20.launcher2.unitconverter.converters import android.content.Context import android.icu.text.PluralRules import android.icu.util.Currency -import android.os.Build import de.mm20.launcher2.currencies.CurrencyRepository import de.mm20.launcher2.search.data.CurrencyUnitConverter import de.mm20.launcher2.search.data.UnitConverter @@ -13,12 +12,14 @@ import java.text.DecimalFormat import java.util.Locale import java.util.Currency as JCurrency import kotlin.math.abs +import org.koin.core.component.KoinComponent +import org.koin.core.component.inject -class CurrencyConverter(context: Context) : Converter() { +class CurrencyConverter(context: Context) : Converter(), KoinComponent { override val dimension: Dimension = Dimension.Currency - val repository = CurrencyRepository.getInstance(context) + val repository: CurrencyRepository by inject() private val topCurrencies = arrayOf("USD", "EUR", "JPY", "GBP", "AUD") diff --git a/websites/build.gradle.kts b/websites/build.gradle.kts index f8ee526b..2252eb2f 100644 --- a/websites/build.gradle.kts +++ b/websites/build.gradle.kts @@ -49,6 +49,8 @@ dependencies { implementation(libs.textdrawable) implementation(libs.jsoup) + implementation(libs.koin.android) + implementation(project(":preferences")) implementation(project(":search")) implementation(project(":base")) diff --git a/websites/src/main/java/de/mm20/launcher2/search/data/Website.kt b/websites/src/main/java/de/mm20/launcher2/search/data/Website.kt index c31a5ea1..e93cf989 100644 --- a/websites/src/main/java/de/mm20/launcher2/search/data/Website.kt +++ b/websites/src/main/java/de/mm20/launcher2/search/data/Website.kt @@ -39,17 +39,6 @@ class Website( val color: Int ) : Searchable() { - override fun serialize(): String { - return jsonObjectOf( - "label" to label, - "url" to url, - "description" to description, - "image" to image, - "favicon" to favicon, - "color" to color - ).toString() - } - override val key = "web://$url" override suspend fun loadIconAsync(context: Context, size: Int): LauncherIcon? { if (favicon.isEmpty()) return null @@ -159,17 +148,5 @@ class Website( "" } } - - fun deserialize(serialized: String): Website { - val json = JSONObject(serialized) - return Website( - label = json.getString("label"), - favicon = json.getString("favicon"), - image = json.getString("image"), - description = json.getString("description"), - url = json.getString("url"), - color = json.getInt("color") - ) - } } } \ No newline at end of file diff --git a/websites/src/main/java/de/mm20/launcher2/websites/Module.kt b/websites/src/main/java/de/mm20/launcher2/websites/Module.kt new file mode 100644 index 00000000..3617c6ce --- /dev/null +++ b/websites/src/main/java/de/mm20/launcher2/websites/Module.kt @@ -0,0 +1,12 @@ +package de.mm20.launcher2.websites + +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val websitesModule = module { + single { WebsiteRepository(androidContext()) } + viewModel { + WebsiteViewModel(get()) + } +} \ No newline at end of file diff --git a/websites/src/main/java/de/mm20/launcher2/websites/WebsiteRepository.kt b/websites/src/main/java/de/mm20/launcher2/websites/WebsiteRepository.kt index eff7e824..6b2b42c9 100644 --- a/websites/src/main/java/de/mm20/launcher2/websites/WebsiteRepository.kt +++ b/websites/src/main/java/de/mm20/launcher2/websites/WebsiteRepository.kt @@ -9,16 +9,16 @@ import kotlinx.coroutines.withContext import okhttp3.OkHttpClient import java.util.concurrent.TimeUnit -class WebsiteRepository private constructor(val context: Context): BaseSearchableRepository() { +class WebsiteRepository(val context: Context) : BaseSearchableRepository() { val website = MutableLiveData() private val httpClient = OkHttpClient - .Builder() - .connectTimeout(200, TimeUnit.MILLISECONDS) - .readTimeout(3000, TimeUnit.MILLISECONDS) - .writeTimeout(1000, TimeUnit.MILLISECONDS) - .build() + .Builder() + .connectTimeout(200, TimeUnit.MILLISECONDS) + .readTimeout(3000, TimeUnit.MILLISECONDS) + .writeTimeout(1000, TimeUnit.MILLISECONDS) + .build() override fun onCancel() { super.onCancel() @@ -40,13 +40,4 @@ class WebsiteRepository private constructor(val context: Context): BaseSearchabl } website.value = wiki } - - companion object { - private lateinit var instance: WebsiteRepository - - fun getInstance(context: Context): WebsiteRepository { - if(!::instance.isInitialized) instance = WebsiteRepository(context.applicationContext) - return instance - } - } } \ No newline at end of file diff --git a/websites/src/main/java/de/mm20/launcher2/websites/WebsiteSerialization.kt b/websites/src/main/java/de/mm20/launcher2/websites/WebsiteSerialization.kt new file mode 100644 index 00000000..2b58b360 --- /dev/null +++ b/websites/src/main/java/de/mm20/launcher2/websites/WebsiteSerialization.kt @@ -0,0 +1,39 @@ +package de.mm20.launcher2.websites + +import de.mm20.launcher2.ktx.jsonObjectOf +import de.mm20.launcher2.search.SearchableDeserializer +import de.mm20.launcher2.search.SearchableSerializer +import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.data.Website +import org.json.JSONObject + +class WebsiteSerializer : SearchableSerializer { + override fun serialize(searchable: Searchable): String { + searchable as Website + return jsonObjectOf( + "label" to searchable.label, + "url" to searchable.url, + "description" to searchable.description, + "image" to searchable.image, + "favicon" to searchable.favicon, + "color" to searchable.color + ).toString() + } + + override val typePrefix: String + get() = "website" +} + +class WebsiteDeserializer: SearchableDeserializer { + override fun deserialize(serialized: String): Searchable? { + val json = JSONObject(serialized) + return Website( + label = json.getString("label"), + favicon = json.getString("favicon"), + image = json.getString("image"), + description = json.getString("description"), + url = json.getString("url"), + color = json.getInt("color") + ) + } +} \ No newline at end of file diff --git a/websites/src/main/java/de/mm20/launcher2/websites/WebsiteViewModel.kt b/websites/src/main/java/de/mm20/launcher2/websites/WebsiteViewModel.kt index de68fd25..6e67dde2 100644 --- a/websites/src/main/java/de/mm20/launcher2/websites/WebsiteViewModel.kt +++ b/websites/src/main/java/de/mm20/launcher2/websites/WebsiteViewModel.kt @@ -1,10 +1,11 @@ package de.mm20.launcher2.websites -import android.app.Application -import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel import de.mm20.launcher2.search.data.Website -class WebsiteViewModel(val app: Application): AndroidViewModel(app) { - val website: LiveData = WebsiteRepository.getInstance(app).website +class WebsiteViewModel( + websiteRepository: WebsiteRepository +): ViewModel() { + val website: LiveData = websiteRepository.website } \ No newline at end of file diff --git a/widgets/build.gradle.kts b/widgets/build.gradle.kts index 0658278a..6e9ca7ef 100644 --- a/widgets/build.gradle.kts +++ b/widgets/build.gradle.kts @@ -46,6 +46,8 @@ dependencies { implementation(libs.materialcomponents) + implementation(libs.koin.android) + implementation(project(":weather")) implementation(project(":calendar")) implementation(project(":music")) diff --git a/widgets/src/main/java/de/mm20/launcher2/widgets/Module.kt b/widgets/src/main/java/de/mm20/launcher2/widgets/Module.kt new file mode 100644 index 00000000..1c4276f4 --- /dev/null +++ b/widgets/src/main/java/de/mm20/launcher2/widgets/Module.kt @@ -0,0 +1,10 @@ +package de.mm20.launcher2.widgets + +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val widgetsModule = module { + single { WidgetRepository(androidContext()) } + viewModel { WidgetViewModel(get(), get()) } +} \ No newline at end of file diff --git a/widgets/src/main/java/de/mm20/launcher2/widgets/WidgetRepository.kt b/widgets/src/main/java/de/mm20/launcher2/widgets/WidgetRepository.kt index 356c13a6..10827f41 100644 --- a/widgets/src/main/java/de/mm20/launcher2/widgets/WidgetRepository.kt +++ b/widgets/src/main/java/de/mm20/launcher2/widgets/WidgetRepository.kt @@ -8,7 +8,7 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.withContext import java.util.concurrent.Executors -internal class WidgetRepository( +class WidgetRepository( val context: Context ) { diff --git a/widgets/src/main/java/de/mm20/launcher2/widgets/WidgetViewModel.kt b/widgets/src/main/java/de/mm20/launcher2/widgets/WidgetViewModel.kt index d650eeb6..74f65a58 100644 --- a/widgets/src/main/java/de/mm20/launcher2/widgets/WidgetViewModel.kt +++ b/widgets/src/main/java/de/mm20/launcher2/widgets/WidgetViewModel.kt @@ -1,18 +1,17 @@ package de.mm20.launcher2.widgets -import android.app.Application -import androidx.lifecycle.AndroidViewModel +import androidx.lifecycle.ViewModel import androidx.lifecycle.viewModelScope import de.mm20.launcher2.calendar.CalendarRepository import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import kotlinx.coroutines.withContext -class WidgetViewModel(app: Application) : AndroidViewModel(app) { +class WidgetViewModel( + private val widgetRepository: WidgetRepository, + private val calendarRepository: CalendarRepository +) : ViewModel() { - private val widgetRepository: WidgetRepository by lazy { - WidgetRepository(app) - } suspend fun getWidgets(): List { return withContext(viewModelScope.coroutineContext + Dispatchers.IO) { @@ -30,10 +29,6 @@ class WidgetViewModel(app: Application) : AndroidViewModel(app) { return widgetRepository.getInternalWidgets() } - private val calendarRepository: CalendarRepository by lazy { - CalendarRepository.getInstance(app) - } - fun requestCalendarUpdate() { calendarRepository.requestCalendarUpdate() } diff --git a/wikipedia/build.gradle.kts b/wikipedia/build.gradle.kts index 7bcd96e5..9d483bd3 100644 --- a/wikipedia/build.gradle.kts +++ b/wikipedia/build.gradle.kts @@ -46,6 +46,8 @@ dependencies { implementation(libs.okhttp) implementation(libs.bundles.retrofit) + implementation(libs.koin.android) + implementation(project(":preferences")) implementation(project(":search")) implementation(project(":base")) diff --git a/wikipedia/src/main/java/de/mm20/launcher2/search/data/Wikipedia.kt b/wikipedia/src/main/java/de/mm20/launcher2/search/data/Wikipedia.kt index 0b3e1587..63993357 100644 --- a/wikipedia/src/main/java/de/mm20/launcher2/search/data/Wikipedia.kt +++ b/wikipedia/src/main/java/de/mm20/launcher2/search/data/Wikipedia.kt @@ -52,15 +52,6 @@ class Wikipedia( return intent.intent } - override fun serialize(): String { - val json = JSONObject() - json.put("label", label) - json.put("text", text) - json.put("id", id) - json.put("image", image) - return json.toString() - } - companion object { fun search(context: Context, query: String, client: OkHttpClient): Wikipedia? { @@ -121,15 +112,5 @@ class Wikipedia( .optJSONObject("thumbnail") ?.getString("source") } - - fun deserialize(serialized: String): Wikipedia { - val json = JSONObject(serialized) - return Wikipedia( - label = json.getString("label"), - text = json.getString("text"), - id = json.getLong("id"), - image = json.optString("image") - ) - } } } \ No newline at end of file diff --git a/wikipedia/src/main/java/de/mm20/launcher2/wikipedia/Module.kt b/wikipedia/src/main/java/de/mm20/launcher2/wikipedia/Module.kt new file mode 100644 index 00000000..711d4f6b --- /dev/null +++ b/wikipedia/src/main/java/de/mm20/launcher2/wikipedia/Module.kt @@ -0,0 +1,10 @@ +package de.mm20.launcher2.wikipedia + +import org.koin.android.ext.koin.androidContext +import org.koin.androidx.viewmodel.dsl.viewModel +import org.koin.dsl.module + +val wikipediaModule = module { + single { WikipediaRepository(androidContext()) } + viewModel { WikipediaViewModel(get()) } +} \ No newline at end of file diff --git a/wikipedia/src/main/java/de/mm20/launcher2/wikipedia/WikipediaRepository.kt b/wikipedia/src/main/java/de/mm20/launcher2/wikipedia/WikipediaRepository.kt index 3a806849..a956a7db 100644 --- a/wikipedia/src/main/java/de/mm20/launcher2/wikipedia/WikipediaRepository.kt +++ b/wikipedia/src/main/java/de/mm20/launcher2/wikipedia/WikipediaRepository.kt @@ -1,7 +1,6 @@ package de.mm20.launcher2.wikipedia import android.content.Context -import androidx.core.text.HtmlCompat import androidx.lifecycle.MutableLiveData import de.mm20.launcher2.crashreporter.CrashReporter import de.mm20.launcher2.preferences.LauncherPreferences @@ -10,11 +9,9 @@ import de.mm20.launcher2.search.data.Wikipedia import okhttp3.OkHttpClient import retrofit2.Retrofit import retrofit2.converter.gson.GsonConverterFactory -import java.io.IOException import java.util.concurrent.TimeUnit -import kotlin.math.min -class WikipediaRepository private constructor(val context: Context) : BaseSearchableRepository() { +class WikipediaRepository(val context: Context) : BaseSearchableRepository() { val wikipedia = MutableLiveData() @@ -85,14 +82,4 @@ class WikipediaRepository private constructor(val context: Context) : BaseSearch wikipedia.value = wiki } - - companion object { - private lateinit var instance: WikipediaRepository - - fun getInstance(context: Context): WikipediaRepository { - if (!::instance.isInitialized) instance = - WikipediaRepository(context.applicationContext) - return instance - } - } } \ No newline at end of file diff --git a/wikipedia/src/main/java/de/mm20/launcher2/wikipedia/WikipediaSerialization.kt b/wikipedia/src/main/java/de/mm20/launcher2/wikipedia/WikipediaSerialization.kt new file mode 100644 index 00000000..6d602913 --- /dev/null +++ b/wikipedia/src/main/java/de/mm20/launcher2/wikipedia/WikipediaSerialization.kt @@ -0,0 +1,34 @@ +package de.mm20.launcher2.wikipedia + +import de.mm20.launcher2.search.SearchableDeserializer +import de.mm20.launcher2.search.SearchableSerializer +import de.mm20.launcher2.search.data.Searchable +import de.mm20.launcher2.search.data.Wikipedia +import org.json.JSONObject + +class WikipediaSerializer : SearchableSerializer { + override fun serialize(searchable: Searchable): String { + searchable as Wikipedia + val json = JSONObject() + json.put("label", searchable.label) + json.put("text", searchable.text) + json.put("id", searchable.id) + json.put("image", searchable.image) + return json.toString() + } + + override val typePrefix: String + get() = "wikipedia" +} + +class WikipediaDeserializer : SearchableDeserializer { + override fun deserialize(serialized: String): Searchable? { + val json = JSONObject(serialized) + return Wikipedia( + label = json.getString("label"), + text = json.getString("text"), + id = json.getLong("id"), + image = json.optString("image") + ) + } +} \ No newline at end of file diff --git a/wikipedia/src/main/java/de/mm20/launcher2/wikipedia/WikipediaViewModel.kt b/wikipedia/src/main/java/de/mm20/launcher2/wikipedia/WikipediaViewModel.kt index 7bef5fb8..522f9a86 100644 --- a/wikipedia/src/main/java/de/mm20/launcher2/wikipedia/WikipediaViewModel.kt +++ b/wikipedia/src/main/java/de/mm20/launcher2/wikipedia/WikipediaViewModel.kt @@ -1,10 +1,11 @@ package de.mm20.launcher2.wikipedia -import android.app.Application -import androidx.lifecycle.AndroidViewModel import androidx.lifecycle.LiveData +import androidx.lifecycle.ViewModel import de.mm20.launcher2.search.data.Wikipedia -class WikipediaViewModel(val app: Application): AndroidViewModel(app) { - val wikipedia: LiveData = WikipediaRepository.getInstance(app).wikipedia +class WikipediaViewModel( + wikipediaRepository: WikipediaRepository +): ViewModel() { + val wikipedia: LiveData = wikipediaRepository.wikipedia } \ No newline at end of file