Add login screen.
This commit is contained in:
		
							parent
							
								
									e9f5c30e01
								
							
						
					
					
						commit
						080d2e2b49
					
				
					 8 changed files with 257 additions and 17 deletions
				
			
		
							
								
								
									
										7
									
								
								.idea/misc.xml
									
										
									
										generated
									
									
									
								
							
							
						
						
									
										7
									
								
								.idea/misc.xml
									
										
									
										generated
									
									
									
								
							| 
						 | 
				
			
			@ -70,8 +70,15 @@
 | 
			
		|||
        <entry key="../../../../../layout/compose-model-1620317783141.xml" value="0.25462962962962965" />
 | 
			
		||||
        <entry key="../../../../../layout/compose-model-1620317894833.xml" value="0.25462962962962965" />
 | 
			
		||||
        <entry key="../../../../../layout/compose-model-1620374189600.xml" value="0.23514851485148514" />
 | 
			
		||||
        <entry key="../../../../../layout/compose-model-1620388426698.xml" value="0.26296296296296295" />
 | 
			
		||||
        <entry key="../../../../../layout/compose-model-1620389580469.xml" value="0.23514851485148514" />
 | 
			
		||||
        <entry key="../../../../../layout/compose-model-1620389588794.xml" value="0.23514851485148514" />
 | 
			
		||||
        <entry key="../../../../../layout/compose-model-1620391384718.xml" value="0.26296296296296295" />
 | 
			
		||||
        <entry key="../../../../../layout/compose-model-1620397702847.xml" value="0.28125" />
 | 
			
		||||
        <entry key="../../../../../layout/compose-model-1620399131186.xml" value="0.1" />
 | 
			
		||||
        <entry key="app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.2898148148148148" />
 | 
			
		||||
        <entry key="app/src/main/res/drawable/ic_baseline_local_library_24.xml" value="0.25462962962962965" />
 | 
			
		||||
        <entry key="app/src/main/res/drawable/ic_google.xml" value="0.2962962962962963" />
 | 
			
		||||
      </map>
 | 
			
		||||
    </option>
 | 
			
		||||
  </component>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -6,20 +6,21 @@ import androidx.activity.compose.setContent
 | 
			
		|||
import androidx.activity.viewModels
 | 
			
		||||
import androidx.compose.animation.Crossfade
 | 
			
		||||
import androidx.compose.animation.core.tween
 | 
			
		||||
import androidx.compose.material.MaterialTheme
 | 
			
		||||
import androidx.compose.material.Surface
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.getValue
 | 
			
		||||
import androidx.compose.runtime.livedata.observeAsState
 | 
			
		||||
import com.pixelized.biblib.ui.composable.screen.LoginScreenComposable
 | 
			
		||||
import com.pixelized.biblib.ui.composable.screen.MainScreenComposable
 | 
			
		||||
import com.pixelized.biblib.ui.composable.screen.SplashScreenComposable
 | 
			
		||||
import com.pixelized.biblib.ui.theme.BibLibTheme
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.AuthenticationViewModel
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel.Screen
 | 
			
		||||
import com.pixelized.biblib.ui.composable.screen.SplashScreenComposable
 | 
			
		||||
import com.pixelized.biblib.ui.composable.screen.MainScreenComposable
 | 
			
		||||
import com.pixelized.biblib.utils.BitmapCache
 | 
			
		||||
 | 
			
		||||
class MainActivity : ComponentActivity() {
 | 
			
		||||
    private val navigationViewModel: NavigationViewModel by viewModels()
 | 
			
		||||
    private val authenticationViewModel: AuthenticationViewModel by viewModels()
 | 
			
		||||
 | 
			
		||||
    override fun onCreate(savedInstanceState: Bundle?) {
 | 
			
		||||
        super.onCreate(savedInstanceState)
 | 
			
		||||
| 
						 | 
				
			
			@ -28,7 +29,7 @@ class MainActivity : ComponentActivity() {
 | 
			
		|||
 | 
			
		||||
        setContent {
 | 
			
		||||
            BibLibTheme {
 | 
			
		||||
                ContentComposable(navigationViewModel)
 | 
			
		||||
                ContentComposable(navigationViewModel, authenticationViewModel)
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
| 
						 | 
				
			
			@ -41,13 +42,20 @@ class MainActivity : ComponentActivity() {
 | 
			
		|||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun ContentComposable(navigationViewModel: NavigationViewModel) {
 | 
			
		||||
fun ContentComposable(
 | 
			
		||||
    navigationViewModel: NavigationViewModel,
 | 
			
		||||
    authenticationViewModel: AuthenticationViewModel
 | 
			
		||||
) {
 | 
			
		||||
    val main by navigationViewModel.screen.observeAsState()
 | 
			
		||||
 | 
			
		||||
    Crossfade(targetState = main, animationSpec = tween(1000)) {
 | 
			
		||||
        when (it) {
 | 
			
		||||
            is Screen.SplashScreen -> SplashScreenComposable(navigationViewModel)
 | 
			
		||||
            is Screen.MainScreen -> MainScreenComposable(navigationViewModel)
 | 
			
		||||
            is Screen.LoginScreen -> LoginScreenComposable(
 | 
			
		||||
                navigationViewModel = navigationViewModel,
 | 
			
		||||
                authenticationViewModel = authenticationViewModel
 | 
			
		||||
            )
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
| 
						 | 
				
			
			@ -0,0 +1,176 @@
 | 
			
		|||
package com.pixelized.biblib.ui.composable.screen
 | 
			
		||||
 | 
			
		||||
import androidx.compose.foundation.Image
 | 
			
		||||
import androidx.compose.foundation.clickable
 | 
			
		||||
import androidx.compose.foundation.layout.*
 | 
			
		||||
import androidx.compose.foundation.rememberScrollState
 | 
			
		||||
import androidx.compose.foundation.verticalScroll
 | 
			
		||||
import androidx.compose.material.*
 | 
			
		||||
import androidx.compose.material.ButtonDefaults.textButtonColors
 | 
			
		||||
import androidx.compose.material.icons.Icons
 | 
			
		||||
import androidx.compose.material.icons.sharp.Visibility
 | 
			
		||||
import androidx.compose.material.icons.sharp.VisibilityOff
 | 
			
		||||
import androidx.compose.runtime.*
 | 
			
		||||
import androidx.compose.runtime.livedata.observeAsState
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.res.painterResource
 | 
			
		||||
import androidx.compose.ui.res.stringResource
 | 
			
		||||
import androidx.compose.ui.text.input.PasswordVisualTransformation
 | 
			
		||||
import androidx.compose.ui.text.input.VisualTransformation
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
import androidx.compose.ui.unit.dp
 | 
			
		||||
import com.pixelized.biblib.R
 | 
			
		||||
import com.pixelized.biblib.ui.theme.BibLibTheme
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.AuthenticationViewModel
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel
 | 
			
		||||
 | 
			
		||||
@Preview
 | 
			
		||||
@Composable
 | 
			
		||||
fun LoginScreenComposablePreview() {
 | 
			
		||||
    BibLibTheme {
 | 
			
		||||
        val navigationViewModel = NavigationViewModel()
 | 
			
		||||
        val authenticationViewModel = AuthenticationViewModel()
 | 
			
		||||
        LoginScreenComposable(navigationViewModel, authenticationViewModel)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
fun LoginScreenComposable(
 | 
			
		||||
    navigationViewModel: NavigationViewModel,
 | 
			
		||||
    authenticationViewModel: AuthenticationViewModel
 | 
			
		||||
) {
 | 
			
		||||
    val typography = MaterialTheme.typography
 | 
			
		||||
 | 
			
		||||
    Column(
 | 
			
		||||
        modifier = Modifier
 | 
			
		||||
            .fillMaxWidth()
 | 
			
		||||
            .fillMaxHeight()
 | 
			
		||||
            .verticalScroll(rememberScrollState())
 | 
			
		||||
            .padding(16.dp)
 | 
			
		||||
    ) {
 | 
			
		||||
        Spacer(modifier = Modifier.weight(1f))
 | 
			
		||||
 | 
			
		||||
        Text(
 | 
			
		||||
            modifier = Modifier
 | 
			
		||||
                .padding(vertical = 16.dp)
 | 
			
		||||
                .align(alignment = Alignment.CenterHorizontally),
 | 
			
		||||
            style = typography.h4,
 | 
			
		||||
            text = stringResource(id = R.string.welcome_sign_in)
 | 
			
		||||
        )
 | 
			
		||||
 | 
			
		||||
        Spacer(modifier = Modifier.weight(1f))
 | 
			
		||||
 | 
			
		||||
        LoginField(
 | 
			
		||||
            viewModel = authenticationViewModel,
 | 
			
		||||
            modifier = Modifier
 | 
			
		||||
                .fillMaxWidth()
 | 
			
		||||
                .padding(bottom = 16.dp)
 | 
			
		||||
        )
 | 
			
		||||
        PasswordField(
 | 
			
		||||
            viewModel = authenticationViewModel,
 | 
			
		||||
            modifier = Modifier
 | 
			
		||||
                .fillMaxWidth()
 | 
			
		||||
                .padding(bottom = 16.dp)
 | 
			
		||||
        )
 | 
			
		||||
        CredentialRemember(
 | 
			
		||||
            viewModel = authenticationViewModel,
 | 
			
		||||
            modifier = Modifier
 | 
			
		||||
                .height(48.dp)
 | 
			
		||||
                .padding(bottom = 16.dp)
 | 
			
		||||
        )
 | 
			
		||||
        Row(
 | 
			
		||||
            modifier = Modifier
 | 
			
		||||
                .padding(bottom = 16.dp)
 | 
			
		||||
                .align(Alignment.End)
 | 
			
		||||
        ) {
 | 
			
		||||
            Button(
 | 
			
		||||
                modifier = Modifier.padding(end = 8.dp),
 | 
			
		||||
                colors = textButtonColors(),
 | 
			
		||||
                onClick = {
 | 
			
		||||
                    // TODO:
 | 
			
		||||
                    navigationViewModel.navigateTo(NavigationViewModel.Screen.MainScreen)
 | 
			
		||||
                }) {
 | 
			
		||||
                Text(text = stringResource(id = R.string.action_register))
 | 
			
		||||
            }
 | 
			
		||||
            Button(onClick = {
 | 
			
		||||
                // TODO:
 | 
			
		||||
                navigationViewModel.navigateTo(NavigationViewModel.Screen.MainScreen)
 | 
			
		||||
            }) {
 | 
			
		||||
                Text(text = stringResource(id = R.string.action_login))
 | 
			
		||||
            }
 | 
			
		||||
        }
 | 
			
		||||
 | 
			
		||||
        Spacer(modifier = Modifier.weight(2f))
 | 
			
		||||
 | 
			
		||||
        Button(
 | 
			
		||||
            modifier = Modifier.fillMaxWidth(),
 | 
			
		||||
            colors = ButtonDefaults.outlinedButtonColors(),
 | 
			
		||||
            onClick = {
 | 
			
		||||
                // TODO:
 | 
			
		||||
                navigationViewModel.navigateTo(NavigationViewModel.Screen.MainScreen)
 | 
			
		||||
            }) {
 | 
			
		||||
            Image(
 | 
			
		||||
                modifier = Modifier.padding(end = 8.dp),
 | 
			
		||||
                painter = painterResource(id = R.drawable.ic_google), contentDescription = ""
 | 
			
		||||
            )
 | 
			
		||||
            Text(text = stringResource(id = R.string.action_google_sign_in))
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
private fun LoginField(viewModel: AuthenticationViewModel, modifier: Modifier = Modifier) {
 | 
			
		||||
    val login: State<String?> = viewModel.login.observeAsState()
 | 
			
		||||
    TextField(
 | 
			
		||||
        modifier = modifier,
 | 
			
		||||
        value = login.value ?: "",
 | 
			
		||||
        label = { Text(text = stringResource(id = R.string.authentication_login)) },
 | 
			
		||||
        maxLines = 1,
 | 
			
		||||
        singleLine = true,
 | 
			
		||||
        onValueChange = { viewModel.updateLogin(it) },
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
private fun PasswordField(viewModel: AuthenticationViewModel, modifier: Modifier = Modifier) {
 | 
			
		||||
    val password = viewModel.password.observeAsState()
 | 
			
		||||
    var passwordVisibility by remember { mutableStateOf(false) }
 | 
			
		||||
    TextField(
 | 
			
		||||
        modifier = modifier,
 | 
			
		||||
        value = password.value ?: "",
 | 
			
		||||
        maxLines = 1,
 | 
			
		||||
        singleLine = true,
 | 
			
		||||
        visualTransformation = if (passwordVisibility) VisualTransformation.None else PasswordVisualTransformation(),
 | 
			
		||||
        trailingIcon = {
 | 
			
		||||
            IconButton(onClick = { passwordVisibility = passwordVisibility.not() }) {
 | 
			
		||||
                Icon(
 | 
			
		||||
                    imageVector = if (passwordVisibility) Icons.Sharp.VisibilityOff else Icons.Sharp.Visibility,
 | 
			
		||||
                    contentDescription = "password visibility"
 | 
			
		||||
                )
 | 
			
		||||
            }
 | 
			
		||||
        },
 | 
			
		||||
        label = { Text(text = stringResource(id = R.string.authentication_password)) },
 | 
			
		||||
        onValueChange = { viewModel.updatePassword(it) }
 | 
			
		||||
    )
 | 
			
		||||
}
 | 
			
		||||
 | 
			
		||||
@Composable
 | 
			
		||||
private fun CredentialRemember(viewModel: AuthenticationViewModel, modifier: Modifier = Modifier) {
 | 
			
		||||
    val credential = viewModel.rememberCredential.observeAsState()
 | 
			
		||||
    Row(modifier = modifier.clickable {
 | 
			
		||||
        viewModel.updateRememberCredential(credential = credential.value?.not() ?: false)
 | 
			
		||||
    }) {
 | 
			
		||||
        Checkbox(
 | 
			
		||||
            modifier = Modifier.align(Alignment.CenterVertically),
 | 
			
		||||
            checked = credential.value ?: false,
 | 
			
		||||
            onCheckedChange = null
 | 
			
		||||
        )
 | 
			
		||||
        Text(
 | 
			
		||||
            modifier = Modifier
 | 
			
		||||
                .align(Alignment.CenterVertically)
 | 
			
		||||
                .padding(start = 8.dp),
 | 
			
		||||
            text = stringResource(id = R.string.authentication_credential_remember)
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -6,11 +6,14 @@ import androidx.compose.foundation.layout.fillMaxWidth
 | 
			
		|||
import androidx.compose.material.MaterialTheme
 | 
			
		||||
import androidx.compose.material.Text
 | 
			
		||||
import androidx.compose.runtime.Composable
 | 
			
		||||
import androidx.compose.runtime.LaunchedEffect
 | 
			
		||||
import androidx.compose.runtime.rememberCoroutineScope
 | 
			
		||||
import androidx.compose.ui.Alignment
 | 
			
		||||
import androidx.compose.ui.Modifier
 | 
			
		||||
import androidx.compose.ui.tooling.preview.Preview
 | 
			
		||||
import com.pixelized.biblib.ui.theme.BibLibTheme
 | 
			
		||||
import com.pixelized.biblib.ui.viewmodel.NavigationViewModel
 | 
			
		||||
import kotlinx.coroutines.*
 | 
			
		||||
 | 
			
		||||
 | 
			
		||||
@Preview
 | 
			
		||||
| 
						 | 
				
			
			@ -36,4 +39,12 @@ fun SplashScreenComposable(navigationViewModel: NavigationViewModel) {
 | 
			
		|||
            text = "Welcome to BibLib"
 | 
			
		||||
        )
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    val coroutineScope = rememberCoroutineScope()
 | 
			
		||||
    LaunchedEffect(key1 = "loading", block = {
 | 
			
		||||
        coroutineScope.launch {
 | 
			
		||||
            delay(1000)
 | 
			
		||||
            navigationViewModel.navigateTo(NavigationViewModel.Screen.LoginScreen)
 | 
			
		||||
        }
 | 
			
		||||
    })
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -0,0 +1,29 @@
 | 
			
		|||
package com.pixelized.biblib.ui.viewmodel
 | 
			
		||||
 | 
			
		||||
import androidx.lifecycle.LiveData
 | 
			
		||||
import androidx.lifecycle.MutableLiveData
 | 
			
		||||
import androidx.lifecycle.ViewModel
 | 
			
		||||
 | 
			
		||||
class AuthenticationViewModel: ViewModel() {
 | 
			
		||||
 | 
			
		||||
    private val _login = MutableLiveData<String>()
 | 
			
		||||
    val login: LiveData<String> get() = _login
 | 
			
		||||
 | 
			
		||||
    private val _password = MutableLiveData<String>()
 | 
			
		||||
    val password: LiveData<String> get() = _password
 | 
			
		||||
 | 
			
		||||
    private val _rememberCredential = MutableLiveData<Boolean>(false)
 | 
			
		||||
    val rememberCredential: LiveData<Boolean> get() = _rememberCredential
 | 
			
		||||
 | 
			
		||||
    fun updateLogin(login: String) {
 | 
			
		||||
        _login.postValue(login)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun updatePassword(password: String) {
 | 
			
		||||
        _password.postValue(password)
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun updateRememberCredential(credential: Boolean) {
 | 
			
		||||
        _rememberCredential.postValue(credential)
 | 
			
		||||
    }
 | 
			
		||||
}
 | 
			
		||||
| 
						 | 
				
			
			@ -13,21 +13,12 @@ class NavigationViewModel : ViewModel() {
 | 
			
		|||
 | 
			
		||||
    private val stack = Stack<Page>()
 | 
			
		||||
 | 
			
		||||
    private val _screen = MutableLiveData<Screen>()
 | 
			
		||||
    private val _screen = MutableLiveData<Screen>(Screen.SplashScreen)
 | 
			
		||||
    val screen: LiveData<Screen> get() = _screen
 | 
			
		||||
 | 
			
		||||
    private val _page = MutableLiveData<Page>()
 | 
			
		||||
    private val _page = MutableLiveData<Page>(Page.HomePage)
 | 
			
		||||
    val page: LiveData<Page> get() = _page
 | 
			
		||||
 | 
			
		||||
    init {
 | 
			
		||||
        _screen.value = Screen.SplashScreen
 | 
			
		||||
        viewModelScope.launch {
 | 
			
		||||
            delay(3000)
 | 
			
		||||
            navigateTo(Page.HomePage)
 | 
			
		||||
            navigateTo(Screen.MainScreen)
 | 
			
		||||
        }
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    fun navigateTo(screen: Screen): Boolean {
 | 
			
		||||
        _screen.postValue(screen)
 | 
			
		||||
        return true
 | 
			
		||||
| 
						 | 
				
			
			@ -52,6 +43,7 @@ class NavigationViewModel : ViewModel() {
 | 
			
		|||
    sealed class Screen {
 | 
			
		||||
        object SplashScreen : Screen()
 | 
			
		||||
        object MainScreen : Screen()
 | 
			
		||||
        object LoginScreen : Screen()
 | 
			
		||||
    }
 | 
			
		||||
 | 
			
		||||
    sealed class Page {
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
							
								
								
									
										7
									
								
								app/src/main/res/drawable/ic_google.xml
									
										
									
									
									
										Normal file
									
								
							
							
						
						
									
										7
									
								
								app/src/main/res/drawable/ic_google.xml
									
										
									
									
									
										Normal file
									
								
							| 
						 | 
				
			
			@ -0,0 +1,7 @@
 | 
			
		|||
<vector android:height="24dp" android:viewportHeight="512"
 | 
			
		||||
    android:viewportWidth="512" android:width="24dp" xmlns:android="http://schemas.android.com/apk/res/android">
 | 
			
		||||
    <path android:fillColor="#FBBB00" android:pathData="M113.47,309.408L95.648,375.94l-65.139,1.378C11.042,341.211 0,299.9 0,256c0,-42.451 10.324,-82.483 28.624,-117.732h0.014l57.992,10.632l25.404,57.644c-5.317,15.501 -8.215,32.141 -8.215,49.456C103.821,274.792 107.225,292.797 113.47,309.408z"/>
 | 
			
		||||
    <path android:fillColor="#518EF8" android:pathData="M507.527,208.176C510.467,223.662 512,239.655 512,256c0,18.328 -1.927,36.206 -5.598,53.451c-12.462,58.683 -45.025,109.925 -90.134,146.187l-0.014,-0.014l-73.044,-3.727l-10.338,-64.535c29.932,-17.554 53.324,-45.025 65.646,-77.911h-136.89V208.176h138.887L507.527,208.176L507.527,208.176z"/>
 | 
			
		||||
    <path android:fillColor="#28B446" android:pathData="M416.253,455.624l0.014,0.014C372.396,490.901 316.666,512 256,512c-97.491,0 -182.252,-54.491 -225.491,-134.681l82.961,-67.91c21.619,57.698 77.278,98.771 142.53,98.771c28.047,0 54.323,-7.582 76.87,-20.818L416.253,455.624z"/>
 | 
			
		||||
    <path android:fillColor="#F14336" android:pathData="M419.404,58.936l-82.933,67.896c-23.335,-14.586 -50.919,-23.012 -80.471,-23.012c-66.729,0 -123.429,42.957 -143.965,102.724l-83.397,-68.276h-0.014C71.23,56.123 157.06,0 256,0C318.115,0 375.068,22.126 419.404,58.936z"/>
 | 
			
		||||
</vector>
 | 
			
		||||
| 
						 | 
				
			
			@ -1,6 +1,16 @@
 | 
			
		|||
<resources>
 | 
			
		||||
    <string name="app_name">BibLib</string>
 | 
			
		||||
 | 
			
		||||
    <string name="action_register">Register</string>
 | 
			
		||||
    <string name="action_login">Login</string>
 | 
			
		||||
    <string name="action_google_sign_in">Sign in with Google</string>
 | 
			
		||||
 | 
			
		||||
    <string name="welcome_sign_in">Sign in to BibLib</string>
 | 
			
		||||
 | 
			
		||||
    <string name="authentication_login">Login</string>
 | 
			
		||||
    <string name="authentication_password">Password</string>
 | 
			
		||||
    <string name="authentication_credential_remember">Remember my credential</string>
 | 
			
		||||
 | 
			
		||||
    <string name="detail_rating">Rating</string>
 | 
			
		||||
    <string name="detail_language">Language</string>
 | 
			
		||||
    <string name="detail_Release">Release</string>
 | 
			
		||||
| 
						 | 
				
			
			
 | 
			
		|||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue