@ -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
fun TailscaleLogoView ( animated : Boolean = false , modifier : Modifier ) {
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
@Composable
fun TailscaleLogoView ( modifier : Modifier ) {
fun EnabledDot ( modifier : Modifier ) {
val primaryColor : Color = MaterialTheme . colorScheme . primary
Canvas ( modifier = modifier , onDraw = { drawCircle ( primaryColor ) } )
val secondaryColor : Color = MaterialTheme . colorScheme . primary . copy ( alpha = 0.3f )
}
@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 ) ) ) {
for ( y in 0. . 2 ) {
Row ( horizontalArrangement = Arrangement . spacedBy ( this @BoxWithConstraints . maxWidth . div ( 8 ) ) ) {
Row ( horizontalArrangement = Arrangement . spacedBy ( this @BoxWithConstraints . maxWidth . div ( 8 ) ) ) {
Canvas (
for ( x in 0. . 2 ) {
modifier = Modifier . size ( this @BoxWithConstraints . maxWidth . div ( 4 ) ) ,
if ( currentMatrix [ y ] [ x ] ) {
onDraw = { drawCircle ( color = secondaryColor ) } )
EnabledDot ( Modifier . size ( this @BoxWithConstraints . maxWidth . div ( 4 ) ) )
Canvas (
} else {
modifier = Modifier . size ( this @BoxWithConstraints . maxWidth . div ( 4 ) ) ,
DisabledDot ( Modifier . size ( this @BoxWithConstraints . maxWidth . div ( 4 ) ) )
onDraw = { drawCircle ( color = secondaryColor ) } )
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 ) ) )