Implement improved task creation flow

This commit is contained in:
Phillip Thelen 2022-06-23 16:11:01 +02:00
parent 253210d0c2
commit cf2e46d199
14 changed files with 238 additions and 82 deletions

View file

@ -6,13 +6,22 @@ import android.view.ViewGroup
import android.widget.FrameLayout import android.widget.FrameLayout
import androidx.activity.ComponentActivity import androidx.activity.ComponentActivity
import androidx.core.view.children import androidx.core.view.children
import androidx.lifecycle.lifecycleScope
import androidx.viewbinding.ViewBinding import androidx.viewbinding.ViewBinding
import androidx.wear.activity.ConfirmationActivity import androidx.wear.activity.ConfirmationActivity
import com.google.android.gms.tasks.Tasks
import com.google.android.gms.wearable.CapabilityClient
import com.google.android.gms.wearable.MessageClient
import com.google.android.gms.wearable.Wearable
import com.habitrpg.android.habitica.databinding.ActivityWrapperBinding import com.habitrpg.android.habitica.databinding.ActivityWrapperBinding
import com.habitrpg.wearos.habitica.ui.viewmodels.BaseViewModel import com.habitrpg.wearos.habitica.ui.viewmodels.BaseViewModel
import com.habitrpg.wearos.habitica.ui.views.IndeterminateProgressView import com.habitrpg.wearos.habitica.ui.views.IndeterminateProgressView
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
abstract class BaseActivity<B: ViewBinding, VM: BaseViewModel> : ComponentActivity() { abstract class BaseActivity<B: ViewBinding, VM: BaseViewModel> : ComponentActivity() {
val messageClient: MessageClient by lazy { Wearable.getMessageClient(this) }
val capabilityClient: CapabilityClient by lazy { Wearable.getCapabilityClient(this) }
companion object { companion object {
var currentActivityClassName: String? = null var currentActivityClassName: String? = null
} }
@ -64,4 +73,37 @@ abstract class BaseActivity<B: ViewBinding, VM: BaseViewModel> : ComponentActivi
} }
} }
} }
internal fun openRemoteActivity(url: String) {
sendMessage("open_activity", url, null)
}
internal fun sendMessage(
permission: String,
url: String,
data: ByteArray?,
function: ((Boolean) -> Unit)? = null
) {
lifecycleScope.launch(Dispatchers.IO) {
val info = Tasks.await(
capabilityClient.getCapability(
permission,
CapabilityClient.FILTER_REACHABLE
)
)
val nodeID = info.nodes.firstOrNull { it.isNearby }
if (nodeID != null) {
function?.invoke(true)
Tasks.await(
messageClient.sendMessage(
nodeID.id,
url,
data
)
)
} else {
function?.invoke(false)
}
}
}
} }

View file

@ -81,7 +81,7 @@ class LoginActivity: BaseActivity<ActivityLoginBinding, LoginViewModel>() {
} }
private fun openRegisterOnPhone() { private fun openRegisterOnPhone() {
openRemoteActivity("/show/register")
} }
private fun loginLocal() { private fun loginLocal() {

View file

@ -4,9 +4,6 @@ import android.os.Bundle
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.view.children import androidx.core.view.children
import androidx.core.view.isVisible import androidx.core.view.isVisible
import com.google.android.gms.wearable.CapabilityClient
import com.google.android.gms.wearable.MessageClient
import com.google.android.gms.wearable.Wearable
import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ActivityRyaBinding import com.habitrpg.android.habitica.databinding.ActivityRyaBinding
import com.habitrpg.android.habitica.databinding.RowDailyBinding import com.habitrpg.android.habitica.databinding.RowDailyBinding
@ -18,8 +15,6 @@ import dagger.hilt.android.AndroidEntryPoint
@AndroidEntryPoint @AndroidEntryPoint
class RYAActivity : BaseActivity<ActivityRyaBinding, RYAViewModel>() { class RYAActivity : BaseActivity<ActivityRyaBinding, RYAViewModel>() {
val messageClient: MessageClient by lazy { Wearable.getMessageClient(this) }
val capabilityClient: CapabilityClient by lazy { Wearable.getCapabilityClient(this) }
override val viewModel: RYAViewModel by viewModels() override val viewModel: RYAViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
@ -40,7 +35,7 @@ class RYAActivity : BaseActivity<ActivityRyaBinding, RYAViewModel>() {
} }
binding.phoneButton.setOnClickListener { binding.phoneButton.setOnClickListener {
openRemoteActivity("/show/rya")
} }
binding.startDayButton.setOnClickListener { binding.startDayButton.setOnClickListener {

View file

@ -5,10 +5,6 @@ import android.os.Bundle
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.google.android.gms.tasks.Tasks
import com.google.android.gms.wearable.CapabilityClient
import com.google.android.gms.wearable.MessageClient
import com.google.android.gms.wearable.Wearable
import com.habitrpg.android.habitica.databinding.ActivitySplashBinding import com.habitrpg.android.habitica.databinding.ActivitySplashBinding
import com.habitrpg.wearos.habitica.ui.viewmodels.SplashViewModel import com.habitrpg.wearos.habitica.ui.viewmodels.SplashViewModel
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
@ -18,8 +14,6 @@ import kotlinx.coroutines.launch
@AndroidEntryPoint @AndroidEntryPoint
class SplashActivity: BaseActivity<ActivitySplashBinding, SplashViewModel>() { class SplashActivity: BaseActivity<ActivitySplashBinding, SplashViewModel>() {
override val viewModel: SplashViewModel by viewModels() override val viewModel: SplashViewModel by viewModels()
private val messageClient: MessageClient by lazy { Wearable.getMessageClient(this) }
private val capabilityClient: CapabilityClient by lazy { Wearable.getCapabilityClient(this) }
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
binding = ActivitySplashBinding.inflate(layoutInflater) binding = ActivitySplashBinding.inflate(layoutInflater)
@ -38,17 +32,14 @@ class SplashActivity: BaseActivity<ActivitySplashBinding, SplashViewModel>() {
} }
messageClient.addListener(viewModel) messageClient.addListener(viewModel)
lifecycleScope.launch(Dispatchers.IO) { sendMessage("provide_auth", "/request/auth", null) {
val info = Tasks.await(capabilityClient.getCapability("provide_auth", CapabilityClient.FILTER_REACHABLE)) if (it) {
val nodeID = info.nodes.firstOrNull { it.isNearby } showAccountLoader(true)
if (nodeID != null) { } else {
showAccountLoader(true) showAccountLoader(false)
Tasks.await(messageClient.sendMessage(nodeID.id, "/request/auth", null)) startLoginActivity()
} else { }
showAccountLoader(false)
startLoginActivity()
} }
}
} }
private fun startMainActivity() { private fun startMainActivity() {

View file

@ -3,22 +3,13 @@ package com.habitrpg.wearos.habitica.ui.activities
import android.os.Bundle import android.os.Bundle
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.lifecycle.lifecycleScope
import com.google.android.gms.tasks.Tasks
import com.google.android.gms.wearable.CapabilityClient
import com.google.android.gms.wearable.MessageClient
import com.google.android.gms.wearable.Wearable
import com.habitrpg.android.habitica.databinding.ActivityTaskDetailBinding import com.habitrpg.android.habitica.databinding.ActivityTaskDetailBinding
import com.habitrpg.wearos.habitica.ui.viewmodels.TaskDetailViewModel import com.habitrpg.wearos.habitica.ui.viewmodels.TaskDetailViewModel
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
import java.util.Locale import java.util.Locale
@AndroidEntryPoint @AndroidEntryPoint
class TaskDetailActivity: BaseActivity<ActivityTaskDetailBinding, TaskDetailViewModel>() { class TaskDetailActivity: BaseActivity<ActivityTaskDetailBinding, TaskDetailViewModel>() {
val messageClient: MessageClient by lazy { Wearable.getMessageClient(this) }
val capabilityClient: CapabilityClient by lazy { Wearable.getCapabilityClient(this) }
override val viewModel: TaskDetailViewModel by viewModels() override val viewModel: TaskDetailViewModel by viewModels()
@ -34,13 +25,7 @@ class TaskDetailActivity: BaseActivity<ActivityTaskDetailBinding, TaskDetailView
} }
private fun openEditFormOnPhone() { private fun openEditFormOnPhone() {
lifecycleScope.launch(Dispatchers.IO) { sendMessage("edit_task", "/tasks/edit", viewModel.taskID?.toByteArray())
val info = Tasks.await(capabilityClient.getCapability("edit_task", CapabilityClient.FILTER_REACHABLE))
val nodeID = info.nodes.firstOrNull { it.isNearby }
if (nodeID != null) {
Tasks.await(messageClient.sendMessage(nodeID.id, "/tasks/edit", viewModel.taskID?.toByteArray()))
}
}
} }
private fun subscribeUI() { private fun subscribeUI() {

View file

@ -1,68 +1,95 @@
package com.habitrpg.wearos.habitica.ui.activities package com.habitrpg.wearos.habitica.ui.activities
import android.app.AlertDialog import android.content.Intent
import android.content.res.ColorStateList
import android.os.Bundle import android.os.Bundle
import android.widget.ArrayAdapter import android.view.inputmethod.EditorInfo
import android.widget.TextView
import androidx.activity.viewModels import androidx.activity.viewModels
import androidx.core.content.ContextCompat
import androidx.core.view.isVisible import androidx.core.view.isVisible
import androidx.core.widget.doOnTextChanged
import androidx.lifecycle.lifecycleScope import androidx.lifecycle.lifecycleScope
import com.habitrpg.android.habitica.R import com.habitrpg.android.habitica.R
import com.habitrpg.android.habitica.databinding.ActivityTaskFormBinding import com.habitrpg.android.habitica.databinding.ActivityTaskFormBinding
import com.habitrpg.common.habitica.models.tasks.TaskType import com.habitrpg.common.habitica.models.tasks.TaskType
import com.habitrpg.wearos.habitica.ui.viewmodels.TaskFormViewModel import com.habitrpg.wearos.habitica.ui.viewmodels.TaskFormViewModel
import dagger.hilt.android.AndroidEntryPoint import dagger.hilt.android.AndroidEntryPoint
import kotlinx.coroutines.CoroutineExceptionHandler
import kotlinx.coroutines.launch import kotlinx.coroutines.launch
@AndroidEntryPoint @AndroidEntryPoint
class TaskFormActivity: BaseActivity<ActivityTaskFormBinding, TaskFormViewModel>() { class TaskFormActivity : BaseActivity<ActivityTaskFormBinding, TaskFormViewModel>() {
var taskType: TaskType? = null var taskType: TaskType? = null
set(value) { set(value) {
field = value field = value
binding.taskTypeButton.text = value?.value updateTaskTypeButton(binding.todoButton, TaskType.TODO)
} updateTaskTypeButton(binding.dailyButton, TaskType.DAILY)
updateTaskTypeButton(binding.habitButton, TaskType.HABIT)
binding.confirmationTitle.text = getString(R.string.create_task, value?.value)
}
override val viewModel: TaskFormViewModel by viewModels() override val viewModel: TaskFormViewModel by viewModels()
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
binding = ActivityTaskFormBinding.inflate(layoutInflater) binding = ActivityTaskFormBinding.inflate(layoutInflater)
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
binding.editText.setOnEditorActionListener { _, actionId, event ->
if (actionId == EditorInfo.IME_ACTION_SEARCH || actionId == EditorInfo.IME_ACTION_DONE) {
if (binding.editText.text?.isNotEmpty() == true) {
binding.editTaskWrapper.isVisible = false
binding.taskConfirmationWrapper.isVisible = true
binding.confirmationText.text = binding.editText.text
binding.editText.clearFocus()
}
}
false
}
binding.editButton.setOnClickListener {
binding.editTaskWrapper.isVisible = true
binding.taskConfirmationWrapper.isVisible = false
}
binding.todoButton.setOnClickListener { taskType = TaskType.TODO }
binding.dailyButton.setOnClickListener { taskType = TaskType.DAILY }
binding.habitButton.setOnClickListener { taskType = TaskType.HABIT }
binding.editText.doOnTextChanged { text, _, _, _ ->
binding.saveButton.isEnabled = text?.isNotBlank() == true
}
binding.taskTypeButton.setOnClickListener {
showTaskTypeSelector()
}
binding.saveButton.setOnClickListener { binding.saveButton.setOnClickListener {
binding.saveButton.isEnabled = false binding.saveButton.isEnabled = false
lifecycleScope.launch { lifecycleScope.launch(CoroutineExceptionHandler { _, _ ->
binding.saveButton.isEnabled = true
binding.editTaskWrapper.isVisible = true
binding.taskConfirmationWrapper.isVisible = false
}) {
viewModel.saveTask(binding.editText.text, taskType) viewModel.saveTask(binding.editText.text, taskType)
finish() finish()
parent.startActivity(Intent(parent, TaskListActivity::class.java).apply {
putExtra("task_type", taskType?.value)
})
} }
} }
if (intent.extras?.containsKey("task_type") == true) { if (intent.extras?.containsKey("task_type") == true) {
taskType = TaskType.from(intent.getStringExtra("task_type")) taskType = TaskType.from(intent.getStringExtra("task_type"))
binding.taskTypeButton.isVisible = false binding.taskTypeHeader.isVisible = false
binding.taskTypeWrapper.isVisible = false
binding.header.textView.text = getString(R.string.create_task, taskType?.value) binding.header.textView.text = getString(R.string.create_task, taskType?.value)
binding.confirmationTitle.text = getString(R.string.create_task, taskType?.value)
} else { } else {
taskType = TaskType.TODO taskType = TaskType.TODO
binding.header.textView.text = getString(R.string.create_task_title) binding.header.textView.text = getString(R.string.create_task_title)
} }
} }
private fun showTaskTypeSelector() { private fun updateTaskTypeButton(button: TextView, thisType: TaskType) {
val taskTypes = arrayOf( if (taskType == thisType) {
TaskType.HABIT, button.backgroundTintList =
TaskType.DAILY, ColorStateList.valueOf(ContextCompat.getColor(this, R.color.watch_purple_10))
TaskType.TODO, button.background.alpha = 100
TaskType.REWARD button.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.radio_checked, 0)
) } else {
val adapter = ArrayAdapter(this, R.layout.spinner_item, taskTypes) button.backgroundTintList =
val alert = AlertDialog.Builder(this).setAdapter(adapter) { _, which -> ColorStateList.valueOf(ContextCompat.getColor(this, R.color.watch_purple_5))
taskType = taskTypes[which] button.background.alpha = 255
binding.taskTypeButton.text = taskType?.value button.setCompoundDrawablesWithIntrinsicBounds(0, 0, R.drawable.radio_unchecked, 0)
} }
alert.show()
} }
} }

View file

@ -0,0 +1,5 @@
<vector android:height="24dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M3,17.25V21h3.75L17.81,9.94l-3.75,-3.75L3,17.25zM20.71,7.04c0.39,-0.39 0.39,-1.02 0,-1.41l-2.34,-2.34c-0.39,-0.39 -1.02,-0.39 -1.41,0l-1.83,1.83 3.75,3.75 1.83,-1.83z"/>
</vector>

View file

@ -0,0 +1,14 @@
<?xml version="1.0" encoding="utf-8"?>
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
<item android:width="10dp" android:height="10dp" android:gravity="center">
<shape android:shape="oval">
<solid android:color="@color/watch_purple_200" />
</shape>
</item>
<item android:width="18dp" android:height="18dp">
<shape android:shape="oval">
<stroke android:width="2dp" android:color="@color/watch_purple_200" />
<padding android:top="4dp" android:right="4dp" android:left="4dp" android:bottom="4dp" />
</shape>
</item>
</layer-list>

View file

@ -0,0 +1,5 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android" android:shape="oval">
<size android:width="18dp" android:height="18dp" />
<stroke android:width="2dp" android:color="@color/gray_200" />
</shape>

View file

@ -0,0 +1,5 @@
<vector android:autoMirrored="true" android:height="24dp"
android:tint="#FFFFFF" android:viewportHeight="24"
android:viewportWidth="24" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
<path android:fillColor="@android:color/white" android:pathData="M2.01,21L23,12 2.01,3 2,10l15,2 -15,2z"/>
</vector>

View file

@ -1,14 +1,61 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto" xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"> android:layout_height="match_parent"
xmlns:tools="http://schemas.android.com/tools">
<com.habitrpg.wearos.habitica.ui.views.HabiticaScrollView <com.habitrpg.wearos.habitica.ui.views.HabiticaScrollView
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
app:layout_boxedEdges="all"> app:layout_boxedEdges="all">
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout <LinearLayout
android:id="@+id/task_confirmation_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:visibility="gone"
tools:visibility="visible"
android:gravity="center">
<TextView
android:id="@+id/confirmation_title"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Text.Subheader1"
android:textSize="14sp"
android:textColor="@color/gray_200"
/>
<TextView
android:id="@+id/confirmation_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
style="@style/Text.Body1"
android:textSize="14sp"
android:textColor="@color/white"
/>
<Button
android:id="@+id/save_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/ChipButton"
android:text="@string/save"
android:gravity="start|center_vertical"
android:layout_marginTop="20dp"
android:layout_marginBottom="4dp"
android:drawableStart="@drawable/save"/>
<Button
android:id="@+id/edit_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/ChipButton"
android:gravity="start|center_vertical"
android:text="@string/action_edit"
android:drawableStart="@drawable/edit"/>
</LinearLayout>
<LinearLayout
android:id="@+id/edit_task_wrapper"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginBottom="38dp" android:layout_marginBottom="38dp"
@ -21,20 +68,52 @@
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:importantForAutofill="no" android:importantForAutofill="no"
android:minHeight="52dp"
android:paddingHorizontal="18dp"
android:hint="@string/task_title_hint"
android:background="@drawable/row_background_outline"
android:inputType="textCapWords" /> android:inputType="textCapWords" />
<Button <TextView
android:id="@+id/task_type_button" android:id="@+id/task_type_header"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
style="@style/Chip" android:gravity="center"
tools:text="Task Type" android:layout_marginTop="20dp"
android:textAllCaps="false" android:layout_marginBottom="12dp"
android:textColor="@color/watch_purple_500"/> android:textSize="14sp"
android:textColor="@color/watch_purple_200"
android:text="@string/task_type" />
<LinearLayout
android:id="@+id/task_type_wrapper"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/todo_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Chip"
android:gravity="start|center_vertical"
android:paddingHorizontal="20dp"
android:text="@string/todo" />
<TextView
android:id="@+id/daily_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Chip"
android:gravity="start|center_vertical"
android:paddingHorizontal="20dp"
android:text="@string/daily" />
<TextView
android:id="@+id/habit_button"
android:layout_width="match_parent"
android:layout_height="wrap_content"
style="@style/Chip"
android:gravity="start|center_vertical"
android:paddingHorizontal="20dp"
android:text="@string/habit" />
</LinearLayout>
</LinearLayout> </LinearLayout>
</FrameLayout>
</com.habitrpg.wearos.habitica.ui.views.HabiticaScrollView> </com.habitrpg.wearos.habitica.ui.views.HabiticaScrollView>
<com.habitrpg.wearos.habitica.ui.views.AddTaskButton
android:id="@+id/save_button"
android:layout_width="match_parent"
android:layout_height="38dp"
android:layout_gravity="bottom" />
</FrameLayout> </FrameLayout>

View file

@ -48,11 +48,12 @@
<color name="watch_gray_100">#A5A1AC</color> <color name="watch_gray_100">#A5A1AC</color>
<color name="watch_gray_10">#878190</color> <color name="watch_gray_10">#878190</color>
<color name="watch_gray_5">#686274</color> <color name="watch_gray_5">#686274</color>
<color name="watch_gray_1">#34313A</color>
<color name="ic_launcher_background">@color/brand_300</color> <color name="ic_launcher_background">@color/brand_300</color>
<color name="surface">#23202A</color> <color name="surface">@color/watch_purple_5</color>
<color name="bar_background_color">#34313a</color> <color name="bar_background_color">@color/gray_1</color>
<color name="exp_bar_color">@color/watch_yellow_100</color> <color name="exp_bar_color">@color/watch_yellow_100</color>
<color name="mp_bar_color">@color/watch_blue_100</color> <color name="mp_bar_color">@color/watch_blue_100</color>
<color name="hp_bar_color">@color/watch_red_100</color> <color name="hp_bar_color">@color/watch_red_100</color>

View file

@ -24,4 +24,7 @@
<string name="checklist_disclaimer">Task checklists are available on the phone</string> <string name="checklist_disclaimer">Task checklists are available on the phone</string>
<string name="start_new_day">Start new day</string> <string name="start_new_day">Start new day</string>
<string name="check_off_yesterday">Check off any you did yesterday:</string> <string name="check_off_yesterday">Check off any you did yesterday:</string>
<string name="task_type">Task Type</string>
<string name="task_title_hint">Task title...</string>
<string name="save">Save</string>
</resources> </resources>

View file

@ -53,6 +53,10 @@
<item name="android:textSize">16sp</item> <item name="android:textSize">16sp</item>
</style> </style>
<style name="Text.Body1">
<item name="android:fontFamily">sans-serif-medium</item>
<item name="android:textSize">14sp</item>
</style>
<style name="Text.Body2"> <style name="Text.Body2">
<item name="android:textSize">14sp</item> <item name="android:textSize">14sp</item>
</style> </style>