Skip to content

Commit 2e7c8be

Browse files
author
code3-dev
committed
Add helper server functionality with fallback support
1 parent 70bc60a commit 2e7c8be

File tree

15 files changed

+334
-262
lines changed

15 files changed

+334
-262
lines changed

.idea/deploymentTargetSelector.xml

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

app/src/main/java/com/pira/ccloud/MainActivity.kt

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -100,8 +100,8 @@ fun MainScreen(onThemeSettingsChanged: (ThemeSettings) -> Unit = {}) {
100100
val currentScreen = when {
101101
currentRoute?.startsWith("single_movie") == true -> AppScreens.SingleMovie
102102
currentRoute?.startsWith("single_series") == true -> AppScreens.SingleSeries
103-
currentRoute == AppScreens.Favorites.route -> AppScreens.Favorites
104-
currentRoute == AppScreens.About.route -> AppScreens.About
103+
currentRoute == "favorites" -> AppScreens.Favorites
104+
currentRoute == "about" -> AppScreens.About
105105
else -> AppScreens.screens.find { it.route == currentRoute } ?: AppScreens.Movies
106106
}
107107

app/src/main/java/com/pira/ccloud/components/DownloadOptionsDialog.kt

Lines changed: 24 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -29,14 +29,14 @@ fun DownloadOptionsDialog(
2929
onDismissRequest = onDismiss,
3030
title = {
3131
Text(
32-
text = "Download Options",
32+
text = "Download & Play Options",
3333
style = MaterialTheme.typography.headlineSmall,
3434
fontWeight = androidx.compose.ui.text.font.FontWeight.Bold
3535
)
3636
},
3737
text = {
3838
Text(
39-
text = "Choose how to download this video",
39+
text = "Choose how to handle this video",
4040
style = MaterialTheme.typography.bodyMedium,
4141
color = MaterialTheme.colorScheme.onSurfaceVariant
4242
)
@@ -46,6 +46,14 @@ fun DownloadOptionsDialog(
4646
modifier = Modifier.fillMaxWidth(),
4747
verticalArrangement = Arrangement.spacedBy(8.dp)
4848
) {
49+
// Download options
50+
Text(
51+
text = "Download Options",
52+
style = MaterialTheme.typography.titleMedium,
53+
color = MaterialTheme.colorScheme.primary,
54+
modifier = Modifier.fillMaxWidth()
55+
)
56+
4957
Button(
5058
onClick = {
5159
onCopyLink()
@@ -91,6 +99,14 @@ fun DownloadOptionsDialog(
9199
Text("Download with ADM")
92100
}
93101

102+
// Play options
103+
Text(
104+
text = "Play Options",
105+
style = MaterialTheme.typography.titleMedium,
106+
color = MaterialTheme.colorScheme.primary,
107+
modifier = Modifier.fillMaxWidth()
108+
)
109+
94110
Button(
95111
onClick = {
96112
onOpenInVLC()
@@ -99,8 +115,8 @@ fun DownloadOptionsDialog(
99115
modifier = Modifier.fillMaxWidth(),
100116
shape = RoundedCornerShape(16.dp),
101117
colors = androidx.compose.material3.ButtonDefaults.buttonColors(
102-
containerColor = MaterialTheme.colorScheme.primaryContainer,
103-
contentColor = MaterialTheme.colorScheme.onPrimaryContainer
118+
containerColor = MaterialTheme.colorScheme.secondaryContainer,
119+
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
104120
)
105121
) {
106122
Text("Open in VLC Player")
@@ -114,8 +130,8 @@ fun DownloadOptionsDialog(
114130
modifier = Modifier.fillMaxWidth(),
115131
shape = RoundedCornerShape(16.dp),
116132
colors = androidx.compose.material3.ButtonDefaults.buttonColors(
117-
containerColor = MaterialTheme.colorScheme.primaryContainer,
118-
contentColor = MaterialTheme.colorScheme.onPrimaryContainer
133+
containerColor = MaterialTheme.colorScheme.secondaryContainer,
134+
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
119135
)
120136
) {
121137
Text("Open in MX Player")
@@ -129,8 +145,8 @@ fun DownloadOptionsDialog(
129145
modifier = Modifier.fillMaxWidth(),
130146
shape = RoundedCornerShape(16.dp),
131147
colors = androidx.compose.material3.ButtonDefaults.buttonColors(
132-
containerColor = MaterialTheme.colorScheme.primaryContainer,
133-
contentColor = MaterialTheme.colorScheme.onPrimaryContainer
148+
containerColor = MaterialTheme.colorScheme.secondaryContainer,
149+
contentColor = MaterialTheme.colorScheme.onSecondaryContainer
134150
)
135151
) {
136152
Text("Open in KM Player")
Lines changed: 62 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,62 @@
1+
package com.pira.ccloud.data.repository
2+
3+
import kotlinx.coroutines.Dispatchers
4+
import kotlinx.coroutines.withContext
5+
import okhttp3.OkHttpClient
6+
import okhttp3.Request
7+
import java.util.concurrent.TimeUnit
8+
9+
open class BaseRepository {
10+
protected val client = OkHttpClient.Builder()
11+
.connectTimeout(30, TimeUnit.SECONDS)
12+
.readTimeout(30, TimeUnit.SECONDS)
13+
.build()
14+
15+
protected val API_KEY = "4F5A9C3D9A86FA54EACEDDD635185"
16+
17+
// Helper servers array
18+
protected val helperServers = arrayOf(
19+
"https://server-hi-speed-iran.info"
20+
)
21+
22+
protected suspend fun executeRequest(
23+
primaryUrl: String,
24+
requestBuilder: (String) -> Request
25+
): String {
26+
return withContext(Dispatchers.IO) {
27+
// First, try the primary server
28+
try {
29+
val primaryRequest = requestBuilder(primaryUrl)
30+
client.newCall(primaryRequest).execute().use { response ->
31+
if (response.isSuccessful) {
32+
return@withContext response.body?.string()
33+
?: throw Exception("Empty response body from primary server")
34+
} else {
35+
throw Exception("Primary server returned error: ${response.code}")
36+
}
37+
}
38+
} catch (primaryException: Exception) {
39+
// If primary server fails, try helper servers
40+
for (helperServer in helperServers) {
41+
try {
42+
// Replace the host in the URL with the helper server
43+
val helperUrl = primaryUrl.replace(Regex("^https?://[^/]+"), helperServer)
44+
val helperRequest = requestBuilder(helperUrl)
45+
client.newCall(helperRequest).execute().use { response ->
46+
if (response.isSuccessful) {
47+
return@withContext response.body?.string()
48+
?: throw Exception("Empty response body from helper server")
49+
}
50+
}
51+
} catch (helperException: Exception) {
52+
// Continue to next helper server
53+
continue
54+
}
55+
}
56+
57+
// If all servers fail, throw the original exception
58+
throw primaryException
59+
}
60+
}
61+
}
62+
}

app/src/main/java/com/pira/ccloud/data/repository/MovieRepository.kt

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -12,33 +12,17 @@ import org.json.JSONArray
1212
import org.json.JSONObject
1313
import java.util.concurrent.TimeUnit
1414

15-
class MovieRepository {
16-
private val client = OkHttpClient.Builder()
17-
.connectTimeout(30, TimeUnit.SECONDS)
18-
.readTimeout(30, TimeUnit.SECONDS)
19-
.build()
20-
15+
class MovieRepository : BaseRepository() {
2116
private val BASE_URL = "https://hostinnegar.com/api/movie/by/filtres/0/created"
22-
private val API_KEY = "4F5A9C3D9A86FA54EACEDDD635185"
2317

2418
suspend fun getMovies(page: Int = 0): List<Movie> {
2519
return withContext(Dispatchers.IO) {
2620
try {
2721
val url = "$BASE_URL/$page/$API_KEY"
28-
val request = Request.Builder()
29-
.url(url)
30-
.build()
3122

32-
client.newCall(request).execute().use { response ->
33-
if (!response.isSuccessful) {
34-
throw Exception("Failed to fetch movies: ${response.code}")
35-
}
36-
37-
val jsonData = response.body?.string()
38-
?: throw Exception("Empty response body")
39-
40-
parseMovies(jsonData)
41-
}
23+
val jsonData = executeRequest(url) { Request.Builder().url(it).build() }
24+
25+
parseMovies(jsonData)
4226
} catch (e: Exception) {
4327
throw Exception("Error fetching movies: ${e.message}")
4428
}

app/src/main/java/com/pira/ccloud/data/repository/SearchRepository.kt

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -15,35 +15,19 @@ import java.net.URLEncoder
1515
import java.nio.charset.StandardCharsets
1616
import java.util.concurrent.TimeUnit
1717

18-
class SearchRepository {
19-
private val client = OkHttpClient.Builder()
20-
.connectTimeout(30, TimeUnit.SECONDS)
21-
.readTimeout(30, TimeUnit.SECONDS)
22-
.build()
23-
18+
class SearchRepository : BaseRepository() {
2419
private val BASE_URL = "https://hostinnegar.com/api/search"
25-
private val API_KEY = "4F5A9C3D9A86FA54EACEDDD635185"
2620

2721
suspend fun search(query: String): SearchResult {
2822
return withContext(Dispatchers.IO) {
2923
try {
3024
// Properly encode the query for URL paths
3125
val encodedQuery = URLEncoder.encode(query, StandardCharsets.UTF_8.toString()).replace("+", "%20")
3226
val url = "$BASE_URL/$encodedQuery/$API_KEY/"
33-
val request = Request.Builder()
34-
.url(url)
35-
.build()
3627

37-
client.newCall(request).execute().use { response ->
38-
if (!response.isSuccessful) {
39-
throw Exception("Failed to search: ${response.code}")
40-
}
41-
42-
val jsonData = response.body?.string()
43-
?: throw Exception("Empty response body")
44-
45-
parseSearchResult(jsonData)
46-
}
28+
val jsonData = executeRequest(url) { Request.Builder().url(it).build() }
29+
30+
parseSearchResult(jsonData)
4731
} catch (e: Exception) {
4832
throw Exception("Error searching: ${e.message}")
4933
}

app/src/main/java/com/pira/ccloud/data/repository/SeasonsRepository.kt

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,17 @@ import org.json.JSONArray
1111
import org.json.JSONObject
1212
import java.util.concurrent.TimeUnit
1313

14-
class SeasonsRepository {
15-
private val client = OkHttpClient.Builder()
16-
.connectTimeout(30, TimeUnit.SECONDS)
17-
.readTimeout(30, TimeUnit.SECONDS)
18-
.build()
19-
14+
class SeasonsRepository : BaseRepository() {
2015
private val BASE_URL = "https://hostinnegar.com/api/season/by/serie"
21-
private val API_KEY = "4F5A9C3D9A86FA54EACEDDD635185"
2216

2317
suspend fun getSeasons(seriesId: Int): List<Season> {
2418
return withContext(Dispatchers.IO) {
2519
try {
2620
val url = "$BASE_URL/$seriesId/$API_KEY/"
27-
val request = Request.Builder()
28-
.url(url)
29-
.build()
3021

31-
client.newCall(request).execute().use { response ->
32-
if (!response.isSuccessful) {
33-
throw Exception("Failed to fetch seasons: ${response.code}")
34-
}
35-
36-
val jsonData = response.body?.string()
37-
?: throw Exception("Empty response body")
38-
39-
parseSeasons(jsonData)
40-
}
22+
val jsonData = executeRequest(url) { Request.Builder().url(it).build() }
23+
24+
parseSeasons(jsonData)
4125
} catch (e: Exception) {
4226
throw Exception("Error fetching seasons: ${e.message}")
4327
}

app/src/main/java/com/pira/ccloud/data/repository/SeriesRepository.kt

Lines changed: 4 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -11,33 +11,17 @@ import org.json.JSONArray
1111
import org.json.JSONObject
1212
import java.util.concurrent.TimeUnit
1313

14-
class SeriesRepository {
15-
private val client = OkHttpClient.Builder()
16-
.connectTimeout(30, TimeUnit.SECONDS)
17-
.readTimeout(30, TimeUnit.SECONDS)
18-
.build()
19-
14+
class SeriesRepository : BaseRepository() {
2015
private val BASE_URL = "https://hostinnegar.com/api/serie/by/filtres/0/created"
21-
private val API_KEY = "4F5A9C3D9A86FA54EACEDDD635185"
2216

2317
suspend fun getSeries(page: Int = 0): List<Series> {
2418
return withContext(Dispatchers.IO) {
2519
try {
2620
val url = "$BASE_URL/$page/$API_KEY"
27-
val request = Request.Builder()
28-
.url(url)
29-
.build()
3021

31-
client.newCall(request).execute().use { response ->
32-
if (!response.isSuccessful) {
33-
throw Exception("Failed to fetch series: ${response.code}")
34-
}
35-
36-
val jsonData = response.body?.string()
37-
?: throw Exception("Empty response body")
38-
39-
parseSeries(jsonData)
40-
}
22+
val jsonData = executeRequest(url) { Request.Builder().url(it).build() }
23+
24+
parseSeries(jsonData)
4125
} catch (e: Exception) {
4226
throw Exception("Error fetching series: ${e.message}")
4327
}

app/src/main/java/com/pira/ccloud/navigation/AppScreens.kt

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ package com.pira.ccloud.navigation
22

33
import androidx.annotation.StringRes
44
import androidx.compose.material.icons.Icons
5+
import androidx.compose.material.icons.filled.Favorite
56
import androidx.compose.material.icons.filled.Info
67
import androidx.compose.material.icons.filled.Movie
78
import androidx.compose.material.icons.filled.Search
@@ -65,8 +66,9 @@ sealed class AppScreens(
6566
data object Favorites : AppScreens(
6667
route = "favorites",
6768
resourceId = R.string.favorites,
69+
icon = Icons.Default.Favorite,
6870
showBottomBar = false,
69-
showSidebar = false
71+
showSidebar = true
7072
)
7173

7274
data object About : AppScreens(
@@ -78,6 +80,6 @@ sealed class AppScreens(
7880
)
7981

8082
companion object {
81-
val screens = listOf(Movies, Series, Search, Settings)
83+
val screens = listOf(Movies, Series, Search, Favorites, Settings)
8284
}
8385
}

app/src/main/java/com/pira/ccloud/navigation/BottomNavigation.kt

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -41,7 +41,7 @@ fun BottomNavigationBar(navController: NavController) {
4141
containerColor = androidx.compose.material3.MaterialTheme.colorScheme.surface,
4242
tonalElevation = 3.dp
4343
) {
44-
AppScreens.screens.forEach { screen ->
44+
AppScreens.screens.filter { it.showBottomBar }.forEach { screen ->
4545
val isSelected = currentRoute == screen.route
4646
val scale by animateFloatAsState(
4747
targetValue = if (isSelected) 1.1f else 1f,

0 commit comments

Comments
 (0)