@ -11,53 +11,126 @@ import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.layout.size
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MaterialTheme
import androidx.compose.runtime.Composable
import androidx.compose.runtime.Composable
import androidx.compose.runtime.collectAsState
import androidx.compose.ui.Modifier
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.Color
import com.tailscale.ipn.ui.util.set
import kotlinx.coroutines.flow.MutableStateFlow
import kotlinx.coroutines.flow.StateFlow
import kotlin.concurrent.timer
// TODO(angott):
// DotsMatrix represents the state of the progress indicator.
// - Implement game-of-life animation for progress indicator.
typealias DotsMatrix = List < List < Boolean > >
// - Remove hardcoded dots, use a for-each and make it dynamically
// use the space available instead of unit = 10.dp
// The initial DotsMatrix that represents the Tailscale logo (T-shaped).
val logoDotsMatrix : DotsMatrix =
listOf (
listOf ( false , false , false ) ,
listOf ( true , true , true ) ,
listOf ( false , true , false ) ,
)
@Composable
@Composable
fun TailscaleLogoView ( modifier : Modifier ) {
fun TailscaleLogoView ( animated : Boolean = false , modifier : Modifier ) {
val primaryColor : Color = MaterialTheme . colorScheme . primary
val secondaryColor : Color = MaterialTheme . colorScheme . primary . copy ( alpha = 0.3f )
val primaryColor : Color = MaterialTheme . colorScheme . secondary
val secondaryColor : Color = MaterialTheme . colorScheme . secondary . copy ( alpha = 0.3f )
val currentDotsMatrix : StateFlow < DotsMatrix > = MutableStateFlow ( logoDotsMatrix )
var currentDotsMatrixIndex = 0
fun advanceToNextMatrix ( ) {
currentDotsMatrixIndex = ( currentDotsMatrixIndex + 1 ) % gameOfLife . size
val newMatrix =
if ( animated ) {
gameOfLife [ currentDotsMatrixIndex ]
} else {
logoDotsMatrix
}
currentDotsMatrix . set ( newMatrix )
}
if ( animated ) {
timer ( period = 300L ) { advanceToNextMatrix ( ) }
}
@Composable
fun EnabledDot ( modifier : Modifier ) {
Canvas ( modifier = modifier , onDraw = { drawCircle ( primaryColor ) } )
}
@Composable
fun DisabledDot ( modifier : Modifier ) {
Canvas ( modifier = modifier , onDraw = { drawCircle ( secondaryColor ) } )
}
BoxWithConstraints ( modifier ) {
BoxWithConstraints ( modifier ) {
val currentMatrix = currentDotsMatrix . collectAsState ( ) . value
Column ( verticalArrangement = Arrangement . spacedBy ( this @BoxWithConstraints . maxWidth . div ( 8 ) ) ) {
Column ( verticalArrangement = Arrangement . spacedBy ( this @BoxWithConstraints . maxWidth . div ( 8 ) ) ) {
Row ( horizontalArrangement = Arrangement . spacedBy ( this @BoxWithConstraints . maxWidth . div ( 8 ) ) ) {
for ( y in 0. . 2 ) {
Canvas (
Row ( horizontalArrangement = Arrangement . spacedBy ( this @BoxWithConstraints . maxWidth . div ( 8 ) ) ) {
modifier = Modifier . size ( this @BoxWithConstraints . maxWidth . div ( 4 ) ) ,
for ( x in 0. . 2 ) {
onDraw = { drawCircle ( color = secondaryColor ) } )
if ( currentMatrix [ y ] [ x ] ) {
Canvas (
EnabledDot ( Modifier . size ( this @BoxWithConstraints . maxWidth . div ( 4 ) ) )
modifier = Modifier . size ( this @BoxWithConstraints . maxWidth . div ( 4 ) ) ,
} else {
onDraw = { drawCircle ( color = secondaryColor ) } )
DisabledDot ( Modifier . size ( this @BoxWithConstraints . maxWidth . div ( 4 ) ) )
Canvas (
}
modifier = Modifier . size ( this @BoxWithConstraints . maxWidth . div ( 4 ) ) ,
}
onDraw = { drawCircle ( color = secondaryColor ) } )
}
}
Row ( horizontalArrangement = Arrangement . spacedBy ( this @BoxWithConstraints . maxWidth . div ( 8 ) ) ) {
Canvas (
modifier = Modifier . size ( this @BoxWithConstraints . maxWidth . div ( 4 ) ) ,
onDraw = { drawCircle ( color = primaryColor ) } )
Canvas (
modifier = Modifier . size ( this @BoxWithConstraints . maxWidth . div ( 4 ) ) ,
onDraw = { drawCircle ( color = primaryColor ) } )
Canvas (
modifier = Modifier . size ( this @BoxWithConstraints . maxWidth . div ( 4 ) ) ,
onDraw = { drawCircle ( color = primaryColor ) } )
}
Row ( horizontalArrangement = Arrangement . spacedBy ( this @BoxWithConstraints . maxWidth . div ( 8 ) ) ) {
Canvas (
modifier = Modifier . size ( this @BoxWithConstraints . maxWidth . div ( 4 ) ) ,
onDraw = { drawCircle ( color = secondaryColor ) } )
Canvas (
modifier = Modifier . size ( this @BoxWithConstraints . maxWidth . div ( 4 ) ) ,
onDraw = { drawCircle ( color = primaryColor ) } )
Canvas (
modifier = Modifier . size ( this @BoxWithConstraints . maxWidth . div ( 4 ) ) ,
onDraw = { drawCircle ( color = secondaryColor ) } )
}
}
}
}
}
}
}
}
val gameOfLife : List < DotsMatrix > =
listOf (
listOf (
listOf ( false , true , true ) ,
listOf ( true , false , true ) ,
listOf ( false , false , true ) ,
) ,
listOf (
listOf ( false , true , true ) ,
listOf ( false , false , true ) ,
listOf ( false , true , false ) ,
) ,
listOf (
listOf ( false , true , true ) ,
listOf ( false , false , false ) ,
listOf ( false , false , true ) ,
) ,
listOf (
listOf ( false , false , true ) ,
listOf ( false , true , false ) ,
listOf ( false , false , false ) ,
) ,
listOf (
listOf ( false , true , false ) ,
listOf ( false , false , false ) ,
listOf ( false , false , false ) ,
) ,
listOf (
listOf ( false , false , false ) ,
listOf ( false , false , true ) ,
listOf ( false , false , false ) ,
) ,
listOf (
listOf ( false , false , false ) ,
listOf ( false , false , false ) ,
listOf ( false , false , false ) ,
) ,
listOf (
listOf ( false , false , true ) ,
listOf ( false , false , false ) ,
listOf ( false , false , false ) ,
) ,
listOf (
listOf ( false , false , false ) ,
listOf ( false , false , false ) ,
listOf ( true , false , false ) ,
) ,
listOf ( listOf ( false , false , false ) , listOf ( false , false , false ) , listOf ( true , true , false ) ) ,
listOf ( listOf ( false , false , false ) , listOf ( true , false , false ) , listOf ( true , true , false ) ) ,
listOf ( listOf ( false , false , false ) , listOf ( true , true , false ) , listOf ( false , true , false ) ) ,
listOf ( listOf ( false , false , false ) , listOf ( true , true , false ) , listOf ( false , true , true ) ) ,
listOf ( listOf ( false , false , false ) , listOf ( true , true , true ) , listOf ( false , false , true ) ) ,
listOf ( listOf ( false , true , false ) , listOf ( true , true , true ) , listOf ( true , false , true ) ) )