Refactor search, favorites and calendar widget
This commit is contained in:
@@ -16,8 +16,8 @@ import androidx.compose.material.icons.rounded.VisibilityOff
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.collectAsState
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.scale
|
||||
@@ -26,11 +26,11 @@ import androidx.compose.ui.res.colorResource
|
||||
import androidx.compose.ui.unit.IntOffset
|
||||
import androidx.compose.ui.unit.LayoutDirection
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.favorites.FavoritesViewModel
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
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 org.koin.androidx.compose.inject
|
||||
import kotlin.math.roundToInt
|
||||
|
||||
@OptIn(ExperimentalMaterialApi::class, ExperimentalAnimationApi::class)
|
||||
@@ -41,19 +41,19 @@ fun DefaultSwipeActions(
|
||||
enabled: Boolean = true,
|
||||
content: @Composable RowScope.() -> Unit
|
||||
) {
|
||||
val viewModel: FavoritesViewModel = getViewModel()
|
||||
val repository: FavoritesRepository by inject()
|
||||
|
||||
val isPinned by viewModel.isPinned(item).observeAsState()
|
||||
val isHidden by viewModel.isHidden(item).observeAsState()
|
||||
val isPinned by repository.isPinned(item).collectAsState(false)
|
||||
val isHidden by repository.isHidden(item).collectAsState(false)
|
||||
|
||||
val state = androidx.compose.material.rememberSwipeableState(
|
||||
SwipeAction.Default,
|
||||
confirmStateChange = {
|
||||
if (it == SwipeAction.Favorites) {
|
||||
if (isPinned == true) {
|
||||
viewModel.unpinItem(item)
|
||||
repository.unpinItem(item)
|
||||
} else {
|
||||
viewModel.pinItem(item)
|
||||
repository.pinItem(item)
|
||||
}
|
||||
}
|
||||
false
|
||||
|
||||
@@ -31,11 +31,12 @@ import androidx.compose.ui.unit.dp
|
||||
import androidx.compose.ui.unit.sp
|
||||
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.launcher.search.SearchViewModel
|
||||
import de.mm20.launcher2.ui.locals.LocalNavController
|
||||
import de.mm20.launcher2.ui.locals.LocalWindowSize
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
import org.koin.androidx.compose.viewModel
|
||||
|
||||
/**
|
||||
* Search bar
|
||||
@@ -54,7 +55,7 @@ fun SearchBar(
|
||||
) {
|
||||
var searchQuery by remember { mutableStateOf("") }
|
||||
|
||||
val viewModel: SearchViewModel = getViewModel()
|
||||
val viewModel: SearchViewModel by viewModel()
|
||||
|
||||
LaunchedEffect(searchQuery) {
|
||||
viewModel.search(searchQuery)
|
||||
|
||||
@@ -14,11 +14,11 @@ import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.integerResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.favorites.FavoritesViewModel
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.R
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
import org.koin.androidx.compose.inject
|
||||
import kotlin.math.min
|
||||
|
||||
@Composable
|
||||
@@ -165,8 +165,8 @@ data class ToggleToolbarAction(
|
||||
|
||||
@Composable
|
||||
fun favoritesToolbarAction(item: Searchable): ToggleToolbarAction {
|
||||
val viewModel: FavoritesViewModel = getViewModel()
|
||||
val isPinned by viewModel.isPinned(item).observeAsState(false)
|
||||
val viewModel: FavoritesRepository by inject()
|
||||
val isPinned by viewModel.isPinned(item).collectAsState(false)
|
||||
|
||||
return ToggleToolbarAction(
|
||||
label = stringResource(
|
||||
@@ -186,8 +186,8 @@ fun favoritesToolbarAction(item: Searchable): ToggleToolbarAction {
|
||||
|
||||
@Composable
|
||||
fun hideToolbarAction(item: Searchable): ToggleToolbarAction {
|
||||
val viewModel: FavoritesViewModel = getViewModel()
|
||||
val isHidden by viewModel.isHidden(item).observeAsState(false)
|
||||
val viewModel: FavoritesRepository by inject()
|
||||
val isHidden by viewModel.isHidden(item).collectAsState(false)
|
||||
|
||||
return ToggleToolbarAction(
|
||||
label = stringResource(
|
||||
|
||||
@@ -0,0 +1,23 @@
|
||||
package de.mm20.launcher2.ui.launcher.modals
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.liveData
|
||||
import de.mm20.launcher2.favorites.FavoritesItem
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class EditFavoritesVM : ViewModel(), KoinComponent {
|
||||
|
||||
private val repository: FavoritesRepository by inject()
|
||||
|
||||
suspend fun getFavorites(): List<FavoritesItem> {
|
||||
return repository.getAllFavoriteItems()
|
||||
}
|
||||
|
||||
fun saveFavorites(favorites: List<FavoritesItem>) {
|
||||
repository.saveFavorites(favorites)
|
||||
}
|
||||
}
|
||||
+6
-15
@@ -1,37 +1,28 @@
|
||||
package de.mm20.launcher2.ui.legacy.component
|
||||
package de.mm20.launcher2.ui.launcher.modals
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.TextView
|
||||
import androidx.activity.viewModels
|
||||
import androidx.annotation.StringRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.AppCompatTextView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.updateMargins
|
||||
import androidx.core.widget.TextViewCompat
|
||||
import com.google.android.material.textview.MaterialTextView
|
||||
import de.mm20.launcher2.favorites.FavoritesItem
|
||||
import de.mm20.launcher2.favorites.FavoritesViewModel
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.ktx.lifecycleScope
|
||||
import de.mm20.launcher2.ktx.setPadding
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.databinding.DialogEditFavoritesBinding
|
||||
import de.mm20.launcher2.ui.databinding.EditFavoritesRowBinding
|
||||
import de.mm20.launcher2.ui.databinding.EditFavoritesTitleBinding
|
||||
import de.mm20.launcher2.ui.legacy.component.EditFavoritesRow
|
||||
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
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : FrameLayout(context, attrs) {
|
||||
|
||||
val viewModel : FavoritesViewModel by (context as AppCompatActivity).viewModel()
|
||||
val viewModel: EditFavoritesVM by (context as AppCompatActivity).viewModels()
|
||||
|
||||
private val binding = DialogEditFavoritesBinding.inflate(LayoutInflater.from(context), this)
|
||||
|
||||
@@ -45,7 +36,7 @@ class EditFavoritesView @JvmOverloads constructor(
|
||||
|
||||
suspend fun initView() {
|
||||
favorites = withContext(Dispatchers.IO) {
|
||||
viewModel.getAllFavoriteItems().toMutableList()
|
||||
viewModel.getFavorites().toMutableList()
|
||||
}
|
||||
binding.progressBar.visibility = View.GONE
|
||||
binding.itemList.addView(getLabel(R.string.edit_favorites_dialog_stage0))
|
||||
@@ -0,0 +1,25 @@
|
||||
package de.mm20.launcher2.ui.launcher.modals
|
||||
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import kotlinx.coroutines.withContext
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class HiddenItemsVM: ViewModel(), KoinComponent {
|
||||
private val repository: FavoritesRepository by inject()
|
||||
|
||||
val hiddenItems = MutableLiveData<List<Searchable>>(emptyList())
|
||||
|
||||
suspend fun onActive() {
|
||||
withContext(Dispatchers.IO) {
|
||||
repository.getHiddenItems().collectLatest {
|
||||
hiddenItems.postValue(it)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package de.mm20.launcher2.ui.launcher.modals
|
||||
|
||||
import android.content.Context
|
||||
import android.util.AttributeSet
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.view.setMargins
|
||||
import androidx.core.widget.NestedScrollView
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.ktx.lifecycleScope
|
||||
import de.mm20.launcher2.ui.legacy.search.SearchGridView
|
||||
import kotlinx.coroutines.Job
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class HiddenItemsView @JvmOverloads constructor(
|
||||
context: Context, attrs: AttributeSet? = null
|
||||
) : NestedScrollView(context, attrs) {
|
||||
private val viewModel: HiddenItemsVM by (context as AppCompatActivity).viewModels()
|
||||
|
||||
init {
|
||||
clipChildren = false
|
||||
layoutParams = ViewGroup.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
)
|
||||
val hiddenItemsGrid = SearchGridView(context)
|
||||
hiddenItemsGrid.layoutParams = FrameLayout.LayoutParams(
|
||||
ViewGroup.LayoutParams.MATCH_PARENT,
|
||||
ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
).apply {
|
||||
setMargins((8 * dp).toInt())
|
||||
}
|
||||
val hiddenItems = viewModel.hiddenItems
|
||||
hiddenItems.observe(context as AppCompatActivity) {
|
||||
hiddenItemsGrid.submitItems(it)
|
||||
}
|
||||
addView(hiddenItemsGrid)
|
||||
}
|
||||
|
||||
private var job: Job? = null
|
||||
|
||||
override fun onAttachedToWindow() {
|
||||
super.onAttachedToWindow()
|
||||
val job = Job()
|
||||
this.job = job
|
||||
lifecycleScope.launch(job) {
|
||||
viewModel.onActive()
|
||||
}
|
||||
}
|
||||
|
||||
override fun onDetachedFromWindow() {
|
||||
super.onDetachedFromWindow()
|
||||
job?.cancel()
|
||||
}
|
||||
}
|
||||
@@ -1,17 +1,116 @@
|
||||
package de.mm20.launcher2.ui.launcher.search
|
||||
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import de.mm20.launcher2.search.data.File
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.applications.AppRepository
|
||||
import de.mm20.launcher2.calculator.CalculatorRepository
|
||||
import de.mm20.launcher2.calendar.CalendarRepository
|
||||
import de.mm20.launcher2.contacts.ContactRepository
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.files.FileRepository
|
||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||
import de.mm20.launcher2.search.WebsearchRepository
|
||||
import de.mm20.launcher2.search.data.*
|
||||
import de.mm20.launcher2.unitconverter.UnitConverterRepository
|
||||
import de.mm20.launcher2.websites.WebsiteRepository
|
||||
import de.mm20.launcher2.wikipedia.WikipediaRepository
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class SearchViewModel: ViewModel(), KoinComponent {
|
||||
class SearchViewModel : ViewModel(), KoinComponent {
|
||||
|
||||
fun search(query: String) {
|
||||
private val favoritesRepository: FavoritesRepository by inject()
|
||||
|
||||
private val calendarRepository: CalendarRepository by inject()
|
||||
private val contactRepository: ContactRepository by inject()
|
||||
private val appRepository: AppRepository by inject()
|
||||
private val wikipediaRepository: WikipediaRepository by inject()
|
||||
private val unitConverterRepository: UnitConverterRepository by inject()
|
||||
private val calculatorRepository: CalculatorRepository by inject()
|
||||
private val websiteRepository: WebsiteRepository by inject()
|
||||
private val fileRepository: FileRepository by inject()
|
||||
private val websearchRepository: WebsearchRepository by inject()
|
||||
|
||||
val isSearching = MutableLiveData(false)
|
||||
|
||||
val favorites by lazy {
|
||||
favoritesRepository.getFavorites().asLiveData()
|
||||
}
|
||||
|
||||
val appResults = MutableLiveData<List<Application>>(emptyList())
|
||||
val fileResults = MutableLiveData<List<File>>(emptyList())
|
||||
val contactResults = MutableLiveData<List<Contact>>(emptyList())
|
||||
val calendarResults = MutableLiveData<List<CalendarEvent>>(emptyList())
|
||||
val wikipediaResult = MutableLiveData<Wikipedia?>(null)
|
||||
val websiteResult = MutableLiveData<Website?>(null)
|
||||
val calculatorResult = MutableLiveData<Calculator?>(null)
|
||||
val unitConverterResult = MutableLiveData<UnitConverter?>(null)
|
||||
val websearchResults = MutableLiveData<List<Websearch>>(emptyList())
|
||||
|
||||
val hideFavorites = MutableLiveData(false)
|
||||
|
||||
var searchJob: Job? = null
|
||||
fun search(query: String) {
|
||||
try {
|
||||
searchJob?.cancel()
|
||||
} catch (e: CancellationException) {
|
||||
}
|
||||
hideFavorites.postValue(query.isNotEmpty())
|
||||
searchJob = viewModelScope.launch {
|
||||
isSearching.postValue(true)
|
||||
val jobs = mutableListOf<Deferred<Any>>()
|
||||
jobs += async {
|
||||
appRepository.search(query).collectLatest {
|
||||
appResults.postValue(it)
|
||||
}
|
||||
}
|
||||
jobs += async {
|
||||
contactRepository.search(query).collectLatest {
|
||||
contactResults.postValue(it)
|
||||
}
|
||||
}
|
||||
jobs += async {
|
||||
calendarRepository.search(query).collectLatest {
|
||||
calendarResults.postValue(it)
|
||||
}
|
||||
}
|
||||
jobs += async {
|
||||
wikipediaRepository.search(query).collectLatest {
|
||||
wikipediaResult.postValue(it)
|
||||
}
|
||||
}
|
||||
jobs += async {
|
||||
unitConverterRepository.search(query).collectLatest {
|
||||
unitConverterResult.postValue(it)
|
||||
}
|
||||
}
|
||||
jobs += async {
|
||||
calculatorRepository.search(query).collectLatest {
|
||||
calculatorResult.postValue(it)
|
||||
}
|
||||
}
|
||||
jobs += async {
|
||||
websiteRepository.search(query).collectLatest {
|
||||
websiteResult.postValue(it)
|
||||
}
|
||||
}
|
||||
jobs += async {
|
||||
fileRepository.search(query).collectLatest {
|
||||
fileResults.postValue(it)
|
||||
}
|
||||
}
|
||||
jobs += async {
|
||||
websearchRepository.search(query).collectLatest {
|
||||
websearchResults.postValue(it)
|
||||
}
|
||||
}
|
||||
jobs.map { it.await() }
|
||||
isSearching.postValue(false)
|
||||
}
|
||||
}
|
||||
|
||||
private val _files = MutableLiveData<List<File>>()
|
||||
val files: LiveData<List<File>> = _files
|
||||
}
|
||||
@@ -0,0 +1,138 @@
|
||||
package de.mm20.launcher2.ui.launcher.widgets.calendar
|
||||
|
||||
import android.content.ContentUris
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.provider.CalendarContract
|
||||
import androidx.lifecycle.MutableLiveData
|
||||
import androidx.lifecycle.ViewModel
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.viewModelScope
|
||||
import de.mm20.launcher2.calendar.CalendarRepository
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
import de.mm20.launcher2.search.data.CalendarEvent
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.lang.Integer.min
|
||||
import java.time.*
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
|
||||
class CalendarWidgetVM : ViewModel(), KoinComponent {
|
||||
|
||||
private val calendarRepository: CalendarRepository by inject()
|
||||
private val favoritesRepository: FavoritesRepository by inject()
|
||||
|
||||
val calendarEvents = MutableLiveData<List<CalendarEvent>>(emptyList())
|
||||
val pinnedCalendarEvents =
|
||||
favoritesRepository.getPinnedCalendarEvents().asLiveData(viewModelScope.coroutineContext)
|
||||
var availableDates = listOf(LocalDate.now())
|
||||
|
||||
val permissionMissing = MutableLiveData(false)
|
||||
|
||||
private var showRunningPastDayEvents = false
|
||||
val hiddenPastEvents = MutableLiveData(0)
|
||||
|
||||
val selectedDate = MutableLiveData(LocalDate.now())
|
||||
|
||||
private var upcomingEvents: List<CalendarEvent> = emptyList()
|
||||
set(value) {
|
||||
field = value
|
||||
val dates = value.flatMap {
|
||||
val startDate =
|
||||
Instant.ofEpochMilli(it.startTime).atZone(ZoneId.systemDefault()).toLocalDate()
|
||||
val endDate =
|
||||
Instant.ofEpochMilli(it.startTime).atZone(ZoneId.systemDefault()).toLocalDate()
|
||||
return@flatMap listOf(
|
||||
startDate,
|
||||
endDate
|
||||
)
|
||||
}.union(listOf(LocalDate.now()))
|
||||
.distinct()
|
||||
.sorted()
|
||||
availableDates = dates
|
||||
val date = selectedDate.value?.takeIf { dates.contains(it) } ?: LocalDate.now()
|
||||
selectDate(date)
|
||||
}
|
||||
|
||||
|
||||
fun nextDay() {
|
||||
val dates = availableDates
|
||||
val date = selectedDate.value ?: return
|
||||
val currentIndex = dates.indexOf(date)
|
||||
val index = min(currentIndex + 1, dates.lastIndex)
|
||||
selectDate(dates[index])
|
||||
}
|
||||
|
||||
fun previousDay() {
|
||||
val dates = availableDates
|
||||
val date = selectedDate.value ?: return
|
||||
val currentIndex = dates.indexOf(date)
|
||||
val index = max(currentIndex - 1, 0)
|
||||
selectDate(dates[index])
|
||||
}
|
||||
|
||||
fun selectDate(date: LocalDate) {
|
||||
val dates = availableDates
|
||||
showRunningPastDayEvents = false
|
||||
if (dates.contains(date)) {
|
||||
selectedDate.value = date
|
||||
updateEvents()
|
||||
}
|
||||
}
|
||||
|
||||
fun showAllEvents() {
|
||||
showRunningPastDayEvents = true
|
||||
updateEvents()
|
||||
}
|
||||
|
||||
fun createEvent(context: Context) {
|
||||
val intent = Intent(Intent.ACTION_EDIT)
|
||||
intent.data = CalendarContract.Events.CONTENT_URI
|
||||
context.tryStartActivity(intent)
|
||||
}
|
||||
|
||||
fun openCalendarApp(context: Context) {
|
||||
val startMillis = System.currentTimeMillis()
|
||||
val builder = CalendarContract.CONTENT_URI.buildUpon()
|
||||
builder.appendPath("time")
|
||||
ContentUris.appendId(builder, startMillis)
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
.setData(builder.build())
|
||||
context.tryStartActivity(intent)
|
||||
}
|
||||
|
||||
private fun updateEvents() {
|
||||
val date = selectedDate.value ?: return
|
||||
val now = System.currentTimeMillis()
|
||||
val offset = OffsetDateTime.now().offset
|
||||
val dayStart = max(now, date.atStartOfDay().toEpochSecond(offset) * 1000)
|
||||
val dayEnd = date.plusDays(1).atStartOfDay().toEpochSecond(offset) * 1000
|
||||
var events = upcomingEvents.filter {
|
||||
it.endTime >= dayStart && it.startTime < dayEnd
|
||||
}
|
||||
|
||||
if (!showRunningPastDayEvents) {
|
||||
val totalCount = events.size
|
||||
|
||||
events = events.filter {
|
||||
it.startTime >= date.atStartOfDay().toEpochSecond(offset) * 1000
|
||||
}
|
||||
|
||||
val hiddenCount = totalCount - events.size
|
||||
hiddenPastEvents.postValue(hiddenCount)
|
||||
} else {
|
||||
hiddenPastEvents.postValue(0)
|
||||
}
|
||||
|
||||
calendarEvents.postValue(events)
|
||||
}
|
||||
|
||||
suspend fun onActive() {
|
||||
calendarRepository.getUpcomingEvents().collectLatest {
|
||||
upcomingEvents = it
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -14,7 +14,6 @@ import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.graphics.Color
|
||||
import android.graphics.Point
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import android.provider.Settings
|
||||
import android.util.Log
|
||||
@@ -23,7 +22,6 @@ import android.view.*
|
||||
import android.view.ViewGroup.LayoutParams.MATCH_PARENT
|
||||
import android.view.ViewGroup.LayoutParams.WRAP_CONTENT
|
||||
import android.view.inputmethod.InputMethodManager
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.LinearLayout
|
||||
import android.widget.Toast
|
||||
import androidx.activity.viewModels
|
||||
@@ -44,7 +42,6 @@ import com.afollestad.materialdialogs.callbacks.onDismiss
|
||||
import com.afollestad.materialdialogs.customview.customView
|
||||
import com.afollestad.materialdialogs.list.listItems
|
||||
import com.jmedeisis.draglinearlayout.DragLinearLayout
|
||||
import de.mm20.launcher2.favorites.FavoritesViewModel
|
||||
import de.mm20.launcher2.icons.DynamicIconController
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
@@ -53,15 +50,15 @@ import de.mm20.launcher2.ktx.isBrightColor
|
||||
import de.mm20.launcher2.legacy.helper.ActivityStarter
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||
import de.mm20.launcher2.search.SearchViewModel
|
||||
import de.mm20.launcher2.transition.ChangingLayoutTransition
|
||||
import de.mm20.launcher2.transition.OneShotLayoutTransition
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.databinding.ActivityLauncherBinding
|
||||
import de.mm20.launcher2.ui.legacy.component.EditFavoritesView
|
||||
import de.mm20.launcher2.ui.launcher.modals.EditFavoritesView
|
||||
import de.mm20.launcher2.ui.launcher.modals.HiddenItemsView
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchViewModel
|
||||
import de.mm20.launcher2.ui.legacy.component.WidgetView
|
||||
import de.mm20.launcher2.ui.legacy.helper.ThemeHelper
|
||||
import de.mm20.launcher2.ui.legacy.search.SearchGridView
|
||||
import de.mm20.launcher2.weather.WeatherViewModel
|
||||
import de.mm20.launcher2.widgets.Widget
|
||||
import de.mm20.launcher2.widgets.WidgetType
|
||||
@@ -79,37 +76,37 @@ class LauncherActivity : AppCompatActivity() {
|
||||
* True if the search result list is visible
|
||||
*/
|
||||
private var searchVisibility = false
|
||||
set(value) {
|
||||
field = value
|
||||
windowBackgroundBlur = value
|
||||
}
|
||||
set(value) {
|
||||
field = value
|
||||
windowBackgroundBlur = value
|
||||
}
|
||||
|
||||
private lateinit var widgetHost: AppWidgetHost
|
||||
private val widgets = mutableListOf<Widget>()
|
||||
|
||||
private lateinit var overlayView: ViewGroupOverlay
|
||||
|
||||
private val searchViewModel: SearchViewModel by viewModel()
|
||||
private val widgetViewModel: WidgetViewModel by viewModel()
|
||||
private val favoritesViewModel: FavoritesViewModel by viewModel()
|
||||
|
||||
private val searchViewModel: SearchViewModel by viewModels()
|
||||
|
||||
private val preferences = LauncherPreferences.instance
|
||||
|
||||
private var windowBackgroundBlur: Boolean = false
|
||||
set(value) {
|
||||
if(field == value) return
|
||||
field = value
|
||||
if (!isAtLeastApiLevel(31)) return
|
||||
window.attributes = window.attributes.also {
|
||||
if (value) {
|
||||
it.blurBehindRadius = (32 * dp).toInt()
|
||||
it.flags = it.flags or WindowManager.LayoutParams.FLAG_BLUR_BEHIND
|
||||
} else {
|
||||
it.blurBehindRadius = 0
|
||||
it.flags = it.flags and WindowManager.LayoutParams.FLAG_BLUR_BEHIND.inv()
|
||||
set(value) {
|
||||
if (field == value) return
|
||||
field = value
|
||||
if (!isAtLeastApiLevel(31)) return
|
||||
window.attributes = window.attributes.also {
|
||||
if (value) {
|
||||
it.blurBehindRadius = (32 * dp).toInt()
|
||||
it.flags = it.flags or WindowManager.LayoutParams.FLAG_BLUR_BEHIND
|
||||
} else {
|
||||
it.blurBehindRadius = 0
|
||||
it.flags = it.flags and WindowManager.LayoutParams.FLAG_BLUR_BEHIND.inv()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private var widgetEditMode = false
|
||||
set(value) {
|
||||
@@ -318,28 +315,11 @@ class LauncherActivity : AppCompatActivity() {
|
||||
)
|
||||
}
|
||||
R.id.menu_item_hidden -> {
|
||||
val layout = NestedScrollView(this)
|
||||
layout.clipChildren = false
|
||||
layout.layoutParams = ViewGroup.LayoutParams(
|
||||
MATCH_PARENT,
|
||||
WRAP_CONTENT
|
||||
)
|
||||
val hiddenItemsGrid = SearchGridView(this)
|
||||
hiddenItemsGrid.layoutParams = FrameLayout.LayoutParams(
|
||||
MATCH_PARENT,
|
||||
WRAP_CONTENT
|
||||
).apply {
|
||||
setMargins((8 * dp).toInt())
|
||||
}
|
||||
val hiddenItems = favoritesViewModel.hiddenItems
|
||||
hiddenItems.observe(this) {
|
||||
hiddenItemsGrid.submitItems(it)
|
||||
}
|
||||
layout.addView(hiddenItemsGrid)
|
||||
val view = HiddenItemsView(this)
|
||||
MaterialDialog(this, BottomSheet(LayoutMode.MATCH_PARENT))
|
||||
.show {
|
||||
title(R.string.menu_hidden_items)
|
||||
customView(view = layout)
|
||||
customView(view = view)
|
||||
negativeButton(R.string.close) { dismiss() }
|
||||
}
|
||||
//hiddenAppsActivated = true
|
||||
@@ -584,8 +564,6 @@ class LauncherActivity : AppCompatActivity() {
|
||||
ActivityStarter.create(binding.rootView)
|
||||
binding.activityStartOverlay.visibility = View.INVISIBLE
|
||||
|
||||
val widgetViewModel by viewModels<WidgetViewModel>()
|
||||
widgetViewModel.requestCalendarUpdate()
|
||||
search(binding.searchBar.getSearchQuery())
|
||||
|
||||
updateSystemBarAppearance()
|
||||
@@ -670,12 +648,8 @@ class LauncherActivity : AppCompatActivity() {
|
||||
PermissionsManager.LOCATION -> {
|
||||
ViewModelProvider(this).get(WeatherViewModel::class.java).requestUpdate(this)
|
||||
}
|
||||
PermissionsManager.CALENDAR -> {
|
||||
widgetViewModel.requestCalendarUpdate()
|
||||
}
|
||||
PermissionsManager.ALL -> {
|
||||
ViewModelProvider(this).get(WeatherViewModel::class.java).requestUpdate(this)
|
||||
widgetViewModel.requestCalendarUpdate()
|
||||
search(binding.searchBar.getSearchQuery())
|
||||
}
|
||||
}
|
||||
@@ -760,7 +734,8 @@ class LauncherActivity : AppCompatActivity() {
|
||||
binding.container.translationY = binding.searchBar.height.toFloat()
|
||||
|
||||
}
|
||||
windowBackgroundBlur = searchVisibility || newTransY > 0.6 * binding.searchBar.height
|
||||
windowBackgroundBlur =
|
||||
searchVisibility || newTransY > 0.6 * binding.searchBar.height
|
||||
|
||||
if (binding.container.translationY == 0f) return@onTouch false
|
||||
}
|
||||
|
||||
@@ -6,12 +6,12 @@ import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.*
|
||||
import de.mm20.launcher2.applications.AppViewModel
|
||||
import de.mm20.launcher2.search.data.Application
|
||||
import de.mm20.launcher2.ui.databinding.ViewApplicationBinding
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchViewModel
|
||||
|
||||
class ApplicationView : FrameLayout {
|
||||
|
||||
@@ -27,8 +27,8 @@ class ApplicationView : FrameLayout {
|
||||
layoutTransition = LayoutTransition()
|
||||
layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
|
||||
binding.applicationCard.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
|
||||
val viewModel: AppViewModel by (context as AppCompatActivity).viewModel()
|
||||
applications = viewModel.applications
|
||||
val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
|
||||
applications = viewModel.appResults
|
||||
applications.observe(context as AppCompatActivity, Observer<List<Application>> {
|
||||
visibility = if (it.isEmpty()) View.GONE else View.VISIBLE
|
||||
binding.applicationGrid.submitItems(it)
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
@@ -14,29 +15,29 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.calculator.CalculatorViewModel
|
||||
import de.mm20.launcher2.search.data.Calculator
|
||||
import de.mm20.launcher2.ui.LegacyLauncherTheme
|
||||
import de.mm20.launcher2.ui.databinding.ViewCalculatorBinding
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchViewModel
|
||||
import de.mm20.launcher2.ui.search.CalculatorItem
|
||||
import de.mm20.launcher2.ui.search.UnitConverterItem
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import kotlin.math.round
|
||||
|
||||
class CalculatorView : FrameLayout {
|
||||
|
||||
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 calculator: LiveData<Calculator?>
|
||||
|
||||
private val binding = ViewCalculatorBinding.inflate(LayoutInflater.from(context), this, true)
|
||||
|
||||
init {
|
||||
val viewModel: CalculatorViewModel by (context as AppCompatActivity).viewModel()
|
||||
calculator = viewModel.calculator
|
||||
val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
|
||||
calculator = viewModel.calculatorResult
|
||||
calculator.observe(context as AppCompatActivity, Observer {
|
||||
if (it == null) visibility = View.GONE
|
||||
else {
|
||||
|
||||
@@ -6,23 +6,26 @@ import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.calendar.CalendarViewModel
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
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.R
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchViewModel
|
||||
import de.mm20.launcher2.ui.legacy.search.SearchListView
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class CalendarView : FrameLayout {
|
||||
|
||||
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 calendarEvents: LiveData<List<CalendarEvent>?>
|
||||
|
||||
@@ -33,21 +36,27 @@ class CalendarView : FrameLayout {
|
||||
val card = findViewById<ViewGroup>(R.id.card)
|
||||
card.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
|
||||
val list = findViewById<SearchListView>(R.id.list)
|
||||
val viewModel: CalendarViewModel by (context as AppCompatActivity).viewModel()
|
||||
calendarEvents = viewModel.calendarEvents
|
||||
val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
|
||||
calendarEvents = viewModel.calendarResults
|
||||
calendarEvents.observe(context as AppCompatActivity, {
|
||||
if (it == null) {
|
||||
visibility = View.GONE
|
||||
return@observe
|
||||
}
|
||||
if (it.isEmpty() && LauncherPreferences.instance.searchCalendars && !PermissionsManager.checkPermission(context, PermissionsManager.CALENDAR)) {
|
||||
if (it.isEmpty() && LauncherPreferences.instance.searchCalendars && !PermissionsManager.checkPermission(
|
||||
context,
|
||||
PermissionsManager.CALENDAR
|
||||
)
|
||||
) {
|
||||
visibility = View.VISIBLE
|
||||
list.submitItems(listOf(
|
||||
list.submitItems(
|
||||
listOf(
|
||||
MissingPermission(
|
||||
context.getString(R.string.permission_calendar_search),
|
||||
PermissionsManager.CALENDAR
|
||||
context.getString(R.string.permission_calendar_search),
|
||||
PermissionsManager.CALENDAR
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
return@observe
|
||||
}
|
||||
visibility = if (it.isEmpty()) View.GONE else View.VISIBLE
|
||||
|
||||
@@ -6,24 +6,28 @@ import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import android.widget.SearchView
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import de.mm20.launcher2.contacts.ContactViewModel
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||
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.launcher.search.SearchViewModel
|
||||
import de.mm20.launcher2.ui.legacy.search.SearchListView
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class ContactView : FrameLayout {
|
||||
private val contacts: LiveData<List<Contact>?>
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
init {
|
||||
View.inflate(context, R.layout.view_search_category_list, this)
|
||||
@@ -31,22 +35,28 @@ class ContactView : FrameLayout {
|
||||
layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
|
||||
val card = findViewById<ViewGroup>(R.id.card)
|
||||
card.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
|
||||
val viewModel: ContactViewModel by (context as AppCompatActivity).viewModel()
|
||||
contacts = viewModel.contacts
|
||||
val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
|
||||
contacts = viewModel.contactResults
|
||||
val list = findViewById<SearchListView>(R.id.list)
|
||||
contacts.observe(context as AppCompatActivity, {
|
||||
if (it == null) {
|
||||
visibility = View.GONE
|
||||
return@observe
|
||||
}
|
||||
if (it.isEmpty() && LauncherPreferences.instance.searchContacts && !PermissionsManager.checkPermission(context, PermissionsManager.CONTACTS)) {
|
||||
if (it.isEmpty() && LauncherPreferences.instance.searchContacts && !PermissionsManager.checkPermission(
|
||||
context,
|
||||
PermissionsManager.CONTACTS
|
||||
)
|
||||
) {
|
||||
visibility = View.VISIBLE
|
||||
list.submitItems(listOf(
|
||||
list.submitItems(
|
||||
listOf(
|
||||
MissingPermission(
|
||||
context.getString(R.string.permission_contact_search),
|
||||
PermissionsManager.CONTACTS
|
||||
context.getString(R.string.permission_contact_search),
|
||||
PermissionsManager.CONTACTS
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
return@observe
|
||||
}
|
||||
visibility = if (it.isEmpty()) View.GONE else View.VISIBLE
|
||||
|
||||
@@ -6,14 +6,10 @@ import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.*
|
||||
import de.mm20.launcher2.favorites.FavoritesViewModel
|
||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.databinding.ViewFavoritesBinding
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchViewModel
|
||||
|
||||
class FavoritesView : FrameLayout {
|
||||
|
||||
@@ -21,18 +17,21 @@ class FavoritesView : FrameLayout {
|
||||
constructor(context: Context, attrs: AttributeSet?) : super(context, attrs)
|
||||
constructor(context: Context, attrs: AttributeSet?, defStyleRes: Int) : super(context, attrs, defStyleRes)
|
||||
|
||||
private val favorites: LiveData<List<Searchable>>
|
||||
|
||||
private val binding = ViewFavoritesBinding.inflate(LayoutInflater.from(context), this, true)
|
||||
|
||||
|
||||
init {
|
||||
val viewModel: FavoritesViewModel by (context as AppCompatActivity).viewModel()
|
||||
favorites = viewModel.getFavorites(LauncherPreferences.instance.gridColumnCount)
|
||||
favorites.observe(context as AppCompatActivity, Observer {
|
||||
visibility = if (it?.isEmpty() == true) View.GONE else View.VISIBLE
|
||||
val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
|
||||
val favorites = viewModel.favorites
|
||||
val hide = viewModel.hideFavorites
|
||||
favorites.observe(context as AppCompatActivity) {
|
||||
visibility = if (it?.isEmpty() == true || hide.value == true) View.GONE else View.VISIBLE
|
||||
binding.favoritesGrid.submitItems(it)
|
||||
})
|
||||
}
|
||||
|
||||
hide.observe(context as AppCompatActivity) {
|
||||
visibility = if(it == true) View.GONE else View.VISIBLE
|
||||
}
|
||||
|
||||
layoutTransition = LayoutTransition().apply { enableTransitionType(LayoutTransition.CHANGING) }
|
||||
binding.favoritesCard.layoutTransition = LayoutTransition().apply { enableTransitionType(LayoutTransition.CHANGING) }
|
||||
|
||||
@@ -6,22 +6,26 @@ import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.LiveData
|
||||
import de.mm20.launcher2.files.FilesViewModel
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
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.launcher.search.SearchViewModel
|
||||
import de.mm20.launcher2.ui.legacy.search.SearchListView
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class FileView : FrameLayout {
|
||||
private val files: LiveData<List<File>?>
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
init {
|
||||
View.inflate(context, R.layout.view_search_category_list, this)
|
||||
@@ -30,21 +34,27 @@ class FileView : FrameLayout {
|
||||
val card = findViewById<ViewGroup>(R.id.card)
|
||||
card.layoutTransition.enableTransitionType(LayoutTransition.CHANGING)
|
||||
val list = findViewById<SearchListView>(R.id.list)
|
||||
val viewModel: FilesViewModel by (context as AppCompatActivity).viewModel()
|
||||
files = viewModel.files
|
||||
val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
|
||||
files = viewModel.fileResults
|
||||
files.observe(context as AppCompatActivity, {
|
||||
if (it == null) {
|
||||
visibility = View.GONE
|
||||
return@observe
|
||||
}
|
||||
if (it.isEmpty() && !PermissionsManager.checkPermission(context, PermissionsManager.EXTERNAL_STORAGE)) {
|
||||
if (it.isEmpty() && !PermissionsManager.checkPermission(
|
||||
context,
|
||||
PermissionsManager.EXTERNAL_STORAGE
|
||||
)
|
||||
) {
|
||||
visibility = View.VISIBLE
|
||||
list.submitItems(listOf(
|
||||
list.submitItems(
|
||||
listOf(
|
||||
MissingPermission(
|
||||
context.getString(R.string.permission_files_search),
|
||||
PermissionsManager.EXTERNAL_STORAGE
|
||||
context.getString(R.string.permission_files_search),
|
||||
PermissionsManager.EXTERNAL_STORAGE
|
||||
)
|
||||
))
|
||||
)
|
||||
)
|
||||
return@observe
|
||||
}
|
||||
visibility = if (it.isEmpty()) View.GONE else View.VISIBLE
|
||||
|
||||
@@ -12,21 +12,17 @@ import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.animation.AccelerateInterpolator
|
||||
import android.view.animation.DecelerateInterpolator
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.core.view.postDelayed
|
||||
import androidx.lifecycle.Observer
|
||||
import com.airbnb.lottie.LottieCompositionFactory
|
||||
import com.airbnb.lottie.LottieDrawable
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.preferences.LauncherPreferences
|
||||
import de.mm20.launcher2.preferences.SearchStyles
|
||||
import de.mm20.launcher2.search.SearchViewModel
|
||||
import de.mm20.launcher2.transition.ChangingLayoutTransition
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.databinding.ViewSearchBarBinding
|
||||
import de.mm20.launcher2.ui.legacy.view.LauncherCardView
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class SearchBar @JvmOverloads constructor(
|
||||
context: Context,
|
||||
@@ -72,12 +68,6 @@ class SearchBar @JvmOverloads constructor(
|
||||
|
||||
})
|
||||
|
||||
val viewModel = (context as AppCompatActivity).viewModel<SearchViewModel>().value
|
||||
|
||||
viewModel.isSearching.observe(context, Observer {
|
||||
binding.searchProgressBar.visibility = if (it) View.VISIBLE else View.GONE
|
||||
})
|
||||
|
||||
binding.overflowMenu.setOnClickListener {
|
||||
if (getSearchQuery().isEmpty()) onRightIconClick?.invoke(it)
|
||||
else (setSearchQuery(""))
|
||||
|
||||
@@ -5,6 +5,7 @@ import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.material3.LocalContentColor
|
||||
@@ -17,9 +18,8 @@ import androidx.lifecycle.Observer
|
||||
import de.mm20.launcher2.search.data.UnitConverter
|
||||
import de.mm20.launcher2.ui.LegacyLauncherTheme
|
||||
import de.mm20.launcher2.ui.databinding.ViewUnitconverterBinding
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchViewModel
|
||||
import de.mm20.launcher2.ui.search.UnitConverterItem
|
||||
import de.mm20.launcher2.unitconverter.UnitConverterViewModel
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
|
||||
class UnitConverterView : FrameLayout {
|
||||
|
||||
@@ -36,8 +36,8 @@ class UnitConverterView : FrameLayout {
|
||||
private val binding = ViewUnitconverterBinding.inflate(LayoutInflater.from(context), this, true)
|
||||
|
||||
init {
|
||||
val unitConverterViewModel by (context as AppCompatActivity).viewModel<UnitConverterViewModel>()
|
||||
unitConverter = unitConverterViewModel.unitConverter
|
||||
val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
|
||||
unitConverter = viewModel.unitConverterResult
|
||||
unitConverter.observe(context as AppCompatActivity, Observer {
|
||||
if (it == null) visibility = View.GONE
|
||||
else {
|
||||
|
||||
@@ -7,6 +7,7 @@ import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.LiveData
|
||||
@@ -17,24 +18,27 @@ import com.bumptech.glide.request.transition.Transition
|
||||
import com.google.android.material.chip.Chip
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
import de.mm20.launcher2.legacy.helper.ActivityStarter
|
||||
import de.mm20.launcher2.search.WebsearchViewModel
|
||||
import de.mm20.launcher2.search.data.Websearch
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.databinding.ViewWebsearchBinding
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchViewModel
|
||||
|
||||
class WebSearchView : FrameLayout {
|
||||
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 websearches: LiveData<List<Websearch>>
|
||||
|
||||
private val binding = ViewWebsearchBinding.inflate(LayoutInflater.from(context), this, true)
|
||||
|
||||
init {
|
||||
val viewModel: WebsearchViewModel by (context as AppCompatActivity).viewModel()
|
||||
websearches = viewModel.websearches
|
||||
val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
|
||||
websearches = viewModel.websearchResults
|
||||
websearches.observe(context as AppCompatActivity, Observer {
|
||||
updateWebsearches(it)
|
||||
})
|
||||
@@ -48,13 +52,16 @@ class WebSearchView : FrameLayout {
|
||||
chip.text = search.label
|
||||
if (search.icon != null) {
|
||||
Glide.with(context)
|
||||
.load(search.icon)
|
||||
.into(object : SimpleTarget<Drawable>() {
|
||||
override fun onResourceReady(resource: Drawable, transition: Transition<in Drawable>?) {
|
||||
chip.chipIcon = resource
|
||||
}
|
||||
.load(search.icon)
|
||||
.into(object : SimpleTarget<Drawable>() {
|
||||
override fun onResourceReady(
|
||||
resource: Drawable,
|
||||
transition: Transition<in Drawable>?
|
||||
) {
|
||||
chip.chipIcon = resource
|
||||
}
|
||||
|
||||
})
|
||||
})
|
||||
chip.chipIconTint = null
|
||||
} else {
|
||||
chip.chipIcon = ContextCompat.getDrawable(context, R.drawable.ic_search)
|
||||
@@ -63,7 +70,8 @@ class WebSearchView : FrameLayout {
|
||||
}
|
||||
chip.chipStrokeWidth = 1 * dp
|
||||
chip.chipStrokeColor = ContextCompat.getColorStateList(context, R.color.chip_stroke)
|
||||
chip.chipBackgroundColor = ContextCompat.getColorStateList(context, R.color.chip_background)
|
||||
chip.chipBackgroundColor =
|
||||
ContextCompat.getColorStateList(context, R.color.chip_background)
|
||||
chip.setTextAppearanceResource(R.style.ChipTextAppearance)
|
||||
chip.setOnClickListener {
|
||||
ActivityStarter.start(context, chip, intent = search.getLaunchIntent())
|
||||
|
||||
@@ -5,34 +5,38 @@ import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import de.mm20.launcher2.legacy.helper.ActivityStarter
|
||||
import de.mm20.launcher2.search.data.Website
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchViewModel
|
||||
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 {
|
||||
|
||||
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 website: LiveData<Website?>
|
||||
|
||||
init {
|
||||
View.inflate(context, R.layout.view_search_category_single_item, this)
|
||||
val websiteView = SearchableView(context, SearchableView.REPRESENTATION_LIST)
|
||||
val params = ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
val params =
|
||||
ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
val card = findViewById<ViewGroup>(R.id.card)
|
||||
websiteView.layoutParams = params
|
||||
card.addView(websiteView)
|
||||
val viewModel: WebsiteViewModel by (context as AppCompatActivity).viewModel()
|
||||
website = viewModel.website
|
||||
val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
|
||||
website = viewModel.websiteResult
|
||||
website.observe(context as AppCompatActivity, Observer {
|
||||
visibility = if (it == null) View.GONE else View.VISIBLE
|
||||
card.setOnClickListener { _ ->
|
||||
|
||||
@@ -5,35 +5,38 @@ import android.util.AttributeSet
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
import android.widget.FrameLayout
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.lifecycle.LiveData
|
||||
import androidx.lifecycle.Observer
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import de.mm20.launcher2.legacy.helper.ActivityStarter
|
||||
import de.mm20.launcher2.search.data.Wikipedia
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchViewModel
|
||||
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 {
|
||||
|
||||
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
|
||||
)
|
||||
|
||||
val wikipedia: LiveData<Wikipedia?>
|
||||
|
||||
init {
|
||||
View.inflate(context, R.layout.view_search_category_single_item, this)
|
||||
val websiteView = SearchableView(context, SearchableView.REPRESENTATION_LIST)
|
||||
val params = ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
val params =
|
||||
ViewGroup.LayoutParams(LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.WRAP_CONTENT)
|
||||
val card = findViewById<ViewGroup>(R.id.card)
|
||||
websiteView.layoutParams = params
|
||||
card.addView(websiteView)
|
||||
val viewModel: WikipediaViewModel by (context as AppCompatActivity).viewModel()
|
||||
wikipedia = viewModel.wikipedia
|
||||
wikipedia.observe(context as AppCompatActivity, Observer {
|
||||
val viewModel: SearchViewModel by (context as AppCompatActivity).viewModels()
|
||||
wikipedia = viewModel.wikipediaResult
|
||||
wikipedia.observe(context as AppCompatActivity, {
|
||||
visibility = if (it == null) View.GONE else View.VISIBLE
|
||||
card.setOnClickListener { _ ->
|
||||
ActivityStarter.start(context, websiteView, item = it)
|
||||
|
||||
@@ -22,7 +22,7 @@ import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import java.lang.ref.WeakReference
|
||||
|
||||
object ActivityStarter: KoinComponent {
|
||||
object ActivityStarter : KoinComponent {
|
||||
|
||||
val favoritesRepository: FavoritesRepository by inject()
|
||||
|
||||
@@ -46,13 +46,20 @@ object ActivityStarter: KoinComponent {
|
||||
initialized = true
|
||||
}
|
||||
|
||||
fun start(context: Context, transitionView: View, item: Searchable? = null, intent: Intent? = null, pendingIntent: PendingIntent? = null): Boolean {
|
||||
fun start(
|
||||
context: Context,
|
||||
transitionView: View,
|
||||
item: Searchable? = null,
|
||||
intent: Intent? = null,
|
||||
pendingIntent: PendingIntent? = null
|
||||
): Boolean {
|
||||
if (!initialized) throw IllegalStateException("Item starter has not been initialized properly.")
|
||||
|
||||
if (!startActivity(context, item, intent, pendingIntent, transitionView)) return false
|
||||
|
||||
if (animationStyle == AppStartAnimation.SLIDE_BOTTOM || animationStyle == AppStartAnimation.FADE ||
|
||||
animationStyle == AppStartAnimation.M) {
|
||||
animationStyle == AppStartAnimation.M
|
||||
) {
|
||||
return true
|
||||
}
|
||||
|
||||
@@ -80,28 +87,28 @@ object ActivityStarter: KoinComponent {
|
||||
|
||||
AnimatorSet().apply {
|
||||
playTogether(
|
||||
ViewPropertyObjectAnimator.animate(background)
|
||||
.scaleX(1f)
|
||||
.scaleY(1f)
|
||||
.translationX(0f)
|
||||
.translationY(0f)
|
||||
.setDuration(200)
|
||||
.setInterpolator(AccelerateInterpolator(0.8f))
|
||||
.get(),
|
||||
ViewPropertyObjectAnimator.animate(transitionView)
|
||||
.scaleX(scale)
|
||||
.scaleY(scale)
|
||||
.alpha(0f)
|
||||
.translationX(x)
|
||||
.translationY(y)
|
||||
.setDuration(200)
|
||||
.setInterpolator(AccelerateInterpolator(0.8f))
|
||||
.get(),
|
||||
ViewPropertyObjectAnimator.animate(searchView)
|
||||
.scaleX(0.8f)
|
||||
.scaleY(0.8f)
|
||||
.alpha(0f)
|
||||
.get()
|
||||
ViewPropertyObjectAnimator.animate(background)
|
||||
.scaleX(1f)
|
||||
.scaleY(1f)
|
||||
.translationX(0f)
|
||||
.translationY(0f)
|
||||
.setDuration(200)
|
||||
.setInterpolator(AccelerateInterpolator(0.8f))
|
||||
.get(),
|
||||
ViewPropertyObjectAnimator.animate(transitionView)
|
||||
.scaleX(scale)
|
||||
.scaleY(scale)
|
||||
.alpha(0f)
|
||||
.translationX(x)
|
||||
.translationY(y)
|
||||
.setDuration(200)
|
||||
.setInterpolator(AccelerateInterpolator(0.8f))
|
||||
.get(),
|
||||
ViewPropertyObjectAnimator.animate(searchView)
|
||||
.scaleX(0.8f)
|
||||
.scaleY(0.8f)
|
||||
.alpha(0f)
|
||||
.get()
|
||||
)
|
||||
}.start()
|
||||
onResumeCallback = {
|
||||
@@ -127,10 +134,17 @@ object ActivityStarter: KoinComponent {
|
||||
|
||||
private var onResumeCallback: (() -> Unit)? = null
|
||||
|
||||
private fun startActivity(context: Context, item: Searchable? = null, intent: Intent? = null, pendingIntent: PendingIntent? = null, sourceView: View): Boolean {
|
||||
private fun startActivity(
|
||||
context: Context,
|
||||
item: Searchable? = null,
|
||||
intent: Intent? = null,
|
||||
pendingIntent: PendingIntent? = null,
|
||||
sourceView: View
|
||||
): Boolean {
|
||||
val pos = intArrayOf(0, 0)
|
||||
sourceView.getLocationOnScreen(pos)
|
||||
val sourceBounds = Rect(pos[0], pos[1], pos[0] + sourceView.width, pos[1] + sourceView.height)
|
||||
val sourceBounds =
|
||||
Rect(pos[0], pos[1], pos[0] + sourceView.width, pos[1] + sourceView.height)
|
||||
|
||||
val bundle = getActivityOptions(context, sourceView, sourceBounds)?.toBundle()
|
||||
|
||||
@@ -145,7 +159,7 @@ object ActivityStarter: KoinComponent {
|
||||
|
||||
if (item != null) {
|
||||
if (item.launch(context, bundle)) {
|
||||
favoritesRepository.incrementLaunchCount(item)
|
||||
favoritesRepository.incrementLaunchCounter(item)
|
||||
return true
|
||||
}
|
||||
return false
|
||||
@@ -162,12 +176,36 @@ object ActivityStarter: KoinComponent {
|
||||
}
|
||||
}
|
||||
|
||||
private fun getActivityOptions(context: Context, sourceView: View, sourceBounds: Rect?): ActivityOptionsCompat? {
|
||||
private fun getActivityOptions(
|
||||
context: Context,
|
||||
sourceView: View,
|
||||
sourceBounds: Rect?
|
||||
): ActivityOptionsCompat? {
|
||||
return when (animationStyle) {
|
||||
AppStartAnimation.FADE -> ActivityOptionsCompat.makeCustomAnimation(context, R.anim.activity_start_fade_enter, R.anim.activity_start_fade_exit)
|
||||
AppStartAnimation.SLIDE_BOTTOM -> ActivityOptionsCompat.makeCustomAnimation(context, R.anim.activity_start_slide_bottom_enter, R.anim.activity_start_slide_bottom_exit)
|
||||
AppStartAnimation.M -> sourceBounds?.let { ActivityOptionsCompat.makeClipRevealAnimation(sourceView, 0, 0, sourceView.width, sourceView.height) }
|
||||
else -> ActivityOptionsCompat.makeCustomAnimation(context, R.anim.activity_start_splash2_enter, R.anim.activity_start_splash2_exit)
|
||||
AppStartAnimation.FADE -> ActivityOptionsCompat.makeCustomAnimation(
|
||||
context,
|
||||
R.anim.activity_start_fade_enter,
|
||||
R.anim.activity_start_fade_exit
|
||||
)
|
||||
AppStartAnimation.SLIDE_BOTTOM -> ActivityOptionsCompat.makeCustomAnimation(
|
||||
context,
|
||||
R.anim.activity_start_slide_bottom_enter,
|
||||
R.anim.activity_start_slide_bottom_exit
|
||||
)
|
||||
AppStartAnimation.M -> sourceBounds?.let {
|
||||
ActivityOptionsCompat.makeClipRevealAnimation(
|
||||
sourceView,
|
||||
0,
|
||||
0,
|
||||
sourceView.width,
|
||||
sourceView.height
|
||||
)
|
||||
}
|
||||
else -> ActivityOptionsCompat.makeCustomAnimation(
|
||||
context,
|
||||
R.anim.activity_start_splash2_enter,
|
||||
R.anim.activity_start_splash2_exit
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
+11
-13
@@ -28,14 +28,13 @@ 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.*
|
||||
import androidx.transition.Scene
|
||||
import com.google.android.material.chip.Chip
|
||||
import com.google.android.material.chip.ChipGroup
|
||||
import de.mm20.launcher2.badges.BadgeProvider
|
||||
import de.mm20.launcher2.crashreporter.CrashReporter
|
||||
import de.mm20.launcher2.favorites.FavoritesViewModel
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.icons.IconRepository
|
||||
import de.mm20.launcher2.ktx.castToOrNull
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
@@ -51,10 +50,8 @@ import de.mm20.launcher2.transition.ChangingLayoutTransition
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.legacy.searchable.SearchableView
|
||||
import de.mm20.launcher2.ui.legacy.view.*
|
||||
import kotlinx.coroutines.flow.collect
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
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
|
||||
@@ -83,9 +80,10 @@ class ApplicationDetailRepresentation : Representation, KoinComponent {
|
||||
shape = LauncherIconView.getDefaultShape(context)
|
||||
icon = iconRepository.getIconIfCached(application)
|
||||
lifecycleScope.launch {
|
||||
iconRepository.getIcon(application, (84 * rootView.dp).toInt()).collectLatest {
|
||||
icon = it
|
||||
}
|
||||
iconRepository.getIcon(application, (84 * rootView.dp).toInt())
|
||||
.collectLatest {
|
||||
icon = it
|
||||
}
|
||||
}
|
||||
}
|
||||
findViewById<SwipeCardView>(R.id.appCard).also {
|
||||
@@ -275,7 +273,7 @@ class ApplicationDetailRepresentation : Representation, KoinComponent {
|
||||
if (launcherApps.hasShortcutHostPermission()) {
|
||||
val shortcuts = app.shortcuts
|
||||
|
||||
val viewModel: FavoritesViewModel by (context as AppCompatActivity).viewModel()
|
||||
val repository: FavoritesRepository by inject()
|
||||
|
||||
var count = 0
|
||||
for (si in shortcuts) {
|
||||
@@ -308,7 +306,7 @@ class ApplicationDetailRepresentation : Representation, KoinComponent {
|
||||
R.color.text_color_primary
|
||||
)
|
||||
)
|
||||
val isPinned = viewModel.isPinned(si)
|
||||
val isPinned = repository.isPinned(si).asLiveData()
|
||||
|
||||
isPinned.observe(context as LifecycleOwner, Observer {
|
||||
view.isCloseIconVisible = isPinned.value == true
|
||||
@@ -320,14 +318,14 @@ class ApplicationDetailRepresentation : Representation, KoinComponent {
|
||||
}
|
||||
view.setOnLongClickListener {
|
||||
if (isPinned.value == true) {
|
||||
viewModel.unpinItem(si)
|
||||
repository.unpinItem(si)
|
||||
} else {
|
||||
viewModel.pinItem(si)
|
||||
repository.pinItem(si)
|
||||
}
|
||||
true
|
||||
}
|
||||
view.setOnCloseIconClickListener {
|
||||
viewModel.unpinItem(si)
|
||||
repository.unpinItem(si)
|
||||
view.isCloseIconVisible = false
|
||||
}
|
||||
appShortcuts.addView(view)
|
||||
|
||||
@@ -14,15 +14,17 @@ import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.animation.doOnEnd
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.LifecycleOwner
|
||||
import androidx.lifecycle.ViewModelProvider
|
||||
import androidx.lifecycle.asLiveData
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import com.google.android.material.card.MaterialCardView
|
||||
import de.mm20.launcher2.favorites.FavoritesViewModel
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
import de.mm20.launcher2.ktx.dp
|
||||
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 org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
import kotlin.math.abs
|
||||
|
||||
class SwipeCardView @JvmOverloads constructor(
|
||||
@@ -376,10 +378,12 @@ class FavoriteSwipeAction(val context: Context, val searchable: Searchable) :
|
||||
R.drawable.ic_star_solid,
|
||||
ContextCompat.getColor(context, R.color.amber),
|
||||
{ false }
|
||||
) {
|
||||
val viewModel: FavoritesViewModel by (context as AppCompatActivity).viewModel()
|
||||
), KoinComponent {
|
||||
|
||||
private val pinned = viewModel.isPinned(searchable)
|
||||
private val repository: FavoritesRepository by inject()
|
||||
|
||||
private val pinned = repository.isPinned(searchable)
|
||||
.asLiveData((context as AppCompatActivity).lifecycleScope.coroutineContext)
|
||||
|
||||
|
||||
init {
|
||||
@@ -392,7 +396,7 @@ class FavoriteSwipeAction(val context: Context, val searchable: Searchable) :
|
||||
if (pinned) {
|
||||
icon = R.drawable.ic_star_outline
|
||||
action = {
|
||||
viewModel.unpinItem(
|
||||
repository.unpinItem(
|
||||
searchable
|
||||
)
|
||||
false
|
||||
@@ -400,7 +404,7 @@ class FavoriteSwipeAction(val context: Context, val searchable: Searchable) :
|
||||
} else {
|
||||
icon = R.drawable.ic_star_solid
|
||||
action = {
|
||||
viewModel.pinItem(
|
||||
repository.pinItem(
|
||||
searchable
|
||||
)
|
||||
false
|
||||
@@ -413,9 +417,12 @@ class HideSwipeAction(val context: Context, val searchable: Searchable) : SwipeC
|
||||
R.drawable.ic_visibility_off,
|
||||
ContextCompat.getColor(context, R.color.blue),
|
||||
{ false }
|
||||
) {
|
||||
val viewModel: FavoritesViewModel by (context as AppCompatActivity).viewModel()
|
||||
private val hidden = viewModel.isHidden(searchable)
|
||||
), KoinComponent {
|
||||
|
||||
private val repository: FavoritesRepository by inject()
|
||||
|
||||
private val hidden = repository.isPinned(searchable)
|
||||
.asLiveData((context as AppCompatActivity).lifecycleScope.coroutineContext)
|
||||
|
||||
init {
|
||||
hidden.observe(context as LifecycleOwner) {
|
||||
@@ -427,7 +434,7 @@ class HideSwipeAction(val context: Context, val searchable: Searchable) : SwipeC
|
||||
if (hidden) {
|
||||
icon = R.drawable.ic_visibility
|
||||
action = {
|
||||
viewModel.unhideItem(
|
||||
repository.unhideItem(
|
||||
searchable
|
||||
)
|
||||
true
|
||||
@@ -435,7 +442,7 @@ class HideSwipeAction(val context: Context, val searchable: Searchable) : SwipeC
|
||||
} else {
|
||||
icon = R.drawable.ic_visibility_off
|
||||
action = {
|
||||
viewModel.hideItem(
|
||||
repository.hideItem(
|
||||
searchable
|
||||
)
|
||||
true
|
||||
|
||||
@@ -6,16 +6,18 @@ import android.view.View
|
||||
import android.widget.ImageView
|
||||
import android.widget.LinearLayout
|
||||
import androidx.annotation.DrawableRes
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.appcompat.widget.TooltipCompat
|
||||
import androidx.core.view.setPadding
|
||||
import androidx.lifecycle.Observer
|
||||
import de.mm20.launcher2.favorites.FavoritesViewModel
|
||||
import androidx.lifecycle.*
|
||||
import de.mm20.launcher2.favorites.FavoritesRepository
|
||||
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
|
||||
import kotlinx.coroutines.*
|
||||
import kotlinx.coroutines.flow.collectLatest
|
||||
import org.koin.core.component.KoinComponent
|
||||
import org.koin.core.component.inject
|
||||
|
||||
class ToolbarView : LinearLayout {
|
||||
constructor(context: Context) : super(context)
|
||||
@@ -223,27 +225,37 @@ 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)
|
||||
) {
|
||||
), KoinComponent {
|
||||
|
||||
private val viewModel: FavoritesViewModel by (context as AppCompatActivity).viewModel()
|
||||
private val isPinned = viewModel.isPinned(item)
|
||||
|
||||
init {
|
||||
isPinned.observe(context as AppCompatActivity, Observer {
|
||||
it ?: return@Observer
|
||||
if (it) {
|
||||
private val repository: FavoritesRepository by inject()
|
||||
private var isPinned = false
|
||||
set(value) {
|
||||
field = value
|
||||
if (value) {
|
||||
title = context.getString(R.string.favorites_menu_unpin)
|
||||
icon = R.drawable.ic_star_solid
|
||||
} else {
|
||||
title = context.getString(R.string.favorites_menu_pin)
|
||||
icon = R.drawable.ic_star_outline
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
init {
|
||||
clickAction = {
|
||||
if (isPinned.value == true) {
|
||||
viewModel.unpinItem(item)
|
||||
if (isPinned) {
|
||||
repository.unpinItem(item)
|
||||
} else {
|
||||
viewModel.pinItem(item)
|
||||
repository.pinItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
(context as LifecycleOwner).apply {
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
repository.isPinned(item).collectLatest {
|
||||
isPinned = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -252,27 +264,40 @@ class FavoriteToolbarAction(val context: Context, val item: Searchable) : Toolba
|
||||
class VisibilityToolbarAction(val context: Context, val item: Searchable) : ToolbarAction(
|
||||
R.drawable.ic_visibility,
|
||||
context.getString(R.string.menu_hide)
|
||||
) {
|
||||
), KoinComponent {
|
||||
|
||||
private val viewModel: FavoritesViewModel by (context as AppCompatActivity).viewModel()
|
||||
private val isHidden = viewModel.isHidden(item)
|
||||
|
||||
init {
|
||||
isHidden.observe(context as AppCompatActivity, Observer {
|
||||
if (it) {
|
||||
private val repository: FavoritesRepository by inject()
|
||||
private var isHidden = false
|
||||
set(value) {
|
||||
field = value
|
||||
if (value) {
|
||||
title = context.getString(R.string.menu_unhide)
|
||||
icon = R.drawable.ic_visibility
|
||||
} else {
|
||||
title = context.getString(R.string.menu_hide)
|
||||
icon = R.drawable.ic_visibility_off
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
init {
|
||||
clickAction = {
|
||||
if (isHidden.value == true) {
|
||||
viewModel.unhideItem(item)
|
||||
if (isHidden) {
|
||||
repository.unhideItem(item)
|
||||
} else {
|
||||
viewModel.hideItem(item)
|
||||
repository.hideItem(item)
|
||||
}
|
||||
}
|
||||
|
||||
(context as LifecycleOwner).apply {
|
||||
lifecycleScope.launch {
|
||||
repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
repository.isHidden(item).collectLatest {
|
||||
isHidden = it
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
@@ -1,145 +1,113 @@
|
||||
package de.mm20.launcher2.ui.legacy.widget
|
||||
|
||||
import android.animation.LayoutTransition
|
||||
import android.content.ContentUris
|
||||
import android.content.Context
|
||||
import android.content.Intent
|
||||
import android.provider.CalendarContract
|
||||
import android.text.format.DateUtils
|
||||
import android.util.AttributeSet
|
||||
import android.view.LayoutInflater
|
||||
import android.view.Menu
|
||||
import android.view.View
|
||||
import androidx.activity.viewModels
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.appcompat.widget.PopupMenu
|
||||
import androidx.lifecycle.LiveData
|
||||
import de.mm20.launcher2.calendar.CalendarViewModel
|
||||
import de.mm20.launcher2.favorites.FavoritesViewModel
|
||||
import de.mm20.launcher2.legacy.helper.ActivityStarter
|
||||
import de.mm20.launcher2.permissions.PermissionsManager
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import de.mm20.launcher2.ktx.lifecycleOwner
|
||||
import de.mm20.launcher2.ktx.lifecycleScope
|
||||
import de.mm20.launcher2.search.data.CalendarEvent
|
||||
import de.mm20.launcher2.ui.legacy.data.InformationText
|
||||
import de.mm20.launcher2.search.data.MissingPermission
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.databinding.ViewCalendarWidgetBinding
|
||||
import org.koin.androidx.viewmodel.ext.android.viewModel
|
||||
import java.util.*
|
||||
import kotlin.math.max
|
||||
import kotlin.math.min
|
||||
import de.mm20.launcher2.ui.launcher.widgets.calendar.CalendarWidgetVM
|
||||
import de.mm20.launcher2.ui.legacy.data.InformationText
|
||||
import kotlinx.coroutines.launch
|
||||
import java.time.LocalDate
|
||||
import java.time.ZoneId
|
||||
|
||||
class CalendarWidget : LauncherWidget {
|
||||
|
||||
override val canResize: Boolean
|
||||
get() = false
|
||||
|
||||
private val calendarEvents: LiveData<List<CalendarEvent>>
|
||||
private val pinnedCalendarEvents: LiveData<List<CalendarEvent>>
|
||||
|
||||
private val zoneOffset = Calendar.getInstance().timeZone.getOffset(System.currentTimeMillis())
|
||||
private var selectedDay = 0L
|
||||
set(value) {
|
||||
field = value
|
||||
binding.calendarDate.text = formatDay(value)
|
||||
updateEventList()
|
||||
}
|
||||
|
||||
private var availableDays: List<Long> = listOf(0L)
|
||||
set(value) {
|
||||
if (value.indexOf(selectedDay) == -1) selectedDay = 0L
|
||||
field = value
|
||||
}
|
||||
|
||||
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 fun formatDay(day: Long): String {
|
||||
return when (day) {
|
||||
0L -> context.getString(R.string.date_today)
|
||||
1L -> context.getString(R.string.date_tomorrow)
|
||||
else -> DateUtils.formatDateTime(context, (getToday() + day) * (1000 * 60 * 60 * 24), DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_WEEKDAY or DateUtils.FORMAT_ABBREV_WEEKDAY)
|
||||
private fun formatDay(day: LocalDate): String {
|
||||
val today = LocalDate.now()
|
||||
return when {
|
||||
today == day -> context.getString(R.string.date_today)
|
||||
today.plusDays(1) == day -> context.getString(R.string.date_tomorrow)
|
||||
else -> DateUtils.formatDateTime(
|
||||
context,
|
||||
day.atStartOfDay(ZoneId.systemDefault()).toEpochSecond() * 1000,
|
||||
DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_WEEKDAY or DateUtils.FORMAT_ABBREV_WEEKDAY
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
private val binding = ViewCalendarWidgetBinding.inflate(LayoutInflater.from(context), this, true)
|
||||
private val binding =
|
||||
ViewCalendarWidgetBinding.inflate(LayoutInflater.from(context), this, true)
|
||||
|
||||
private val viewModel: CalendarWidgetVM by (context as AppCompatActivity).viewModels()
|
||||
|
||||
init {
|
||||
clipToPadding = false
|
||||
clipChildren = false
|
||||
|
||||
lifecycleScope.launch {
|
||||
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.RESUMED) {
|
||||
viewModel.onActive()
|
||||
}
|
||||
}
|
||||
|
||||
binding.calendarNewEvent.setOnClickListener {
|
||||
val intent = Intent(Intent.ACTION_EDIT)
|
||||
intent.data = CalendarContract.Events.CONTENT_URI
|
||||
ActivityStarter.start(context, this, intent = intent)
|
||||
viewModel.createEvent(context)
|
||||
}
|
||||
|
||||
binding.calendarDate.setOnClickListener {
|
||||
val menu = PopupMenu(context, binding.calendarDate)
|
||||
for (d in availableDays) {
|
||||
val availableDates = viewModel.availableDates
|
||||
for ((i, d) in availableDates.withIndex()) {
|
||||
menu.menu.add(
|
||||
Menu.NONE,
|
||||
d.toInt(),
|
||||
Menu.NONE,
|
||||
formatDay(d)
|
||||
Menu.NONE,
|
||||
i,
|
||||
Menu.NONE,
|
||||
formatDay(d)
|
||||
)
|
||||
}
|
||||
menu.setOnMenuItemClickListener {
|
||||
selectedDay = it.itemId.toLong()
|
||||
viewModel.selectDate(availableDates[it.itemId])
|
||||
true
|
||||
}
|
||||
menu.show()
|
||||
}
|
||||
|
||||
binding.calendarOpenApp.setOnClickListener {
|
||||
val startMillis = System.currentTimeMillis()
|
||||
val builder = CalendarContract.CONTENT_URI.buildUpon()
|
||||
builder.appendPath("time")
|
||||
ContentUris.appendId(builder, startMillis)
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
.setData(builder.build())
|
||||
ActivityStarter.start(context, binding.calendarWidgetRoot, intent = intent)
|
||||
viewModel.openCalendarApp(context)
|
||||
}
|
||||
|
||||
binding.calendarDateNext.setOnClickListener {
|
||||
val i = min(availableDays.lastIndex - 1, availableDays.indexOf(selectedDay))
|
||||
selectedDay = availableDays[i + 1]
|
||||
viewModel.nextDay()
|
||||
}
|
||||
binding.calendarDatePrev.setOnClickListener {
|
||||
val i = max(1, availableDays.indexOf(selectedDay))
|
||||
selectedDay = availableDays[i - 1]
|
||||
viewModel.previousDay()
|
||||
}
|
||||
|
||||
val viewModel: CalendarViewModel by (context as AppCompatActivity).viewModel()
|
||||
val favViewModel: FavoritesViewModel by (context as AppCompatActivity).viewModel()
|
||||
calendarEvents = viewModel.upcomingCalendarEvents
|
||||
pinnedCalendarEvents = favViewModel.pinnedCalendarEvents
|
||||
val calendarEvents = viewModel.calendarEvents
|
||||
val pinnedCalendarEvents = viewModel.pinnedCalendarEvents
|
||||
val hiddenPastEvents = viewModel.hiddenPastEvents
|
||||
val selectedDate = viewModel.selectedDate
|
||||
|
||||
calendarEvents.observe(context as AppCompatActivity, {
|
||||
if (!PermissionsManager.checkPermission(context, PermissionsManager.CALENDAR)) {
|
||||
binding.calendarWidgetList.submitItems(listOf(
|
||||
MissingPermission(
|
||||
context.getString(R.string.permission_calendar_widget),
|
||||
PermissionsManager.CALENDAR
|
||||
)
|
||||
))
|
||||
return@observe
|
||||
}
|
||||
val today = getToday()
|
||||
availableDays = it
|
||||
.map { ((it.startTime + zoneOffset) / (1000 * 60 * 60 * 24)) - today }
|
||||
.union(it.map { ((it.endTime + zoneOffset) / (1000 * 60 * 60 * 24)) - today })
|
||||
.union(listOf(0L))
|
||||
.toSet().toList().sorted()
|
||||
updateEventList()
|
||||
updateEventList(it, hiddenPastEvents.value ?: 0)
|
||||
})
|
||||
pinnedCalendarEvents.observe(context as AppCompatActivity) {
|
||||
val today = getToday()
|
||||
binding.calendarWidgetPinnedList.submitItems(it.filter {
|
||||
it.endTime > System.currentTimeMillis() &&
|
||||
(it.startTime + zoneOffset) / (1000 * 60 * 60 * 24) != today &&
|
||||
(it.endTime + zoneOffset) / (1000 * 60 * 60 * 24) != today
|
||||
}.sortedBy { it.startTime })
|
||||
binding.calendarWidgetPinnedList.submitItems(it)
|
||||
if (it.isEmpty()) {
|
||||
binding.calendarWidgetPinnedList.visibility = View.GONE
|
||||
binding.calendarUpcomingEventsTitle.visibility = View.GONE
|
||||
@@ -149,38 +117,41 @@ class CalendarWidget : LauncherWidget {
|
||||
}
|
||||
}
|
||||
|
||||
selectedDate.observe(context as AppCompatActivity) {
|
||||
binding.calendarDate.text = formatDay(it)
|
||||
}
|
||||
|
||||
binding.calendarWidgetRoot.layoutTransition = LayoutTransition().apply {
|
||||
enableTransitionType(LayoutTransition.CHANGING)
|
||||
}
|
||||
}
|
||||
|
||||
private fun getToday(): Long {
|
||||
return (System.currentTimeMillis() + zoneOffset) / (1000 * 60 * 60 * 24)
|
||||
}
|
||||
|
||||
private fun updateEventList(includePastEvents: Boolean = false) {
|
||||
val today = getToday()
|
||||
val events: MutableList<Searchable> = calendarEvents.value?.filter { (it.startTime + zoneOffset) / (1000 * 60 * 60 * 24) == today + selectedDay || (it.endTime + zoneOffset) / (1000 * 60 * 60 * 24) == today + selectedDay }
|
||||
?.toMutableList() ?: mutableListOf()
|
||||
private fun updateEventList(
|
||||
events: List<CalendarEvent>,
|
||||
hiddenPastDayEvents: Int
|
||||
) {
|
||||
val items = events.toMutableList<Searchable>()
|
||||
|
||||
if (events.isEmpty()) {
|
||||
events.add(
|
||||
InformationText(context.getString(R.string.calendar_widget_no_events))
|
||||
items.add(
|
||||
InformationText(context.getString(R.string.calendar_widget_no_events))
|
||||
)
|
||||
}
|
||||
val pastEvents = calendarEvents.value?.filter { (it.startTime + zoneOffset) / (1000 * 60 * 60 * 24) < today + selectedDay && (it.endTime + zoneOffset) / (1000 * 60 * 60 * 24) > today + selectedDay }
|
||||
|
||||
if (pastEvents?.isNotEmpty() == true) {
|
||||
if (includePastEvents) {
|
||||
events.addAll(pastEvents)
|
||||
} else {
|
||||
events.add(InformationText(resources.getQuantityString(R.plurals.calendar_widget_running_events, pastEvents.size, pastEvents.size)) {
|
||||
updateEventList(true)
|
||||
if (hiddenPastDayEvents > 0) {
|
||||
items.add(
|
||||
InformationText(
|
||||
resources.getQuantityString(
|
||||
R.plurals.calendar_widget_running_events,
|
||||
hiddenPastDayEvents,
|
||||
hiddenPastDayEvents
|
||||
)
|
||||
) {
|
||||
viewModel.showAllEvents()
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
binding.calendarWidgetList.submitItems(events)
|
||||
binding.calendarWidgetList.submitItems(items)
|
||||
}
|
||||
|
||||
|
||||
|
||||
@@ -6,13 +6,12 @@ import androidx.compose.runtime.Composable
|
||||
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
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchViewModel
|
||||
|
||||
@Composable
|
||||
fun applicationResults(): LazyListScope.(listState: LazyListState) -> Unit {
|
||||
val viewModel: AppViewModel = getViewModel()
|
||||
val apps by viewModel.applications.observeAsState(emptyList())
|
||||
val viewModel: SearchViewModel by viewModel()
|
||||
val apps by viewModel.appResults.observeAsState(emptyList())
|
||||
return {
|
||||
SearchableGrid(items = apps, listState = it)
|
||||
}
|
||||
|
||||
@@ -1,30 +1,19 @@
|
||||
package de.mm20.launcher2.ui.search
|
||||
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyListScope
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material.ContentAlpha
|
||||
import androidx.compose.material.LocalContentAlpha
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.CompositionLocalProvider
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.text.font.FontFamily
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.calculator.CalculatorViewModel
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.ui.component.SectionDivider
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchViewModel
|
||||
|
||||
@Composable
|
||||
fun calculatorItem(): LazyListScope.() -> Unit {
|
||||
val viewModel: CalculatorViewModel = getViewModel()
|
||||
val calculator by viewModel.calculator.observeAsState()
|
||||
val viewModel: SearchViewModel by viewModel()
|
||||
val calculator by viewModel.calculatorResult.observeAsState(null)
|
||||
return {
|
||||
calculator?.let {
|
||||
item {
|
||||
|
||||
@@ -6,14 +6,13 @@ import androidx.compose.runtime.Composable
|
||||
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
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchViewModel
|
||||
|
||||
@Composable
|
||||
fun favoriteResults(): LazyListScope.(listState: LazyListState) -> Unit {
|
||||
val viewModel: FavoritesViewModel = getViewModel()
|
||||
val viewModel: SearchViewModel by viewModel()
|
||||
|
||||
val favorites by viewModel.getFavorites(5).observeAsState(emptyList())
|
||||
val favorites by viewModel.favorites.observeAsState(emptyList())
|
||||
return {
|
||||
SearchableGrid(items = favorites, listState = it)
|
||||
}
|
||||
|
||||
@@ -5,13 +5,12 @@ import androidx.compose.runtime.Composable
|
||||
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
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchViewModel
|
||||
|
||||
@Composable
|
||||
fun fileResults(): LazyListScope.() -> Unit {
|
||||
val viewModel: FilesViewModel = getViewModel()
|
||||
val files by viewModel.files.observeAsState(emptyList())
|
||||
val viewModel: SearchViewModel by viewModel()
|
||||
val files by viewModel.fileResults.observeAsState(emptyList())
|
||||
return {
|
||||
files?.let { SearchableList(items = it) }
|
||||
}
|
||||
|
||||
@@ -7,13 +7,13 @@ import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.ui.component.SectionDivider
|
||||
import de.mm20.launcher2.wikipedia.WikipediaViewModel
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
import de.mm20.launcher2.ui.launcher.search.SearchViewModel
|
||||
import org.koin.androidx.compose.viewModel
|
||||
|
||||
@Composable
|
||||
fun wikipediaResult(): LazyListScope.() -> Unit {
|
||||
val viewModel: WikipediaViewModel = getViewModel()
|
||||
val wikipedia by viewModel.wikipedia.observeAsState()
|
||||
val viewModel: SearchViewModel by viewModel()
|
||||
val wikipedia by viewModel.wikipediaResult.observeAsState()
|
||||
return {
|
||||
wikipedia?.let {
|
||||
item {
|
||||
|
||||
@@ -1,241 +0,0 @@
|
||||
package de.mm20.launcher2.ui.searchable
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.text.format.DateUtils
|
||||
import androidx.compose.animation.AnimatedVisibility
|
||||
import androidx.compose.animation.ExperimentalAnimationApi
|
||||
import androidx.compose.animation.core.animateDpAsState
|
||||
import androidx.compose.animation.core.animateFloatAsState
|
||||
import androidx.compose.foundation.BorderStroke
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.Card
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.ArrowBack
|
||||
import androidx.compose.material.icons.rounded.Star
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.draw.drawWithCache
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.graphics.vector.rememberVectorPainter
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.painterResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.favorites.FavoritesViewModel
|
||||
import de.mm20.launcher2.ktx.tryStartActivity
|
||||
import de.mm20.launcher2.search.data.CalendarEvent
|
||||
import de.mm20.launcher2.ui.R
|
||||
import de.mm20.launcher2.ui.component.DefaultSwipeActions
|
||||
import de.mm20.launcher2.ui.search.Representation
|
||||
import de.mm20.launcher2.ui.toPixels
|
||||
import java.net.URLEncoder
|
||||
|
||||
@OptIn(ExperimentalAnimationApi::class)
|
||||
@Composable
|
||||
fun CalendarEventItem(
|
||||
event: CalendarEvent,
|
||||
initialRepresentation: Representation,
|
||||
modifier: Modifier
|
||||
) {
|
||||
|
||||
var representation by remember { mutableStateOf(initialRepresentation) }
|
||||
|
||||
val favViewModel: FavoritesViewModel = viewModel()
|
||||
val isPinned by favViewModel.isPinned(event).observeAsState()
|
||||
|
||||
val borderWidth = 8.dp.toPixels()
|
||||
|
||||
val context = LocalContext.current
|
||||
DefaultSwipeActions(item = event, enabled = representation == Representation.List) {
|
||||
Card(
|
||||
elevation = animateDpAsState(if (representation == Representation.Full) 4.dp else 0.dp).value,
|
||||
border = BorderStroke(
|
||||
width = animateDpAsState(if (representation == Representation.List) 1.dp else 0.dp).value,
|
||||
color = MaterialTheme.colorScheme.onSurface.copy(alpha = animateFloatAsState(if (representation == Representation.List) 0.18f else 0f).value)
|
||||
),
|
||||
modifier = modifier
|
||||
) {
|
||||
Row(
|
||||
modifier = (if (representation == Representation.List) Modifier.clickable(
|
||||
onClick = {
|
||||
representation = Representation.Full
|
||||
}) else Modifier)
|
||||
.fillMaxWidth()
|
||||
.drawWithCache {
|
||||
val color = Color(CalendarEvent.getDisplayColor(context, event.color))
|
||||
onDrawWithContent {
|
||||
drawContent()
|
||||
drawRect(color, size = size.copy(width = borderWidth))
|
||||
}
|
||||
}
|
||||
) {
|
||||
Column {
|
||||
Column(
|
||||
modifier = Modifier.padding(vertical = 14.dp, horizontal = 16.dp)
|
||||
) {
|
||||
Text(
|
||||
text = event.label,
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
AnimatedVisibility(
|
||||
representation == Representation.List
|
||||
) {
|
||||
Text(
|
||||
text = formatEventTime(event = event),
|
||||
style = MaterialTheme.typography.bodyMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
AnimatedVisibility(
|
||||
representation == Representation.Full
|
||||
) {
|
||||
Column(
|
||||
modifier = Modifier.padding(start = 4.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_time),
|
||||
contentDescription = null
|
||||
)
|
||||
Text(
|
||||
text = if (event.allDay) {
|
||||
stringResource(id = R.string.calendar_event_allday)
|
||||
} else {
|
||||
DateUtils.formatDateRange(
|
||||
LocalContext.current,
|
||||
event.startTime,
|
||||
event.endTime,
|
||||
DateUtils.FORMAT_SHOW_TIME
|
||||
)
|
||||
},
|
||||
modifier = Modifier
|
||||
.padding(start = 12.dp)
|
||||
)
|
||||
}
|
||||
|
||||
if (event.description.isNotEmpty()) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_description),
|
||||
contentDescription = null
|
||||
)
|
||||
Text(
|
||||
text = event.description,
|
||||
modifier = Modifier.padding(start = 12.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
if (event.location.isNotEmpty()) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.fillMaxWidth()
|
||||
.clickable(onClick = {
|
||||
val intent = Intent(Intent.ACTION_VIEW)
|
||||
intent.data = Uri.parse(
|
||||
"geo:0,0?q=${
|
||||
URLEncoder.encode(
|
||||
event.location,
|
||||
"utf8"
|
||||
)
|
||||
}"
|
||||
)
|
||||
context.tryStartActivity(intent, null)
|
||||
})
|
||||
.padding(12.dp),
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
Icon(
|
||||
painter = painterResource(id = R.drawable.ic_location),
|
||||
contentDescription = null
|
||||
)
|
||||
Text(
|
||||
text = event.location,
|
||||
modifier = Modifier.padding(start = 12.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
Row(
|
||||
modifier = Modifier.padding(bottom = 4.dp)
|
||||
) {
|
||||
IconButton(onClick = { representation = Representation.List }) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.ArrowBack,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
Spacer(modifier = Modifier.weight(1f))
|
||||
IconButton(onClick = {
|
||||
if (isPinned == true) {
|
||||
favViewModel.unpinItem(event)
|
||||
} else {
|
||||
favViewModel.pinItem(event)
|
||||
}
|
||||
}) {
|
||||
Icon(
|
||||
painter = if (isPinned == true) rememberVectorPainter(
|
||||
Icons.Rounded.Star
|
||||
) else painterResource(id = R.drawable.ic_star_outline),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun formatEventTime(event: CalendarEvent): String {
|
||||
val isToday = DateUtils.isToday(event.startTime) && DateUtils.isToday(event.endTime)
|
||||
return if (isToday) {
|
||||
if (event.allDay) {
|
||||
stringResource(R.string.calendar_event_allday)
|
||||
} else {
|
||||
DateUtils.formatDateRange(
|
||||
LocalContext.current,
|
||||
event.startTime,
|
||||
event.endTime,
|
||||
DateUtils.FORMAT_SHOW_TIME
|
||||
)
|
||||
}
|
||||
} else {
|
||||
if (event.allDay) {
|
||||
DateUtils.formatDateRange(
|
||||
LocalContext.current,
|
||||
event.startTime,
|
||||
event.endTime,
|
||||
DateUtils.FORMAT_SHOW_DATE
|
||||
)
|
||||
} else {
|
||||
DateUtils.formatDateRange(
|
||||
LocalContext.current,
|
||||
event.startTime,
|
||||
event.endTime,
|
||||
DateUtils.FORMAT_SHOW_TIME or DateUtils.FORMAT_SHOW_DATE
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,41 +0,0 @@
|
||||
package de.mm20.launcher2.ui.searchable
|
||||
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.key
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.unit.dp
|
||||
import de.mm20.launcher2.search.data.CalendarEvent
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.search.Representation
|
||||
|
||||
@Deprecated("Use [SearchableList] instead")
|
||||
@Composable
|
||||
fun DeprecatedSearchableList(items: List<Searchable>, modifier: Modifier = Modifier) {
|
||||
|
||||
Column(
|
||||
modifier = modifier
|
||||
.fillMaxWidth()
|
||||
.animateContentSize()
|
||||
) {
|
||||
|
||||
for (item in items) {
|
||||
Box(
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
key(item.key) {
|
||||
if (item is CalendarEvent) {
|
||||
CalendarEventItem(
|
||||
event = item,
|
||||
initialRepresentation = Representation.List,
|
||||
modifier = Modifier.fillMaxWidth()
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,259 +1,8 @@
|
||||
package de.mm20.launcher2.ui.widget
|
||||
|
||||
import android.content.Context
|
||||
import android.text.format.DateUtils
|
||||
import android.view.View
|
||||
import android.widget.FrameLayout
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.*
|
||||
import androidx.compose.material.DropdownMenu
|
||||
import androidx.compose.material.DropdownMenuItem
|
||||
import androidx.compose.material3.*
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.rounded.*
|
||||
import androidx.compose.runtime.*
|
||||
import androidx.compose.runtime.livedata.observeAsState
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.ComposeView
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.text.style.TextOverflow
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.lifecycle.viewmodel.compose.viewModel
|
||||
import de.mm20.launcher2.calendar.CalendarViewModel
|
||||
import de.mm20.launcher2.favorites.FavoritesViewModel
|
||||
import de.mm20.launcher2.search.data.Searchable
|
||||
import de.mm20.launcher2.ui.InformationText
|
||||
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
|
||||
import androidx.compose.runtime.Composable
|
||||
|
||||
@Composable
|
||||
fun CalendarWidget() {
|
||||
|
||||
val viewModel: CalendarViewModel = getViewModel()
|
||||
val favViewModel: FavoritesViewModel = viewModel()
|
||||
|
||||
val events by viewModel.upcomingCalendarEvents.observeAsState()
|
||||
val pinnedEvents by favViewModel.pinnedCalendarEvents.observeAsState(emptyList())
|
||||
val today = getToday()
|
||||
val availableDays: List<Long> = remember(events) {
|
||||
events?.map { ((it.startTime + zoneOffset) / (1000 * 60 * 60 * 24)) - today }
|
||||
?.union(events?.map { ((it.endTime + zoneOffset) / (1000 * 60 * 60 * 24)) - today }
|
||||
?: emptyList())
|
||||
?.union(listOf(0L))
|
||||
?.toSet()?.toList()?.sorted() ?: emptyList()
|
||||
}
|
||||
|
||||
var selectedDay by remember { mutableStateOf(0L) }
|
||||
|
||||
var showAll by remember { mutableStateOf(false) }
|
||||
|
||||
val pastEvents =
|
||||
events?.filter { (it.startTime + zoneOffset) / (1000 * 60 * 60 * 24) < today + selectedDay && (it.endTime + zoneOffset) / (1000 * 60 * 60 * 24) > today + selectedDay }
|
||||
|
||||
var noEvents = true
|
||||
val selectedEvents = remember(today, events, selectedDay, showAll) {
|
||||
events
|
||||
?.filter { (it.startTime + zoneOffset) / (1000 * 60 * 60 * 24) == today + selectedDay || (it.endTime + zoneOffset) / (1000 * 60 * 60 * 24) == today + selectedDay }
|
||||
?.also { noEvents = it.isEmpty() }
|
||||
?.union(if (showAll && pastEvents != null) pastEvents else emptyList())?.toList()
|
||||
?: listOf<Searchable>()
|
||||
}
|
||||
|
||||
|
||||
|
||||
Column(
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier.padding(8.dp)
|
||||
) {
|
||||
DaySelector(
|
||||
availableDays = availableDays,
|
||||
modifier = Modifier.weight(1f),
|
||||
onSelectDay = {
|
||||
selectedDay = it
|
||||
showAll = false
|
||||
}
|
||||
)
|
||||
IconButton(onClick = { /*TODO*/ }) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.OpenInNew,
|
||||
contentDescription = stringResource(R.string.calendar_menu_open_externally),
|
||||
)
|
||||
}
|
||||
IconButton(onClick = { /*TODO*/ }) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.Add,
|
||||
contentDescription = stringResource(R.string.calendar_widget_new_event)
|
||||
)
|
||||
}
|
||||
}
|
||||
Column(
|
||||
modifier = Modifier.padding(horizontal = 16.dp)
|
||||
) {
|
||||
if (noEvents) {
|
||||
InformationText(
|
||||
text = stringResource(id = R.string.calendar_widget_no_events)
|
||||
)
|
||||
}
|
||||
DeprecatedSearchableList(
|
||||
items = selectedEvents,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
if (!showAll && pastEvents?.isNotEmpty() == true) {
|
||||
InformationText(
|
||||
text = pluralResource(
|
||||
R.plurals.calendar_widget_running_events,
|
||||
pastEvents.size,
|
||||
pastEvents.size
|
||||
),
|
||||
modifier = Modifier.padding(bottom = 8.dp),
|
||||
onClick = {
|
||||
showAll = true
|
||||
}
|
||||
)
|
||||
}
|
||||
if (pinnedEvents.isNotEmpty()) {
|
||||
Text(
|
||||
text = stringResource(id = R.string.calendar_widget_pinned_events),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
DeprecatedSearchableList(
|
||||
items = pinnedEvents,
|
||||
modifier = Modifier.padding(bottom = 8.dp)
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun DaySelector(
|
||||
modifier: Modifier = Modifier,
|
||||
availableDays: List<Long>,
|
||||
onSelectDay: (Long) -> Unit
|
||||
) {
|
||||
var menuExpanded by remember { mutableStateOf(false) }
|
||||
var selectedDay by remember { mutableStateOf(0L) }
|
||||
|
||||
Row(
|
||||
modifier = modifier,
|
||||
verticalAlignment = Alignment.CenterVertically
|
||||
) {
|
||||
IconButton(onClick = {
|
||||
val i = max(1, availableDays.indexOf(selectedDay))
|
||||
selectedDay = availableDays[i - 1]
|
||||
onSelectDay(selectedDay)
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.ChevronLeft,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.weight(1f),
|
||||
horizontalArrangement = Arrangement.Center
|
||||
) {
|
||||
Row(
|
||||
modifier = Modifier
|
||||
.clickable(onClick = {
|
||||
menuExpanded = true
|
||||
})
|
||||
.padding(all = 12.dp)
|
||||
.wrapContentWidth()
|
||||
.animateContentSize()
|
||||
) {
|
||||
Text(
|
||||
overflow = TextOverflow.Ellipsis,
|
||||
maxLines = 1,
|
||||
modifier = Modifier
|
||||
.wrapContentWidth(),
|
||||
text = formatDay(LocalContext.current, selectedDay),
|
||||
style = MaterialTheme.typography.titleLarge
|
||||
)
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.ArrowDropDown,
|
||||
modifier = Modifier.size(24.dp),
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
DropdownMenu(expanded = menuExpanded, onDismissRequest = {
|
||||
menuExpanded = false
|
||||
}) {
|
||||
|
||||
for (day in availableDays) {
|
||||
DropdownMenuItem(onClick = {
|
||||
selectedDay = day
|
||||
menuExpanded = false
|
||||
onSelectDay(selectedDay)
|
||||
}) {
|
||||
Text(
|
||||
text = formatDay(LocalContext.current, day),
|
||||
style = MaterialTheme.typography.titleMedium
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
IconButton(onClick = {
|
||||
val i = min(availableDays.lastIndex - 1, availableDays.indexOf(selectedDay))
|
||||
selectedDay = availableDays[i + 1]
|
||||
onSelectDay(selectedDay)
|
||||
}) {
|
||||
Icon(
|
||||
imageVector = Icons.Rounded.ChevronRight,
|
||||
contentDescription = null
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private fun getToday(): Long {
|
||||
return (System.currentTimeMillis() + zoneOffset) / (1000 * 60 * 60 * 24)
|
||||
}
|
||||
|
||||
private val zoneOffset
|
||||
get() = Calendar.getInstance().timeZone.getOffset(System.currentTimeMillis()).toLong()
|
||||
|
||||
private fun formatDay(context: Context, day: Long): String {
|
||||
return when (day) {
|
||||
0L -> context.getString(R.string.date_today)
|
||||
1L -> context.getString(R.string.date_tomorrow)
|
||||
else -> DateUtils.formatDateTime(
|
||||
context,
|
||||
(getToday() + day) * (1000 * 60 * 60 * 24),
|
||||
DateUtils.FORMAT_SHOW_DATE or DateUtils.FORMAT_SHOW_WEEKDAY or DateUtils.FORMAT_ABBREV_WEEKDAY
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
object CalendarWidgetShim {
|
||||
fun getLegacyView(context: Context): View {
|
||||
val composeView = ComposeView(context)
|
||||
composeView.id = FrameLayout.generateViewId()
|
||||
composeView.setContent {
|
||||
LauncherTheme {
|
||||
CompositionLocalProvider(LocalContentColor provides MaterialTheme.colorScheme.onSurface) {
|
||||
Column {
|
||||
CalendarWidget()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return composeView
|
||||
}
|
||||
}
|
||||
@@ -9,9 +9,7 @@ import androidx.compose.material3.TextButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import de.mm20.launcher2.calendar.CalendarViewModel
|
||||
import de.mm20.launcher2.ui.component.TextClock
|
||||
import org.koin.androidx.compose.getViewModel
|
||||
|
||||
@Composable
|
||||
fun DatePart() {
|
||||
|
||||
Reference in New Issue
Block a user