Add support for ambient mode on wearable
This commit is contained in:
parent
26c6977fd6
commit
fbf60ac13e
11 changed files with 214 additions and 53 deletions
123
.idea/codeStyles/Project.xml
Normal file
123
.idea/codeStyles/Project.xml
Normal 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>
|
|
@ -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 =
|
||||||
|
|
|
@ -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'
|
||||||
}
|
}
|
|
@ -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,30 +56,48 @@ class MainActivity : WearableActivity(), DataClient.OnDataChangedListener {
|
||||||
.dataMap
|
.dataMap
|
||||||
.toIntervalTimerState()
|
.toIntervalTimerState()
|
||||||
?: return@forEach
|
?: return@forEach
|
||||||
when (intervalTimerState) {
|
lastState = intervalTimerState
|
||||||
is IntervalTimerState.LoadingState -> timeRemaining.text = "Loading"
|
renderState()
|
||||||
is IntervalTimerState.TimerRunningState -> {
|
}
|
||||||
val backgroundColor =
|
}
|
||||||
resources.getColor(intervalTimerState.phase.colorRes, theme)
|
|
||||||
timerRoot.setBackgroundColor(backgroundColor)
|
private fun renderState() {
|
||||||
timeRemaining.text = intervalTimerState.timeRemaining
|
val intervalTimerState = lastState ?: return
|
||||||
|
when (intervalTimerState) {
|
||||||
|
is IntervalTimerState.LoadingState -> timeRemaining.text = "Loading"
|
||||||
|
is IntervalTimerState.TimerRunningState -> {
|
||||||
|
val backgroundColor = if (ambientController.isAmbient) Color.BLACK
|
||||||
|
else resources.getColor(intervalTimerState.phase.colorRes, theme)
|
||||||
|
timerRoot.setBackgroundColor(backgroundColor)
|
||||||
|
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) {
|
||||||
|
vibrator.vibrate(
|
||||||
|
VibrationEffect.createWaveform(
|
||||||
|
longArrayOf(0L, 100L, 50L, 100L),
|
||||||
|
-1
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
if (intervalTimerState.vibrate) {
|
|
||||||
vibrator.vibrate(
|
|
||||||
VibrationEffect.createWaveform(
|
|
||||||
longArrayOf(0L, 100L, 50L, 100L),
|
|
||||||
-1
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
is IntervalTimerState.ExitState -> timeRemaining.text = "Exit"
|
|
||||||
}
|
}
|
||||||
|
is IntervalTimerState.ExitState -> timeRemaining.text = "Exit"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -91,12 +116,16 @@ class MainActivity : WearableActivity(), DataClient.OnDataChangedListener {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onEnterAmbient(ambientDetails: Bundle?) {
|
override fun getAmbientCallback(): AmbientModeSupport.AmbientCallback =
|
||||||
super.onEnterAmbient(ambientDetails)
|
object : AmbientModeSupport.AmbientCallback() {
|
||||||
}
|
override fun onEnterAmbient(ambientDetails: Bundle?) {
|
||||||
|
super.onEnterAmbient(ambientDetails)
|
||||||
|
renderState()
|
||||||
|
}
|
||||||
|
|
||||||
override fun onExitAmbient() {
|
override fun onExitAmbient() {
|
||||||
super.onExitAmbient()
|
super.onExitAmbient()
|
||||||
|
renderState()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
}
|
}
|
6
wear/src/main/res/drawable/background_round.xml
Normal file
6
wear/src/main/res/drawable/background_round.xml
Normal 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>
|
8
wear/src/main/res/drawable/background_round_normal.xml
Normal file
8
wear/src/main/res/drawable/background_round_normal.xml
Normal 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>
|
8
wear/src/main/res/drawable/background_round_pressed.xml
Normal file
8
wear/src/main/res/drawable/background_round_pressed.xml
Normal 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>
|
|
@ -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" />
|
|
|
@ -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" />
|
|
|
@ -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>
|
|
@ -1,5 +1,3 @@
|
||||||
<resources>
|
<resources>
|
||||||
<!-- Base application theme. -->
|
|
||||||
<style name="AppTheme" parent="Theme.Wearable" />
|
<style name="AppTheme" parent="Theme.Wearable" />
|
||||||
|
|
||||||
</resources>
|
</resources>
|
Loading…
Reference in a new issue