Feat(All): Tis

create first version
This commit is contained in:
Amir Mousavi
2026-04-29 16:31:04 +03:30
commit c9e656f705
145 changed files with 3136 additions and 0 deletions
@@ -0,0 +1,24 @@
package com.example.design_system
import androidx.test.platform.app.InstrumentationRegistry
import androidx.test.ext.junit.runners.AndroidJUnit4
import org.junit.Test
import org.junit.runner.RunWith
import org.junit.Assert.*
/**
* Instrumented test, which will execute on an Android device.
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
@RunWith(AndroidJUnit4::class)
class ExampleInstrumentedTest {
@Test
fun useAppContext() {
// Context of the app under test.
val appContext = InstrumentationRegistry.getInstrumentation().targetContext
assertEquals("com.example.design_system.test", appContext.packageName)
}
}
@@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
</manifest>
@@ -0,0 +1,120 @@
package com.example.design_system
import android.app.Activity
import android.view.ViewGroup
import android.webkit.JavascriptInterface
import android.webkit.WebSettings
import android.webkit.WebView
import android.webkit.WebViewClient
import androidx.activity.compose.BackHandler
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableLongStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.setValue
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.viewinterop.AndroidView
import com.example.design_system.util.PspJavaScriptInterface
import com.example.design_system.util.snackbar.SnackbarController
import com.example.design_system.util.snackbar.SnackbarEvent
import kotlinx.coroutines.launch
@OptIn(ExperimentalMaterial3Api::class)
@Composable
fun PspWebView(
url: String,
webView: WebView?,
modifier: Modifier = Modifier,
onWebView: (WebView) -> Unit,
onPayClick: (amount: Long, id: String?) -> Unit,
onPrintClick: () -> Unit,
) {
val canGoBack = remember { mutableStateOf(false) }
val scope = rememberCoroutineScope()
val context = LocalContext.current
var lastBackTime by remember { mutableLongStateOf(0L) }
var isPaymentLaunching by remember { mutableStateOf(false) }
BackHandler {
if (canGoBack.value) {
webView?.goBack()
} else {
val now = System.currentTimeMillis()
if (now - lastBackTime < 2000) {
(context as? Activity)?.finish()
} else {
lastBackTime = now
scope.launch {
SnackbarController.sendEvent(
SnackbarEvent(
message = "Press back again to exit"
)
)
}
}
}
}
AndroidView(
modifier = modifier,
factory = { ctx ->
WebView(ctx).apply {
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
ViewGroup.LayoutParams.MATCH_PARENT
)
webViewClient = object : WebViewClient() {
override fun onPageFinished(view: WebView?, url: String?) {
super.onPageFinished(view, url)
canGoBack.value = view?.canGoBack() ?: false
}
}
settings.apply {
javaScriptEnabled = true
domStorageEnabled = true
loadWithOverviewMode = true
useWideViewPort = true
cacheMode = WebSettings.LOAD_DEFAULT
mixedContentMode = WebSettings.MIXED_CONTENT_ALWAYS_ALLOW
userAgentString =
"Mozilla/5.0 (Linux; Android 13) AppleWebKit/537.36 Chrome/120.0 Mobile Safari/537.36"
}
addJavascriptInterface(
object : PspJavaScriptInterface {
@JavascriptInterface
override fun pay(amount: Long, id: String?) {
onPayClick(amount, id)
}
@JavascriptInterface
override fun print(text: String) {
TODO("Not yet implemented")
}
}, "AndroidPSP"
)
loadUrl(url)
onWebView(this)
}
},
update = { view ->
onWebView(view)
}
)
}
@@ -0,0 +1,13 @@
package com.example.design_system.theme.tis
import androidx.compose.ui.graphics.Color
val Purple80 = Color(0xFFD0BCFF)
val PurpleGrey80 = Color(0xFFCCC2DC)
val Pink80 = Color(0xFFEFB8C8)
val Purple40 = Color(0xFF6650a4)
val PurpleGrey40 = Color(0xFF625b71)
val Pink40 = Color(0xFF7D5260)
val TisPrimaryColor = Color(0xFF08723f)
@@ -0,0 +1,23 @@
package com.example.design_system.theme.tis
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.lightColorScheme
import androidx.compose.runtime.Composable
private val LightColorScheme = lightColorScheme(
primary = TisPrimaryColor,
)
@Composable
fun TisTheme(
content: @Composable () -> Unit
) {
MaterialTheme(
colorScheme = LightColorScheme,
typography = Typography,
content = content
)
}
@@ -0,0 +1,34 @@
package com.example.design_system.theme.tis
import androidx.compose.material3.Typography
import androidx.compose.ui.text.TextStyle
import androidx.compose.ui.text.font.FontFamily
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.unit.sp
// Set of Material typography styles to start with
val Typography = Typography(
bodyLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 16.sp,
lineHeight = 24.sp,
letterSpacing = 0.5.sp
)
/* Other default text styles to override
titleLarge = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Normal,
fontSize = 22.sp,
lineHeight = 28.sp,
letterSpacing = 0.sp
),
labelSmall = TextStyle(
fontFamily = FontFamily.Default,
fontWeight = FontWeight.Medium,
fontSize = 11.sp,
lineHeight = 16.sp,
letterSpacing = 0.5.sp
)
*/
)
@@ -0,0 +1,27 @@
package com.example.design_system.util
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.compose.LocalLifecycleOwner
import androidx.lifecycle.repeatOnLifecycle
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.withContext
@Composable
fun <T> ObserveAsEvents(
flow: Flow<T>,
key1: Any? = null,
key2: Any? = null,
onEvent: (T) -> Unit
) {
val lifecycleOwner = LocalLifecycleOwner.current
LaunchedEffect(lifecycleOwner.lifecycle, key1, key2, flow) {
lifecycleOwner.repeatOnLifecycle(Lifecycle.State.STARTED) {
withContext(Dispatchers.Main.immediate) {
flow.collect(onEvent)
}
}
}
}
@@ -0,0 +1,8 @@
package com.example.design_system.util
interface PspJavaScriptInterface {
fun pay(amount: Long,id: String?)
fun print(text: String)
}
@@ -0,0 +1,6 @@
package com.example.design_system.util.snackbar
data class SnackbarAction(
val name: String,
val action: suspend () -> Unit
)
@@ -0,0 +1,13 @@
package com.example.design_system.util.snackbar
import kotlinx.coroutines.channels.Channel
import kotlinx.coroutines.flow.receiveAsFlow
object SnackbarController {
private val _events = Channel<SnackbarEvent>()
val events = _events.receiveAsFlow()
suspend fun sendEvent(event: SnackbarEvent) {
_events.send(event)
}
}
@@ -0,0 +1,9 @@
package com.example.design_system.util.snackbar
import androidx.compose.material3.SnackbarDuration
data class SnackbarEvent(
val message: String,
val action: SnackbarAction? = null,
val duration: SnackbarDuration = if (action == null) SnackbarDuration.Short else SnackbarDuration.Long
)
@@ -0,0 +1,17 @@
package com.example.design_system
import org.junit.Test
import org.junit.Assert.*
/**
* Example local unit test, which will execute on the development machine (host).
*
* See [testing documentation](http://d.android.com/tools/testing).
*/
class ExampleUnitTest {
@Test
fun addition_isCorrect() {
assertEquals(4, 2 + 2)
}
}