Skip to content

Commit e836102

Browse files
committed
I'm too lazy to list all the commits
1 parent a16087d commit e836102

8 files changed

Lines changed: 287 additions & 47 deletions

File tree

app/src/main/java/com/kaeru/app/MainActivity.kt

Lines changed: 12 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -139,7 +139,7 @@ class MainActivity : ComponentActivity() {
139139
// rotas
140140
object Routes {
141141
const val HOME = "home_screen"
142-
const val RESULT = "result_screen/{code}"
142+
const val RESULT = "result_screen/{code}?carrier={carrier}"
143143
const val SETTINGS = "settings_screen"
144144
const val APPEARANCE = "appearance_screen"
145145
const val THEME = "theme_screen"
@@ -172,8 +172,8 @@ fun KaeruNavGraph(viewModel: TrackingViewModel, updateRelease: GithubRelease?) {
172172
KaeruTabsScreen(
173173
viewModel = viewModel,
174174
updateRelease = updateRelease,
175-
onNavigateToResult = { code ->
176-
navController.navigate("result_screen/$code")
175+
onNavigateToResult = { code, carrier ->
176+
navController.navigate("result_screen/$code?carrier=$carrier")
177177
},
178178
onNavigateToSettings = {
179179
navController.navigate(Routes.SETTINGS)
@@ -220,12 +220,18 @@ fun KaeruNavGraph(viewModel: TrackingViewModel, updateRelease: GithubRelease?) {
220220

221221
composable(
222222
route = Routes.RESULT,
223-
arguments = listOf(navArgument("code") { type = NavType.StringType })
223+
arguments = listOf(navArgument("code") { type = NavType.StringType },
224+
navArgument("carrier") {
225+
type = NavType.StringType
226+
defaultValue = "Auto"
227+
})
224228
) { backStackEntry ->
225229
val code = backStackEntry.arguments?.getString("code") ?: ""
230+
val carrier = backStackEntry.arguments?.getString("carrier") ?: "Auto"
226231

227232
ResultScreen(
228233
trackingCode = code,
234+
carrier = carrier,
229235
viewModel = viewModel,
230236
onBack = {
231237
navController.popBackStack()
@@ -261,7 +267,7 @@ fun KaeruNavGraph(viewModel: TrackingViewModel, updateRelease: GithubRelease?) {
261267
fun KaeruTabsScreen(
262268
viewModel: TrackingViewModel,
263269
updateRelease: GithubRelease?,
264-
onNavigateToResult: (String) -> Unit,
270+
onNavigateToResult: (String, String) -> Unit,
265271
onNavigateToSettings: () -> Unit
266272
) {
267273
val defaultTab by viewModel.defaultOpenTab.collectAsState()
@@ -319,7 +325,7 @@ fun KaeruTabsScreen(
319325
AppDestinations.HISTORY -> {
320326
HistoryScreen(
321327
viewModel = viewModel,
322-
onNavigateToResult = onNavigateToResult
328+
onNavigateToResult = { code -> onNavigateToResult(code, "Auto") }
323329
)
324330
}
325331
AppDestinations.SEARCH -> {
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
package com.kaeru.app.tracking
2+
3+
import com.google.gson.annotations.SerializedName
4+
5+
data class TotalExpressResponse(
6+
@SerializedName("data") val data: TotalExpressData?
7+
)
8+
9+
data class TotalExpressData(
10+
@SerializedName("layouts") val layouts: List<TotalExpressLayout>?
11+
)
12+
13+
data class TotalExpressLayout(
14+
@SerializedName("etapas") val etapas: List<TotalExpressEtapa>?
15+
)
16+
17+
data class TotalExpressEtapa(
18+
@SerializedName("listaStatus") val listaStatus: List<TotalExpressStatus>?
19+
)
20+
21+
data class TotalExpressStatus(
22+
@SerializedName("statusDescricao") val statusDescricao: String?,
23+
@SerializedName("data") val data: String?,
24+
@SerializedName("hora") val hora: String?,
25+
@SerializedName("mensagemEvaTraducao") val mensagemEvaTraducao: TotalExpressMensagem?
26+
)
27+
28+
data class TotalExpressMensagem(
29+
@SerializedName("mensagemEva") val mensagemEva: String?
30+
)

app/src/main/java/com/kaeru/app/tracking/TrackingRepository.kt

Lines changed: 93 additions & 10 deletions
Original file line numberDiff line numberDiff line change
@@ -30,12 +30,12 @@ class TrackingRepository(private val context: Context) {
3030
listOf("ME").any { cleanCode.startsWith(it) } && cleanCode.length >= 12 -> true
3131
listOf("AJ").any { cleanCode.startsWith(it) } && cleanCode.length >= 12 -> true
3232
listOf("BR", "SPX").any { cleanCode.startsWith(it) } && cleanCode.length >= 12 -> true
33+
listOf("TX").any { cleanCode.startsWith(it) } || listOf("TX").any { cleanCode.endsWith(it) } -> true
3334
listOf("AD", "AB", "AN").any { cleanCode.startsWith(it) } && cleanCode.length >= 12 -> true
3435
else -> false
3536
}
3637
}
37-
38-
suspend fun trackPackage(code: String, forceRefresh: Boolean = false): TrackingResponse? {
38+
suspend fun trackPackage(code: String, forceRefresh: Boolean = false, carrier: String = "Auto"): TrackingResponse? {
3939
val cleanCode = code.trim().uppercase()
4040

4141
if (!forceRefresh) {
@@ -48,14 +48,26 @@ class TrackingRepository(private val context: Context) {
4848

4949
Log.d("TRACKING", "Baixando da internet: $cleanCode")
5050

51-
val result = when {
52-
listOf("MZ", "LJ", "LOG").any { cleanCode.startsWith(it) } -> { trackViaLoggi(cleanCode) }
53-
listOf("NN", "LP").any { cleanCode.startsWith(it) } -> { trackCainiaoFree(cleanCode) }
54-
listOf("ME").any { cleanCode.startsWith(it) } -> { trackMelhorRastreioFree(cleanCode) }
55-
listOf("AJ").any { cleanCode.startsWith(it) } -> { trackViaAnjun(cleanCode) }
56-
listOf("BR", "SPX").any { cleanCode.startsWith(it) } -> { trackViaSpxWebView(cleanCode) }
57-
listOf("AD", "AB", "AN").any { cleanCode.startsWith(it) } -> { trackViaLinketrackWebView(cleanCode) }
58-
else -> { null }
51+
val result = when (carrier) {
52+
"Correios" -> trackViaLinketrackWebView(cleanCode)
53+
"Loggi" -> trackViaLoggi(cleanCode)
54+
"Shopee" -> trackViaSpxWebView(cleanCode)
55+
"AliExpress" -> trackCainiaoFree(cleanCode)
56+
"Shein" -> trackViaAnjun(cleanCode)
57+
"Melhor Envio" -> trackMelhorRastreioFree(cleanCode)
58+
"Total Express" -> trackViaTotalExpress(cleanCode)
59+
else -> {
60+
when {
61+
listOf("MZ", "LJ", "LOG").any { cleanCode.startsWith(it) } -> { trackViaLoggi(cleanCode) }
62+
listOf("NN", "LP").any { cleanCode.startsWith(it) } -> { trackCainiaoFree(cleanCode) }
63+
listOf("ME").any { cleanCode.startsWith(it) } -> { trackMelhorRastreioFree(cleanCode) }
64+
listOf("AJ").any { cleanCode.startsWith(it) } -> { trackViaAnjun(cleanCode) }
65+
listOf("BR", "SPX").any { cleanCode.startsWith(it) } -> { trackViaSpxWebView(cleanCode) }
66+
listOf("TX").any { cleanCode.startsWith(it) } || listOf("TX").any { cleanCode.endsWith(it) } -> { trackViaTotalExpress(cleanCode) }
67+
listOf("AD", "AB", "AN").any { cleanCode.startsWith(it) } -> { trackViaLinketrackWebView(cleanCode) }
68+
else -> { null }
69+
}
70+
}
5971
}
6072

6173
if (result != null && !result.events.isNullOrEmpty()) {
@@ -65,6 +77,77 @@ class TrackingRepository(private val context: Context) {
6577
return result
6678
}
6779

80+
private suspend fun trackViaTotalExpress(code: String): TrackingResponse? {
81+
return withContext(Dispatchers.IO) {
82+
try {
83+
val url = "https://totalconecta.totalexpress.com.br/mfe-rastreio/api/order-data?awb=$code&language=pt"
84+
val request = Request.Builder()
85+
.url(url)
86+
.addHeader("User-Agent", "Mozilla/5.0 (Windows NT 10.0; Win64; x64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/122.0.0.0 Safari/537.36")
87+
.addHeader("Accept", "application/json, text/plain, */*")
88+
.addHeader("Accept-Language", "pt-BR,pt;q=0.9,en-US;q=0.8,en;q=0.7")
89+
.addHeader("Sec-Ch-Ua", "\"Chromium\";v=\"122\", \"Not(A:Brand\";v=\"24\", \"Google Chrome\";v=\"122\"")
90+
.addHeader("Sec-Ch-Ua-Mobile", "?0")
91+
.addHeader("Sec-Ch-Ua-Platform", "\"Windows\"")
92+
.addHeader("Sec-Fetch-Dest", "empty")
93+
.addHeader("Sec-Fetch-Mode", "cors")
94+
.addHeader("Sec-Fetch-Site", "same-site")
95+
.addHeader("Referer", "https://totalconecta.totalexpress.com.br/mfe-rastreio/")
96+
.build()
97+
val response = client.newCall(request).execute()
98+
val body = response.body?.string()
99+
if (!response.isSuccessful) {
100+
return@withContext null
101+
}
102+
if (body.isNullOrBlank()) {
103+
return@withContext null
104+
}
105+
val rawData = gson.fromJson(body, TotalExpressResponse::class.java)
106+
val layouts = rawData.data?.layouts
107+
if (layouts.isNullOrEmpty()) {
108+
return@withContext null
109+
}
110+
val events = mutableListOf<TrackingEvent>()
111+
112+
layouts.forEach { layout ->
113+
layout.etapas?.forEach { etapa ->
114+
etapa.listaStatus?.forEach { status ->
115+
val dateParts = status.data?.split("-")
116+
val formattedDate = if (dateParts != null && dateParts.size == 3) {
117+
"${dateParts[2]}/${dateParts[1]}/${dateParts[0]}"
118+
} else {
119+
status.data ?: ""
120+
}
121+
122+
events.add(
123+
TrackingEvent(
124+
status = status.statusDescricao ?: "Atualização",
125+
date = formattedDate,
126+
time = status.hora ?: "",
127+
location = status.mensagemEvaTraducao?.mensagemEva ?: "Total Express",
128+
subStatus = null
129+
)
130+
)
131+
}
132+
}
133+
}
134+
135+
if (events.isEmpty()) {
136+
Log.e("TOTAL_EXPRESS", "Eventos vazios após mapeamento.")
137+
return@withContext null
138+
}
139+
140+
Log.d("TOTAL_EXPRESS", "Sucesso! Foram mapeados ${events.size} eventos.")
141+
return@withContext TrackingResponse(tracking_code = code, events = events.reversed())
142+
143+
} catch (e: Exception) {
144+
Log.e("TOTAL_EXPRESS", "Erro fatal no rastreio: ${e.message}")
145+
e.printStackTrace()
146+
null
147+
}
148+
}
149+
}
150+
68151
private suspend fun trackViaLinketrackWebView(code: String): TrackingResponse? {
69152
val cleanCode = code.substringBefore("|").trim()
70153

app/src/main/java/com/kaeru/app/tracking/TrackingViewModel.kt

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -172,11 +172,11 @@ class TrackingViewModel(
172172
prefs.edit().putString("default_filter", filter.name).apply()
173173
}
174174
private var searchJob: Job? = null
175-
fun trackPackage(code: String) {
175+
fun trackPackage(code: String, carrier: String = "Auto") {
176176
searchJob?.cancel()
177177
errorMessage = null
178178
trackingResult = null
179-
if (!repository.isCarrierSupported(code)) {
179+
if (carrier == "Auto" && !repository.isCarrierSupported(code)) {
180180
isLoading = false
181181
return
182182
}
@@ -206,7 +206,7 @@ class TrackingViewModel(
206206
isLoading = false
207207
return@launch
208208
}
209-
val response = repository.trackPackage(code, forceRefresh = true)
209+
val response = repository.trackPackage(code, forceRefresh = true, carrier = carrier)
210210
if (response != null) {
211211
trackingResult = response
212212
updateHistoryStatusIfExists(code, response)

app/src/main/java/com/kaeru/app/ui/screens/HistoryScreen.kt

Lines changed: 38 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
package com.kaeru.app.ui.screens
22

3+
import androidx.compose.foundation.Image
34
import androidx.compose.foundation.background
45
import androidx.compose.foundation.clickable
56
import androidx.compose.foundation.layout.*
@@ -36,8 +37,10 @@ import androidx.compose.runtime.saveable.rememberSaveable
3637
import androidx.compose.ui.draw.alpha
3738
import androidx.compose.ui.draw.scale
3839
import androidx.compose.ui.graphics.Color
40+
import androidx.compose.ui.text.style.TextAlign
3941
import com.kaeru.app.ui.components.AnimatedFilterChip
4042
import kotlinx.coroutines.launch
43+
import androidx.compose.ui.graphics.ColorFilter
4144

4245
@Composable
4346
fun HistoryScreen(
@@ -148,24 +151,13 @@ fun HistoryScreen(
148151
}
149152
}
150153
}
151-
if (history.isEmpty()) {
154+
if (filteredHistory.isEmpty()) {
152155
item {
153-
Box(
156+
EmptyHistoryState(
154157
modifier = Modifier
155158
.fillMaxWidth()
156-
.height(300.dp),
157-
contentAlignment = Alignment.Center
158-
) {
159-
val emptyText = when (currentFilter) {
160-
TrackingFilter.IN_TRANSIT -> stringResource(R.string.no_packages_saved)
161-
TrackingFilter.DELIVERED -> stringResource(R.string.no_packages_saved)
162-
TrackingFilter.ALL -> stringResource(R.string.no_packages_saved)
163-
}
164-
Text(
165-
text = emptyText,
166-
color = MaterialTheme.colorScheme.onSurfaceVariant
167-
)
168-
}
159+
.fillParentMaxHeight(1f)
160+
)
169161
}
170162
} else {
171163
items(filteredHistory, key = { it.code }) { item ->
@@ -416,4 +408,35 @@ fun SwipeToDeleteIcon(state: SwipeToDismissBoxState) {
416408
.alpha(iconAlpha)
417409
)
418410
}
411+
}
412+
413+
@Composable
414+
fun EmptyHistoryState(modifier: Modifier = Modifier) {
415+
Column(
416+
modifier = modifier,
417+
horizontalAlignment = Alignment.CenterHorizontally,
418+
verticalArrangement = Arrangement.Center
419+
) {
420+
Image(
421+
painter = painterResource(id = R.drawable.ic_empty_screen),
422+
contentDescription = "Histórico vazio",
423+
modifier = Modifier
424+
.size(220.dp)
425+
.padding(bottom = 24.dp),
426+
colorFilter = ColorFilter.tint(MaterialTheme.colorScheme.onSurfaceVariant)
427+
)
428+
Text(
429+
text = "Parece meio vazio aqui...",
430+
color = MaterialTheme.colorScheme.onSurfaceVariant,
431+
fontSize = 22.sp,
432+
modifier = Modifier
433+
.padding(bottom = 15.dp),
434+
textAlign = TextAlign.Center
435+
)
436+
Text(
437+
text = "Ainda sem encomendas",
438+
color = MaterialTheme.colorScheme.onSurfaceVariant,
439+
textAlign = TextAlign.Center
440+
)
441+
}
419442
}

app/src/main/java/com/kaeru/app/ui/screens/ResultScreen.kt

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ val SoftRed = Color(0xFFEF5350)
4848
@Composable
4949
fun ResultScreen(
5050
trackingCode: String,
51+
carrier: String,
5152
viewModel: TrackingViewModel,
5253
onBack: () -> Unit
5354
) {
@@ -65,7 +66,7 @@ fun ResultScreen(
6566

6667
LaunchedEffect(trackingCode) {
6768
if (viewModel.trackingResult?.tracking_code != trackingCode) {
68-
viewModel.trackPackage(trackingCode)
69+
viewModel.trackPackage(trackingCode, carrier = carrier)
6970
}
7071
}
7172

0 commit comments

Comments
 (0)