blob: e88ea1cbd0a98848e83199800a96efef9c5c5df0 [file] [log] [blame]
/*
* Copyright 2021 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.wear.watchface.editor.sample
import android.app.Activity
import android.content.Context
import android.content.Intent
import android.graphics.Typeface
import android.graphics.drawable.Drawable
import android.graphics.drawable.Icon
import android.graphics.drawable.LayerDrawable
import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.support.wearable.watchface.Constants
import android.text.SpannableStringBuilder
import android.text.style.StyleSpan
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.LifecycleEventObserver
import androidx.recyclerview.widget.RecyclerView
import androidx.wear.complications.ProviderInfoRetriever
import androidx.wear.watchface.R
import androidx.wear.widget.SwipeDismissFrameLayout
import androidx.wear.widget.WearableLinearLayoutManager
import androidx.wear.widget.WearableRecyclerView
import kotlinx.coroutines.launch
/**
* Top level configuration fragment. Lets the user select whether they want to select a complication
* to configure, configure a background complication or select an option from a user style setting.
* Should only be used if theres's at least two items from that list.
*/
internal class ConfigFragment : Fragment() {
private val watchFaceConfigActivity: WatchFaceConfigActivity
get() = activity as WatchFaceConfigActivity
private lateinit var view: SwipeDismissFrameLayout
private lateinit var configViewAdapter: ConfigViewAdapter
private var lifecycleObserver = LifecycleEventObserver { _, event ->
if (event == Lifecycle.Event.ON_START) {
initConfigOptions()
}
}
override fun onAttach(context: Context) {
super.onAttach(context)
lifecycle.addObserver(lifecycleObserver)
}
override fun onCreateView(
inflater: LayoutInflater,
container: ViewGroup?,
savedState: Bundle?
): View {
view = inflater.inflate(R.layout.config_layout, container, false) as
SwipeDismissFrameLayout
view.addCallback(object : SwipeDismissFrameLayout.Callback() {
override fun onDismissed(layout: SwipeDismissFrameLayout) {
parentFragmentManager.popBackStackImmediate()
}
})
return view
}
private fun initConfigOptions() {
val editingSession = watchFaceConfigActivity.editorSession
val hasBackgroundComplication = editingSession.backgroundComplicationId != null
val numComplications = editingSession.complicationState.size
val hasNonBackgroundComplication =
numComplications > if (hasBackgroundComplication) 1 else 0
val configOptions = ArrayList<ConfigOption>()
if (hasNonBackgroundComplication) {
configOptions.add(
ConfigOption(
id = Constants.KEY_COMPLICATIONS_SETTINGS,
icon = Icon.createWithResource(
context,
R.drawable.ic_elements_settings_complications
),
title = resources.getString(R.string.settings_complications),
summary = ""
)
)
}
if (hasBackgroundComplication) {
configOptions.add(createBackgroundConfigOption())
}
for (styleCategory in editingSession.userStyleSchema.userStyleSettings) {
configOptions.add(
ConfigOption(
id = styleCategory.id,
icon = styleCategory.icon,
title = styleCategory.displayName.toString(),
summary = styleCategory.description.toString()
)
)
}
configViewAdapter = ConfigViewAdapter(
requireContext(),
configOptions,
this::onItemClick
)
view.findViewById<WearableRecyclerView>(R.id.configOptionsList).apply {
adapter = configViewAdapter
layoutManager = WearableLinearLayoutManager(context)
}
lifecycle.removeObserver(lifecycleObserver)
}
private fun createBackgroundConfigOption(): ConfigOption {
// Initially assume there is no background image provider.
val backgroundConfigOption = ConfigOption(
id = Constants.KEY_BACKGROUND_IMAGE_SETTINGS,
icon = Icon.createWithResource(
context,
R.drawable.ic_elements_comps_bg
),
title = getResources().getString(R.string.settings_background_image),
summary = resources.getString(R.string.none_background_image_provider)
)
// Update the summary with the actual background complication provider name, if there is
// one.
watchFaceConfigActivity.coroutineScope.launch {
val providerInfoRetriever = ProviderInfoRetriever(activity as WatchFaceConfigActivity)
val infoArray = providerInfoRetriever.retrieveProviderInfo(
watchFaceConfigActivity.editorSession.watchFaceComponentName,
intArrayOf(watchFaceConfigActivity.editorSession.backgroundComplicationId!!)
)
infoArray?.let {
it[0].info?.apply {
backgroundConfigOption.summary = providerName!!
}
configViewAdapter.notifyDataSetChanged()
}
providerInfoRetriever.close()
}
return backgroundConfigOption
}
/** Called with the result from the call to watchFaceImpl.onComplicationConfigTap() above. */
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (requestCode == Constants.PROVIDER_CHOOSER_REQUEST_CODE &&
resultCode == Activity.RESULT_OK
) {
activity?.finish()
}
}
override fun onDestroy() {
super.onDestroy()
}
private fun onItemClick(configKey: String) {
val editingSession = watchFaceConfigActivity.editorSession
when (configKey) {
Constants.KEY_COMPLICATIONS_SETTINGS ->
watchFaceConfigActivity.fragmentController.showComplicationConfigSelectionFragment()
Constants.KEY_BACKGROUND_IMAGE_SETTINGS -> {
watchFaceConfigActivity.coroutineScope.launch {
watchFaceConfigActivity.fragmentController.showComplicationConfig(
editingSession.backgroundComplicationId!!
)
}
}
else -> {
watchFaceConfigActivity.fragmentController.showStyleConfigFragment(
configKey,
editingSession.userStyleSchema,
editingSession.userStyle
)
}
}
}
}
internal data class ConfigOption(
val id: String,
val icon: Icon?,
val title: String,
var summary: String
)
internal class ConfigViewHolder(view: View) : RecyclerView.ViewHolder(view) {
var configOption: ConfigOption? = null
}
internal class Helper {
companion object {
/**
* Wraps a given [Drawable] with a standard background to match the normal preference icon
* styling. The wrapping is idempotent, calling it multiple times will only wrap the icon
* once.
*
* @param context the current [Context], used for resolving resources.
* @param icon the icon to wrap.
* @return the wrapped icon.
*/
fun wrapIcon(
context: Context,
icon: Drawable
): Drawable {
if (icon is LayerDrawable && icon.findDrawableByLayerId(R.id.nested_icon) != null) {
return icon // icon was already wrapped, return the icon without modifying it
}
val wrappedDrawable =
(context.getDrawable(R.drawable.preference_wrapped_icon) as LayerDrawable)
wrappedDrawable.setDrawableByLayerId(R.id.nested_icon, icon)
return wrappedDrawable
}
}
}
/** Adapter for top level config options. */
internal class ConfigViewAdapter(
private val context: Context,
private val configOptions: List<ConfigOption>,
val clickListener: (String) -> Unit
) :
RecyclerView.Adapter<ConfigViewHolder>() {
private val handler = Handler(Looper.getMainLooper())
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int) = ConfigViewHolder(
LayoutInflater.from(parent.context).inflate(
R.layout.configlist_item_layout,
parent,
false
)
).apply {
itemView.setOnClickListener { clickListener(configOption!!.id) }
}
override fun onBindViewHolder(holder: ConfigViewHolder, position: Int) {
val configOption = configOptions[position]
holder.configOption = configOption
val textView = holder.itemView as TextView
val builder = SpannableStringBuilder()
builder.append(configOption.title)
builder.setSpan(StyleSpan(Typeface.BOLD), 0, configOption.title.length, 0)
builder.append("\n")
builder.append(configOption.summary)
textView.text = builder
configOption.icon?.loadDrawableAsync(
context,
{ drawable ->
textView.setCompoundDrawablesRelativeWithIntrinsicBounds(
Helper.wrapIcon(context, drawable),
/* top = */ null,
/* end = */ null,
/* bottom = */ null
)
},
handler
)
}
override fun getItemCount() = configOptions.size
}