Add support for ambient mode on wearable

This commit is contained in:
William Brawner 2020-09-22 17:57:04 -07:00
parent 26c6977fd6
commit fbf60ac13e
11 changed files with 214 additions and 53 deletions

View file

@ -0,0 +1,123 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
<option name="WRAP_ON_TYPING" value="1" />
</codeStyleSettings>
</code_scheme>
</component>

View file

@ -18,8 +18,8 @@ import androidx.fragment.app.activityViewModels
import androidx.lifecycle.Observer import androidx.lifecycle.Observer
import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewModelScope
import androidx.navigation.fragment.findNavController import androidx.navigation.fragment.findNavController
import com.robinhood.ticker.TickerUtils
import com.google.android.gms.wearable.* import com.google.android.gms.wearable.*
import com.robinhood.ticker.TickerUtils
import com.wbrawner.trainterval.Logger import com.wbrawner.trainterval.Logger
import com.wbrawner.trainterval.R import com.wbrawner.trainterval.R
import com.wbrawner.trainterval.shared.IntervalTimerDao import com.wbrawner.trainterval.shared.IntervalTimerDao
@ -92,7 +92,7 @@ class ActiveTimerFragment : Fragment(), MessageClient.OnMessageReceivedListener
it.setSupportActionBar(toolbar) it.setSupportActionBar(toolbar)
it.supportActionBar?.setDisplayHomeAsUpEnabled(true) it.supportActionBar?.setDisplayHomeAsUpEnabled(true)
} }
timeRemaining.setCharacterLists(TickerUtils.provideNumberList() + ":") timeRemaining.setCharacterLists(TickerUtils.provideNumberList())
timerSets.setCharacterLists(TickerUtils.provideNumberList()) timerSets.setCharacterLists(TickerUtils.provideNumberList())
timerRounds.setCharacterLists(TickerUtils.provideNumberList()) timerRounds.setCharacterLists(TickerUtils.provideNumberList())
coroutineScope = CoroutineScope(Dispatchers.Main) coroutineScope = CoroutineScope(Dispatchers.Main)
@ -137,7 +137,6 @@ class ActiveTimerFragment : Fragment(), MessageClient.OnMessageReceivedListener
} }
(activity as? AppCompatActivity)?.supportActionBar?.title = state.timerName (activity as? AppCompatActivity)?.supportActionBar?.title = state.timerName
val backgroundColor = resources.getColor(state.phase.colorRes, context?.theme) val backgroundColor = resources.getColor(state.phase.colorRes, context?.theme)
Log.d("ActiveTimerFragment", "State: $state")
state.previousPhase?.let { state.previousPhase?.let {
val previousBackgroundColor = resources.getColor(it.colorRes, context?.theme) val previousBackgroundColor = resources.getColor(it.colorRes, context?.theme)
val colorAnimation = val colorAnimation =

View file

@ -56,6 +56,7 @@ dependencies {
implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version"
implementation 'androidx.wear:wear:1.0.0' implementation 'androidx.wear:wear:1.0.0'
implementation 'com.google.android.support:wearable:2.7.0' implementation 'com.google.android.support:wearable:2.7.0'
implementation 'androidx.appcompat:appcompat:1.2.0'
compileOnly 'com.google.android.wearable:wearable:2.7.0' compileOnly 'com.google.android.wearable:wearable:2.7.0'
testImplementation 'junit:junit:4.12' testImplementation 'junit:junit:4.12'
} }

View file

@ -1,12 +1,15 @@
package com.wbrawner.trainterval.wear package com.wbrawner.trainterval.wear
import android.content.Context import android.content.Context
import android.graphics.Color
import android.os.Bundle import android.os.Bundle
import android.os.VibrationEffect import android.os.VibrationEffect
import android.os.Vibrator import android.os.Vibrator
import android.support.wearable.activity.WearableActivity
import android.util.Log import android.util.Log
import android.view.View import android.view.View
import androidx.core.content.ContextCompat
import androidx.fragment.app.FragmentActivity
import androidx.wear.ambient.AmbientModeSupport
import com.google.android.gms.wearable.* import com.google.android.gms.wearable.*
import com.wbrawner.trainterval.R import com.wbrawner.trainterval.R
import com.wbrawner.trainterval.shared.IntervalTimerState import com.wbrawner.trainterval.shared.IntervalTimerState
@ -15,12 +18,16 @@ import com.wbrawner.trainterval.shared.IntervalTimerState.Companion.TIMER_STATE
import com.wbrawner.trainterval.shared.toIntervalTimerState import com.wbrawner.trainterval.shared.toIntervalTimerState
import kotlinx.android.synthetic.main.activity_main.* import kotlinx.android.synthetic.main.activity_main.*
class MainActivity : WearableActivity(), DataClient.OnDataChangedListener { class MainActivity : FragmentActivity(),
AmbientModeSupport.AmbientCallbackProvider,
DataClient.OnDataChangedListener {
private lateinit var dataClient: DataClient private lateinit var dataClient: DataClient
private lateinit var messageClient: MessageClient private lateinit var messageClient: MessageClient
private lateinit var nodeClient: NodeClient private lateinit var nodeClient: NodeClient
private lateinit var vibrator: Vibrator private lateinit var vibrator: Vibrator
private lateinit var ambientController: AmbientModeSupport.AmbientController
private var lastState: IntervalTimerState? = null
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
@ -29,7 +36,7 @@ class MainActivity : WearableActivity(), DataClient.OnDataChangedListener {
messageClient = Wearable.getMessageClient(this) messageClient = Wearable.getMessageClient(this)
nodeClient = Wearable.getNodeClient(this) nodeClient = Wearable.getNodeClient(this)
vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator vibrator = getSystemService(Context.VIBRATOR_SERVICE) as Vibrator
setAmbientEnabled() ambientController = AmbientModeSupport.attach(this)
} }
override fun onResume() { override fun onResume() {
@ -49,19 +56,38 @@ class MainActivity : WearableActivity(), DataClient.OnDataChangedListener {
.dataMap .dataMap
.toIntervalTimerState() .toIntervalTimerState()
?: return@forEach ?: return@forEach
lastState = intervalTimerState
renderState()
}
}
private fun renderState() {
val intervalTimerState = lastState ?: return
when (intervalTimerState) { when (intervalTimerState) {
is IntervalTimerState.LoadingState -> timeRemaining.text = "Loading" is IntervalTimerState.LoadingState -> timeRemaining.text = "Loading"
is IntervalTimerState.TimerRunningState -> { is IntervalTimerState.TimerRunningState -> {
val backgroundColor = val backgroundColor = if (ambientController.isAmbient) Color.BLACK
resources.getColor(intervalTimerState.phase.colorRes, theme) else resources.getColor(intervalTimerState.phase.colorRes, theme)
timerRoot.setBackgroundColor(backgroundColor) timerRoot.setBackgroundColor(backgroundColor)
timeRemaining.text = intervalTimerState.timeRemaining timeRemaining.text = intervalTimerState.timeRemaining
val textColor = if (ambientController.isAmbient) resources.getColor(
intervalTimerState.phase.colorRes,
theme
)
else Color.BLACK
timeRemaining.setTextColor(textColor)
if (ambientController.isAmbient) {
toggleButton.visibility = View.GONE
} else {
toggleButton.visibility = View.VISIBLE
toggleButton.setImageDrawable( toggleButton.setImageDrawable(
getDrawable( ContextCompat.getDrawable(
if (intervalTimerState.isRunning) R.drawable.ic_pause_inset this,
else R.drawable.ic_play_inset if (intervalTimerState.isRunning) R.drawable.ic_pause
else R.drawable.ic_play_arrow
) )
) )
}
if (intervalTimerState.vibrate) { if (intervalTimerState.vibrate) {
vibrator.vibrate( vibrator.vibrate(
VibrationEffect.createWaveform( VibrationEffect.createWaveform(
@ -74,7 +100,6 @@ class MainActivity : WearableActivity(), DataClient.OnDataChangedListener {
is IntervalTimerState.ExitState -> timeRemaining.text = "Exit" is IntervalTimerState.ExitState -> timeRemaining.text = "Exit"
} }
} }
}
fun toggleTimer(@Suppress("UNUSED_PARAMETER") view: View) { fun toggleTimer(@Suppress("UNUSED_PARAMETER") view: View) {
nodeClient.connectedNodes.addOnSuccessListener { nodes -> nodeClient.connectedNodes.addOnSuccessListener { nodes ->
@ -91,12 +116,16 @@ class MainActivity : WearableActivity(), DataClient.OnDataChangedListener {
} }
} }
override fun getAmbientCallback(): AmbientModeSupport.AmbientCallback =
object : AmbientModeSupport.AmbientCallback() {
override fun onEnterAmbient(ambientDetails: Bundle?) { override fun onEnterAmbient(ambientDetails: Bundle?) {
super.onEnterAmbient(ambientDetails) super.onEnterAmbient(ambientDetails)
renderState()
} }
override fun onExitAmbient() { override fun onExitAmbient() {
super.onExitAmbient() super.onExitAmbient()
renderState()
}
} }
} }

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<selector xmlns:android="http://schemas.android.com/apk/res/android">
<item android:drawable="@drawable/background_round_pressed" android:state_pressed="true" />
<item android:drawable="@drawable/background_round_pressed" android:state_focused="true" />
<item android:drawable="@drawable/background_round_normal" />
</selector>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:height="48dp"
android:width="48dp" />
<solid android:color="#40000000" />
</shape>

View file

@ -0,0 +1,8 @@
<?xml version="1.0" encoding="utf-8"?>
<shape xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="oval">
<size
android:height="48dp"
android:width="48dp" />
<solid android:color="#80000000" />
</shape>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_pause"
android:insetTop="5dp"
android:insetBottom="5dp" />

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<inset xmlns:android="http://schemas.android.com/apk/res/android"
android:drawable="@drawable/ic_play_arrow"
android:insetTop="5dp"
android:insetBottom="5dp" />

View file

@ -1,19 +1,19 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android" <LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
android:id="@+id/timerRoot" android:id="@+id/timerRoot"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:background="@color/colorSurface"> android:background="@color/colorSurface"
android:gravity="center"
android:orientation="vertical">
<TextView <TextView
android:id="@+id/timeRemaining" android:id="@+id/timeRemaining"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="100dp" android:layout_height="100dp"
android:layout_gravity="center"
android:autoSizeTextType="uniform" android:autoSizeTextType="uniform"
android:fontFamily="monospace" android:fontFamily="monospace"
android:gravity="center"
android:padding="16dp" android:padding="16dp"
android:textAlignment="center" android:textAlignment="center"
android:textColor="@color/colorOnSurface" android:textColor="@color/colorOnSurface"
@ -21,14 +21,13 @@
<ImageButton <ImageButton
android:id="@+id/toggleButton" android:id="@+id/toggleButton"
android:layout_width="match_parent" android:layout_width="wrap_content"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_gravity="bottom" android:background="@drawable/background_round"
android:backgroundTint="#40000000"
android:contentDescription="@string/toggle_timer" android:contentDescription="@string/toggle_timer"
android:fitsSystemWindows="true" android:fitsSystemWindows="true"
android:foregroundGravity="center"
android:onClick="toggleTimer" android:onClick="toggleTimer"
android:padding="8dp"
android:src="@drawable/ic_play_arrow" /> android:src="@drawable/ic_play_arrow" />
</FrameLayout> </LinearLayout>

View file

@ -1,5 +1,3 @@
<resources> <resources>
<!-- Base application theme. -->
<style name="AppTheme" parent="Theme.Wearable" /> <style name="AppTheme" parent="Theme.Wearable" />
</resources> </resources>