Skip to content

Commit 333b09b

Browse files
committed
Convert main UI to compose to fix edge-to-edge
1 parent e41e0ea commit 333b09b

23 files changed

+903
-979
lines changed

app/src/main/AndroidManifest.xml

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -53,7 +53,7 @@
5353
</service>
5454

5555
<activity
56-
android:name=".MainActivity"
56+
android:name=".main.MainActivity"
5757
android:launchMode="singleTask"
5858
android:exported="true">
5959
<intent-filter>
@@ -84,7 +84,7 @@
8484
-->
8585
<activity-alias
8686
android:name=".RemoteControlMainActivity"
87-
android:targetActivity=".MainActivity"
87+
android:targetActivity=".main.MainActivity"
8888
android:permission="android.permission.INJECT_EVENTS"
8989
android:exported="true">
9090
<intent-filter>
@@ -101,10 +101,10 @@
101101
</intent-filter>
102102
</activity-alias>
103103

104-
<activity android:name=".qrscan.QRScanActivity" android:parentActivityName=".MainActivity">
104+
<activity android:name=".qrscan.QRScanActivity" android:parentActivityName=".main.MainActivity">
105105
<meta-data
106106
android:name="android.support.PARENT_ACTIVITY"
107-
android:value=".MainActivity" />
107+
android:value=".main.MainActivity" />
108108
</activity>
109109

110110
<activity android:name=".appselection.ApplicationListActivity" />

app/src/main/java/tech/httptoolkit/android/ConnectionStatusView.kt

Lines changed: 0 additions & 109 deletions
This file was deleted.

app/src/main/java/tech/httptoolkit/android/ProxyVpnService.kt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import android.util.Log
1212
import androidx.core.app.NotificationCompat
1313
import androidx.localbroadcastmanager.content.LocalBroadcastManager
1414
import io.sentry.Sentry
15+
import tech.httptoolkit.android.main.MainActivity
1516
import tech.httptoolkit.android.vpn.socket.IProtectSocket
1617
import tech.httptoolkit.android.vpn.socket.SocketProtector
1718
import java.io.IOException
Lines changed: 247 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,247 @@
1+
package tech.httptoolkit.android.connection
2+
3+
import android.os.Build
4+
import androidx.compose.foundation.layout.*
5+
import androidx.compose.material.icons.Icons
6+
import androidx.compose.material.icons.filled.Check
7+
import androidx.compose.material.icons.filled.Warning
8+
import androidx.compose.ui.graphics.Color
9+
import androidx.compose.material3.*
10+
import androidx.compose.runtime.Composable
11+
import androidx.compose.ui.Alignment
12+
import androidx.compose.ui.Modifier
13+
import androidx.compose.ui.graphics.vector.ImageVector
14+
import androidx.compose.ui.res.painterResource
15+
import androidx.compose.ui.res.stringResource
16+
import androidx.compose.ui.text.font.FontWeight
17+
import androidx.compose.ui.unit.dp
18+
import androidx.compose.ui.unit.sp
19+
import tech.httptoolkit.android.ProxyConfig
20+
import tech.httptoolkit.android.R
21+
import tech.httptoolkit.android.portfilter.DEFAULT_PORTS
22+
import tech.httptoolkit.android.whereIsCertTrusted
23+
import tech.httptoolkit.android.ui.DmSansFontFamily
24+
25+
@Composable
26+
fun ConnectionStatusScreen(
27+
proxyConfig: ProxyConfig,
28+
totalAppCount: Int,
29+
interceptedAppCount: Int,
30+
onChangeApps: () -> Unit,
31+
interceptedPorts: Set<Int>,
32+
onChangePorts: () -> Unit,
33+
modifier: Modifier = Modifier
34+
) {
35+
val certTrustStatus = whereIsCertTrusted(proxyConfig)
36+
37+
Column(
38+
modifier = modifier
39+
.fillMaxWidth()
40+
.padding(horizontal = 16.dp, vertical = 4.dp)
41+
) {
42+
// Connected to text
43+
Text(
44+
text = if (proxyConfig.ip == "127.0.0.1") {
45+
stringResource(R.string.connected_tunnel_details)
46+
} else {
47+
stringResource(R.string.connected_details, proxyConfig.ip, proxyConfig.port)
48+
},
49+
fontSize = 16.sp,
50+
fontFamily = DmSansFontFamily,
51+
fontWeight = FontWeight.Normal,
52+
color = MaterialTheme.colorScheme.onBackground,
53+
textAlign = androidx.compose.ui.text.style.TextAlign.Center,
54+
modifier = Modifier
55+
.fillMaxWidth()
56+
.padding(bottom = 24.dp)
57+
)
58+
59+
val successColor = Color(0xFF4CAF7D)
60+
when (certTrustStatus) {
61+
"user" -> {
62+
if (Build.VERSION.SDK_INT < Build.VERSION_CODES.N) {
63+
// Pre-Android 7: User trust is sufficient
64+
CertificateStatusCard(
65+
icon = Icons.Default.Check,
66+
iconTint = successColor,
67+
heading = stringResource(R.string.pre_v7_connection_status_enabled_heading),
68+
details = stringResource(R.string.pre_v7_connection_status_details)
69+
)
70+
} else {
71+
// Android 7+: User trust is limited
72+
CertificateStatusCard(
73+
icon = Icons.Default.Check,
74+
iconTint = successColor,
75+
heading = stringResource(R.string.user_connection_status_enabled_heading)
76+
)
77+
78+
CertificateStatusCard(
79+
icon = Icons.Default.Warning,
80+
iconTint = MaterialTheme.colorScheme.error,
81+
heading = stringResource(R.string.system_connection_status_disabled_heading),
82+
details = stringResource(R.string.user_connection_status_details)
83+
)
84+
}
85+
}
86+
"system" -> {
87+
// System trust: show BOTH user and system cards
88+
CertificateStatusCard(
89+
icon = Icons.Default.Check,
90+
iconTint = successColor,
91+
heading = stringResource(R.string.user_connection_status_enabled_heading)
92+
)
93+
94+
CertificateStatusCard(
95+
icon = Icons.Default.Check,
96+
iconTint = successColor,
97+
heading = stringResource(R.string.system_connection_status_enabled_heading),
98+
details = stringResource(R.string.system_connection_status_details)
99+
)
100+
}
101+
else -> {
102+
// No certificate trust
103+
CertificateStatusCard(
104+
icon = Icons.Default.Warning,
105+
iconTint = MaterialTheme.colorScheme.error,
106+
heading = stringResource(R.string.disabled_connection_status_heading),
107+
details = stringResource(R.string.none_connection_status_details)
108+
)
109+
}
110+
}
111+
112+
// App and Port interception buttons
113+
Row(
114+
modifier = Modifier.fillMaxWidth(),
115+
horizontalArrangement = Arrangement.spacedBy(10.dp)
116+
) {
117+
InterceptionButton(
118+
icon = R.drawable.ic_apps_24,
119+
text = getAppStatusText(totalAppCount, interceptedAppCount),
120+
onClick = {
121+
onChangeApps()
122+
},
123+
modifier = Modifier
124+
.weight(1f)
125+
)
126+
127+
InterceptionButton(
128+
icon = R.drawable.ic_network_ports_24,
129+
text = getPortStatusText(interceptedPorts),
130+
onClick = onChangePorts,
131+
modifier = Modifier.weight(1f)
132+
)
133+
}
134+
}
135+
}
136+
137+
@Composable
138+
private fun CertificateStatusCard(
139+
modifier: Modifier = Modifier,
140+
icon: ImageVector,
141+
iconTint: Color,
142+
heading: String,
143+
details: String? = null,
144+
) {
145+
Card(
146+
modifier = modifier
147+
.fillMaxWidth()
148+
.padding(bottom = 10.dp),
149+
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.surface),
150+
elevation = CardDefaults.cardElevation(defaultElevation = 0.dp)
151+
) {
152+
Column(
153+
modifier = Modifier.padding(10.dp)
154+
) {
155+
Row(
156+
horizontalArrangement = Arrangement.spacedBy(10.dp),
157+
verticalAlignment = Alignment.CenterVertically
158+
) {
159+
Icon(
160+
imageVector = icon,
161+
contentDescription = null,
162+
tint = iconTint
163+
)
164+
Text(
165+
text = heading.uppercase(),
166+
fontSize = 14.sp,
167+
fontFamily = DmSansFontFamily,
168+
fontWeight = FontWeight.Bold,
169+
color = MaterialTheme.colorScheme.outline
170+
)
171+
}
172+
173+
if (details != null) {
174+
Spacer(modifier = Modifier.height(4.dp))
175+
Text(
176+
text = details,
177+
fontSize = 14.sp,
178+
lineHeight = 18.sp,
179+
fontFamily = DmSansFontFamily,
180+
fontWeight = FontWeight.Normal,
181+
color = MaterialTheme.colorScheme.onSurface,
182+
modifier = Modifier.padding(start = 34.dp, top = 5.dp)
183+
)
184+
}
185+
}
186+
}
187+
}
188+
189+
@Composable
190+
private fun InterceptionButton(
191+
icon: Int,
192+
text: String,
193+
onClick: () -> Unit,
194+
modifier: Modifier = Modifier
195+
) {
196+
Card(
197+
onClick = onClick,
198+
modifier = modifier,
199+
colors = CardDefaults.cardColors(containerColor = MaterialTheme.colorScheme.background),
200+
border = CardDefaults.outlinedCardBorder()
201+
) {
202+
Row(
203+
modifier = Modifier
204+
.fillMaxWidth()
205+
.padding(6.dp),
206+
horizontalArrangement = Arrangement.spacedBy(8.dp)
207+
) {
208+
Icon(
209+
painter = painterResource(id = icon),
210+
contentDescription = null,
211+
tint = MaterialTheme.colorScheme.onSurface
212+
)
213+
Text(
214+
text = text,
215+
fontSize = 14.sp,
216+
fontFamily = DmSansFontFamily,
217+
color = MaterialTheme.colorScheme.onSurface
218+
)
219+
}
220+
}
221+
}
222+
223+
@Composable
224+
private fun getAppStatusText(totalAppCount: Int, interceptedAppCount: Int): String {
225+
return when {
226+
totalAppCount == interceptedAppCount -> stringResource(R.string.all_apps)
227+
interceptedAppCount > 10 -> stringResource(R.string.selected_apps)
228+
else -> stringResource(
229+
R.string.few_apps,
230+
interceptedAppCount,
231+
if (interceptedAppCount != 1) "s" else ""
232+
)
233+
}
234+
}
235+
236+
@Composable
237+
private fun getPortStatusText(interceptedPorts: Set<Int>): String {
238+
return when {
239+
interceptedPorts == DEFAULT_PORTS -> stringResource(R.string.default_ports)
240+
interceptedPorts.size > 10 -> stringResource(R.string.selected_ports)
241+
else -> stringResource(
242+
R.string.few_ports,
243+
interceptedPorts.size,
244+
if (interceptedPorts.size != 1) "s" else ""
245+
)
246+
}
247+
}

0 commit comments

Comments
 (0)