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
+1
View File
@@ -0,0 +1 @@
/build
+47
View File
@@ -0,0 +1,47 @@
plugins {
alias(libs.plugins.android.library)
alias(libs.plugins.kotlin.compose)
}
android {
namespace = "com.example.design_system"
compileSdk {
version = release(36) {
minorApiLevel = 1
}
}
defaultConfig {
minSdk = 24
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
consumerProguardFiles("consumer-rules.pro")
}
compileOptions {
sourceCompatibility = JavaVersion.VERSION_11
targetCompatibility = JavaVersion.VERSION_11
}
buildFeatures {
compose = true
}
}
dependencies {
implementation(platform(libs.androidx.compose.bom))
implementation(libs.androidx.activity.compose)
implementation(libs.androidx.compose.material3)
implementation(libs.androidx.compose.ui)
implementation(libs.androidx.compose.ui.graphics)
implementation(libs.androidx.compose.ui.tooling.preview)
implementation(libs.androidx.core.ktx)
implementation(libs.androidx.lifecycle.runtime.ktx)
testImplementation(libs.junit)
androidTestImplementation(platform(libs.androidx.compose.bom))
androidTestImplementation(libs.androidx.compose.ui.test.junit4)
androidTestImplementation(libs.androidx.espresso.core)
androidTestImplementation(libs.androidx.junit)
debugImplementation(libs.androidx.compose.ui.test.manifest)
debugImplementation(libs.androidx.compose.ui.tooling)
}
View File
+21
View File
@@ -0,0 +1,21 @@
# Add project specific ProGuard rules here.
# You can control the set of applied configuration files using the
# proguardFiles setting in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html
# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}
# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable
# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile
@@ -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)
}
}