Skip to content

Commit 754de03

Browse files
committed
jetpack compose
1 parent 67a4c35 commit 754de03

34 files changed

Lines changed: 3315 additions & 2053 deletions

app/build.gradle.kts

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,8 @@ plugins {
77
id("pvnclient.android.application")
88
id("com.google.gms.google-services")
99
alias(libs.plugins.crashlytics)
10+
alias(libs.plugins.kotlin.compose)
11+
alias(libs.plugins.kotlin.lombok)
1012
}
1113

1214
val keystorePropertiesFile: File = rootProject.file("keystore.properties")
@@ -101,6 +103,7 @@ android {
101103
}
102104
buildFeatures {
103105
viewBinding = true
106+
compose = true
104107
}
105108

106109
externalNativeBuild {
@@ -113,6 +116,16 @@ android {
113116

114117
dependencies {
115118
implementation(platform(libs.firebase.bom))
119+
val composeBom = platform(libs.compose.bom)
120+
implementation(composeBom)
121+
implementation(libs.accompanist.drawablepainter)
122+
implementation(libs.activity.compose)
123+
implementation(libs.compose.material3)
124+
implementation(libs.compose.runtime.livedata)
125+
implementation(libs.compose.ui)
126+
implementation(libs.compose.ui.tooling.preview)
127+
implementation(libs.lifecycle.viewmodel.compose)
128+
debugImplementation(libs.compose.ui.tooling)
116129
implementation(project(":core:common"))
117130
implementation(project(":vpnclient"))
118131
implementation(libs.androidx.activity)

app/src/main/java/org/fptn/vpn/services/snichecker/SniCheckerService.java

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52,9 +52,12 @@ public class SniCheckerService extends Service {
5252

5353
public static final int SNI_BATCH_SIZE = 25;
5454

55-
@Getter
5655
private static final MutableLiveData<SniCheckerServiceState> staticServiceState = new MutableLiveData<>(SniCheckerServiceState.INACTIVE);
5756

57+
public static MutableLiveData<SniCheckerServiceState> getStaticServiceState() {
58+
return staticServiceState;
59+
}
60+
5861
@Getter
5962
private final MutableLiveData<SniCheckerServiceState> serviceState = new MutableLiveData<>(SniCheckerServiceState.INACTIVE);
6063

Lines changed: 125 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,125 @@
1+
package org.fptn.vpn.ui.components
2+
3+
import android.app.AlertDialog
4+
import android.content.Context
5+
import android.content.Intent
6+
import android.graphics.Bitmap
7+
import android.text.Html
8+
import android.text.method.LinkMovementMethod
9+
import android.widget.ImageView
10+
import android.widget.TextView
11+
import androidx.compose.material3.Icon
12+
import androidx.compose.material3.NavigationBar
13+
import androidx.compose.material3.NavigationBarItem
14+
import androidx.compose.material3.NavigationBarItemDefaults
15+
import androidx.compose.material3.Text
16+
import androidx.compose.runtime.Composable
17+
import androidx.compose.ui.graphics.Color
18+
import androidx.compose.ui.res.painterResource
19+
import androidx.compose.ui.res.stringResource
20+
import com.google.zxing.BarcodeFormat
21+
import com.google.zxing.MultiFormatWriter
22+
import org.fptn.vpn.R
23+
import org.fptn.vpn.ui.theme.Primary
24+
import org.fptn.vpn.ui.theme.Secondary
25+
import org.fptn.vpn.ui.theme.White
26+
import org.fptn.vpn.ui.theme.White10
27+
28+
enum class BottomNavTab { HOME, SETTINGS, SHARE }
29+
30+
@Composable
31+
fun BottomNavBar(
32+
current: BottomNavTab,
33+
onHome: () -> Unit,
34+
onSettings: () -> Unit,
35+
context: Context,
36+
) {
37+
NavigationBar(
38+
containerColor = Primary,
39+
contentColor = White,
40+
) {
41+
NavigationBarItem(
42+
selected = current == BottomNavTab.HOME,
43+
onClick = { if (current != BottomNavTab.HOME) onHome() },
44+
icon = {
45+
Icon(
46+
painter = painterResource(R.drawable.ic_baseline_home_24),
47+
contentDescription = stringResource(R.string.menu_home)
48+
)
49+
},
50+
label = { Text(stringResource(R.string.menu_home)) },
51+
colors = navItemColors(),
52+
)
53+
NavigationBarItem(
54+
selected = current == BottomNavTab.SETTINGS,
55+
onClick = { if (current != BottomNavTab.SETTINGS) onSettings() },
56+
icon = {
57+
Icon(
58+
painter = painterResource(R.drawable.ic_baseline_settings_24),
59+
contentDescription = stringResource(R.string.menu_settings)
60+
)
61+
},
62+
label = { Text(stringResource(R.string.menu_settings)) },
63+
colors = navItemColors(),
64+
)
65+
NavigationBarItem(
66+
selected = false,
67+
onClick = { showShareDialog(context) },
68+
icon = {
69+
Icon(
70+
painter = painterResource(R.drawable.ic_baseline_share_24),
71+
contentDescription = stringResource(R.string.menu_share)
72+
)
73+
},
74+
label = { Text(stringResource(R.string.menu_share)) },
75+
colors = navItemColors(),
76+
)
77+
}
78+
}
79+
80+
@Composable
81+
private fun navItemColors() = NavigationBarItemDefaults.colors(
82+
selectedIconColor = Secondary,
83+
selectedTextColor = Secondary,
84+
unselectedIconColor = White10,
85+
unselectedTextColor = White10,
86+
indicatorColor = Color.Transparent,
87+
)
88+
89+
private fun showShareDialog(context: Context) {
90+
val qrBitmap = generateQRCode(context.getString(R.string.play_market_link), 500, 500)
91+
val inflater = android.view.LayoutInflater.from(context)
92+
val dialogView = inflater.inflate(R.layout.share_dialog, null)
93+
94+
(dialogView.findViewById<ImageView>(R.id.qr_code_image)).setImageBitmap(qrBitmap)
95+
val textView = dialogView.findViewById<TextView>(R.id.link_text)
96+
textView.text = Html.fromHtml(context.getString(R.string.info_message_html), Html.FROM_HTML_MODE_LEGACY)
97+
textView.movementMethod = LinkMovementMethod.getInstance()
98+
99+
AlertDialog.Builder(context)
100+
.setView(dialogView)
101+
.setMessage(R.string.menu_share)
102+
.setPositiveButton(R.string.share_via_message) { _, _ -> shareViaMessage(context) }
103+
.setNeutralButton(R.string.close_button_text, null)
104+
.create()
105+
.show()
106+
}
107+
108+
private fun shareViaMessage(context: Context) {
109+
val shareIntent = Intent(Intent.ACTION_SEND).apply {
110+
type = "text/plain"
111+
putExtra(Intent.EXTRA_TEXT, context.getString(R.string.share_message))
112+
}
113+
context.startActivity(Intent.createChooser(shareIntent, context.getString(R.string.share_title)))
114+
}
115+
116+
private fun generateQRCode(text: String, width: Int, height: Int): Bitmap {
117+
val bitMatrix = MultiFormatWriter().encode(text, BarcodeFormat.QR_CODE, width, height)
118+
val bitmap = Bitmap.createBitmap(width, height, Bitmap.Config.ARGB_8888)
119+
for (x in 0 until width) {
120+
for (y in 0 until height) {
121+
bitmap.setPixel(x, y, if (bitMatrix[x, y]) 0xFF000000.toInt() else 0xFFFFFFFF.toInt())
122+
}
123+
}
124+
return bitmap
125+
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package org.fptn.vpn.ui.components
2+
3+
import android.text.Html
4+
import android.text.method.LinkMovementMethod
5+
import android.widget.TextView
6+
import androidx.compose.runtime.Composable
7+
import androidx.compose.ui.Modifier
8+
import androidx.compose.ui.graphics.Color
9+
import androidx.compose.ui.graphics.toArgb
10+
import androidx.compose.ui.viewinterop.AndroidView
11+
12+
@Composable
13+
fun HtmlText(
14+
html: String,
15+
modifier: Modifier = Modifier,
16+
textColor: Color = Color.White,
17+
textSizeSp: Float = 16f,
18+
) {
19+
AndroidView(
20+
modifier = modifier,
21+
factory = { context ->
22+
TextView(context).apply {
23+
movementMethod = LinkMovementMethod.getInstance()
24+
setTextColor(textColor.toArgb())
25+
setTextSize(android.util.TypedValue.COMPLEX_UNIT_SP, textSizeSp)
26+
setPadding(8, 8, 8, 8)
27+
}
28+
},
29+
update = { view ->
30+
view.text = Html.fromHtml(html, Html.FROM_HTML_MODE_LEGACY)
31+
}
32+
)
33+
}

0 commit comments

Comments
 (0)