diff --git a/.directory b/.directory
new file mode 100644
index 0000000..46de82d
--- /dev/null
+++ b/.directory
@@ -0,0 +1,6 @@
+[Dolphin]
+Timestamp=2022,1,4,18,4,55.221
+Version=4
+
+[Settings]
+HiddenFilesShown=true
diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml
new file mode 100644
index 0000000..7643783
--- /dev/null
+++ b/.idea/codeStyles/Project.xml
@@ -0,0 +1,123 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ xmlns:android
+
+ ^$
+
+
+
+
+
+
+
+
+ xmlns:.*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*:id
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ .*:name
+
+ http://schemas.android.com/apk/res/android
+
+
+
+
+
+
+
+
+ name
+
+ ^$
+
+
+
+
+
+
+
+
+ style
+
+ ^$
+
+
+
+
+
+
+
+
+ .*
+
+ ^$
+
+
+ BY_NAME
+
+
+
+
+
+
+ .*
+
+ http://schemas.android.com/apk/res/android
+
+
+ ANDROID_ATTRIBUTE_ORDER
+
+
+
+
+
+
+ .*
+
+ .*
+
+
+ BY_NAME
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/codeStyles/codeStyleConfig.xml b/.idea/codeStyles/codeStyleConfig.xml
new file mode 100644
index 0000000..79ee123
--- /dev/null
+++ b/.idea/codeStyles/codeStyleConfig.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/.idea/deploymentTargetDropDown.xml b/.idea/deploymentTargetDropDown.xml
index 76aae83..a55641d 100644
--- a/.idea/deploymentTargetDropDown.xml
+++ b/.idea/deploymentTargetDropDown.xml
@@ -1,18 +1,18 @@
-
+
-
+
-
-
+
+
-
-
+
+
diff --git a/.idea/gradle.xml b/.idea/gradle.xml
index 526b4c2..a2d7c21 100644
--- a/.idea/gradle.xml
+++ b/.idea/gradle.xml
@@ -13,7 +13,6 @@
-
diff --git a/.idea/misc.xml b/.idea/misc.xml
index 5197594..a43e999 100644
--- a/.idea/misc.xml
+++ b/.idea/misc.xml
@@ -51,6 +51,43 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/app/build.gradle b/app/build.gradle
index 2f7de43..b6ba446 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -8,7 +8,7 @@ plugins {
android {
- compileSdk 31
+ compileSdk 33
defaultConfig {
applicationId "eu.toldi.balazs.caster"
@@ -82,6 +82,7 @@ dependencies {
implementation 'com.google.android.material:material:1.6.1'
implementation "androidx.compose.ui:ui:$compose_version"
implementation "androidx.compose.material:material:$compose_version"
+ implementation "androidx.compose.material3:material3:1.0.0-beta01"
implementation "androidx.compose.ui:ui-tooling-preview:$compose_version"
implementation "androidx.compose.material:material-icons-extended:$compose_version"
implementation "androidx.compose.runtime:runtime-livedata:$compose_version"
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 95a1393..c2d88e0 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -10,13 +10,18 @@
+
+
+
+
+
+
+
@@ -42,11 +53,10 @@
android:name=".ChromecastManagerActivity"
android:exported="true"
android:label="@string/title_activity_chromecast_manager"
- android:theme="@style/Theme.Caster.NoActionBar" >
+ android:theme="@style/Theme.Caster.NoActionBar">
-
+
-
-
+
+
-
\ No newline at end of file
+
diff --git a/app/src/main/ic_launcher_adaptive-playstore.png b/app/src/main/ic_launcher_adaptive-playstore.png
new file mode 100644
index 0000000..23b9459
Binary files /dev/null and b/app/src/main/ic_launcher_adaptive-playstore.png differ
diff --git a/app/src/main/ic_new_caster-playstore.png b/app/src/main/ic_new_caster-playstore.png
new file mode 100644
index 0000000..3075d78
Binary files /dev/null and b/app/src/main/ic_new_caster-playstore.png differ
diff --git a/app/src/main/ic_new_caster_monochrome-playstore.png b/app/src/main/ic_new_caster_monochrome-playstore.png
new file mode 100644
index 0000000..3075d78
Binary files /dev/null and b/app/src/main/ic_new_caster_monochrome-playstore.png differ
diff --git a/app/src/main/java/eu/toldi/balazs/caster/ChromeCastHelper.kt b/app/src/main/java/eu/toldi/balazs/caster/ChromeCastHelper.kt
index ea89933..b7a61fd 100644
--- a/app/src/main/java/eu/toldi/balazs/caster/ChromeCastHelper.kt
+++ b/app/src/main/java/eu/toldi/balazs/caster/ChromeCastHelper.kt
@@ -132,6 +132,7 @@ object ChromeCastHelper {
}
if (_streamInfo == null) {
exitStatus = false
+ Log.e("Caster_ChromecastHelper","Failed to fetch stream!")
callBack.invoke()
} else {
try {
diff --git a/app/src/main/java/eu/toldi/balazs/caster/ChromecastManagerActivity.kt b/app/src/main/java/eu/toldi/balazs/caster/ChromecastManagerActivity.kt
index 0aced4e..578e1c7 100644
--- a/app/src/main/java/eu/toldi/balazs/caster/ChromecastManagerActivity.kt
+++ b/app/src/main/java/eu/toldi/balazs/caster/ChromecastManagerActivity.kt
@@ -1,33 +1,43 @@
package eu.toldi.balazs.caster
-import android.annotation.SuppressLint
+import android.content.Context
import android.content.Intent
-import android.database.Cursor
+import android.content.SharedPreferences
import android.net.Uri
import android.os.Build
import android.os.Bundle
-import android.provider.OpenableColumns
import android.util.Log
import android.view.KeyEvent
-import android.webkit.MimeTypeMap
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.RequiresApi
import androidx.compose.foundation.Image
+import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.*
+import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.*
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.*
+import androidx.compose.material3.ColorScheme
import androidx.compose.runtime.*
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.graphics.Color
+import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
import androidx.compose.ui.unit.dp
import androidx.core.content.ContextCompat
+import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModelProvider
+import androidx.navigation.NavHostController
+import androidx.navigation.compose.NavHost
+import androidx.navigation.compose.composable
+import androidx.navigation.compose.currentBackStackEntryAsState
+import androidx.navigation.compose.rememberNavController
+import coil.compose.AsyncImage
import coil.compose.rememberImagePainter
import com.yausername.ffmpeg.FFmpeg
import com.yausername.youtubedl_android.YoutubeDL
@@ -36,25 +46,111 @@ import eu.toldi.balazs.caster.helpers.FileCacheHelper
import eu.toldi.balazs.caster.model.ChromecastManageViewmodel
import eu.toldi.balazs.caster.services.ChromecastManagerService
import eu.toldi.balazs.caster.ui.theme.CasterTheme
+import eu.toldi.balazs.caster.ui.theme.getColorScheme
import su.litvak.chromecast.api.v2.ChromeCast
import su.litvak.chromecast.api.v2.Media
import su.litvak.chromecast.api.v2.MediaStatus
-import java.io.BufferedInputStream
-import java.io.BufferedOutputStream
-import java.io.FileOutputStream
+import java.io.File
+import java.util.regex.Matcher
+import java.util.regex.Pattern
+data class BottomNavItem(
+ val label: String,
+ val icon: ImageVector,
+ val route: String,
+)
+
+object Constants {
+ val BottomNavItems = listOf(
+ BottomNavItem(
+ label = "Home",
+ icon = Icons.Filled.Home,
+ route = "home"
+ ),
+ BottomNavItem(
+ label = "Gallery",
+ icon = Icons.Filled.Image,
+ route = "gallery"
+ ),
+ BottomNavItem(
+ label = "Settings",
+ icon = Icons.Filled.Settings,
+ route = "settings"
+ )
+ )
+
+}
+
class ChromecastManagerActivity : ComponentActivity() {
companion object {
var chromeCast_: ChromeCast? = null
const val METADATA_THUMB = "thumb"
}
+ val pattern =
+ "https?:\\/\\/(?:[0-9A-Z-]+\\.)?(?:youtu\\.be\\/|youtube\\.com\\S*[^\\w\\-\\s])([\\w\\-]{11})(?=[^\\w\\-]|$)(?![?=&+%\\w]*(?:['\"][^<>]*>|<\\/a>))[?=&+%\\w]*"
+
+ val compiledPattern: Pattern =
+ Pattern.compile(pattern, Pattern.CASE_INSENSITIVE)
+
+ lateinit var colorScheme: ColorScheme
+ lateinit var prefs: SharedPreferences
+
+ @Composable
+ fun BottomNavigationBar(navController: NavHostController) {
+
+ BottomNavigation(
+ backgroundColor = colorScheme.secondary,
+ contentColor = colorScheme.onSecondary
+ ) {
+ // observe the backstack
+ val navBackStackEntry by navController.currentBackStackEntryAsState()
+
+ // observe current route to change the icon
+ // color,label color when navigated
+ val currentRoute = navBackStackEntry?.destination?.route
+
+ // Bottom nav items we declared
+ Constants.BottomNavItems.forEach { navItem ->
+
+ // Place the bottom nav items
+ BottomNavigationItem(
+
+ // it currentRoute is equal then its selected route
+ selected = currentRoute == navItem.route,
+
+ // navigate on click
+ onClick = {
+ navController.navigate(navItem.route)
+ },
+
+ // Icon of navItem
+ icon = {
+ Icon(imageVector = navItem.icon, contentDescription = navItem.label)
+ },
+
+ // label
+ label = {
+ Text(text = navItem.label)
+ },
+ alwaysShowLabel = false
+ )
+ }
+ }
+ }
+
private lateinit var chromeCast: ChromeCast
- private lateinit var viewModel : ChromecastManageViewmodel
+ private lateinit var viewModel: ChromecastManageViewmodel
+
+
@RequiresApi(Build.VERSION_CODES.O)
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
+
+ prefs = getSharedPreferences(
+ "eu.toldi.Caster", Context.MODE_PRIVATE
+ )
if (chromeCast_ == null)
finish()
chromeCast = chromeCast_ as ChromeCast
@@ -73,56 +169,74 @@ class ChromecastManagerActivity : ComponentActivity() {
}
setContent {
CasterTheme {
- viewModel = ViewModelProvider(this).get(ChromecastManageViewmodel::class.java)
- viewModel.chromeCast = chromeCast
- // A surface container using the 'background' color from the theme
- Surface(color = MaterialTheme.colors.background) {
- Column {
- //viewModel.fetchMediaStatus()
- MenuBar()
- Column(
- modifier = Modifier
- .padding(6.dp)
- .fillMaxWidth()
- .fillMaxHeight(),
- horizontalAlignment = Alignment.CenterHorizontally
- ) {
- var text by remember { mutableStateOf("") }
+ colorScheme = getColorScheme()
+ // remember navController so it does not
+ // get recreated on recomposition
+ val navController = rememberNavController()
- OutlinedTextField(
- value = text,
- onValueChange = {
- text = it
- },
- label = { Text(stringResource(id = R.string.cast_url)) },
- modifier = Modifier.padding(vertical = 4.dp)
- )
- var castEnabled by remember { mutableStateOf(true) }
- Button(
- enabled = castEnabled,
- onClick = {
- castEnabled = false
- viewModel.castLink(text) {
- castEnabled = true
- }
- },
- modifier = Modifier.padding(vertical = 4.dp)
- ) {
- Text(text = stringResource(id = R.string.cast))
- }
- if (castEnabled.not()) {
- CircularProgressIndicator()
- }
- PlayBackControl()
- mediaStatus()
+ Surface(color = colorScheme.background, contentColor = colorScheme.onBackground) {
+
+ // Scaffold Component
+ Scaffold(
+ backgroundColor = colorScheme.background,
+ contentColor = colorScheme.onBackground,
+ // Bottom navigation
+ bottomBar = {
+ BottomNavigationBar(navController = navController)
+ },
+ content = { padding ->
+ // Navhost: where screens are placed
+ NavHostContainer(navController = navController, padding = padding)
}
- }
+ )
}
}
+
}
}
+ @Composable
+ fun NavHostContainer(
+ navController: NavHostController,
+ padding: PaddingValues
+ ) {
+
+ NavHost(
+ navController = navController,
+
+ // set the start destination as home
+ startDestination = "home",
+
+ // Set the padding provided by scaffold
+ modifier = Modifier.padding(paddingValues = padding),
+
+ builder = {
+
+ // route : Home
+ composable("home") {
+ Column {
+ MenuBar()
+ HomeScreen()
+ }
+ }
+
+ // route : search
+ composable("gallery") {
+ Column {
+ MenuBar()
+ ImageScreen()
+ }
+ }
+
+ // route : profile
+ composable("settings") {
+ SettingsScreen()
+ }
+ })
+
+ }
+
@Composable
fun mediaStatus() {
val mediaStatus = viewModel.mediaStatus.value
@@ -157,13 +271,13 @@ class ChromecastManagerActivity : ComponentActivity() {
val image =
mediaStatus.media.metadata[METADATA_THUMB] as String
Log.e("Caster", image)
- Image(
- painter = rememberImagePainter(image),
+ AsyncImage(
+ model = image,
contentDescription = null,
modifier = Modifier.fillMaxWidth()
)
}
- if(mediaStatus.media.contentType.startsWith("video/")) {
+ if (mediaStatus.media.contentType.startsWith("video/")) {
Row(
Modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceBetween
@@ -198,13 +312,11 @@ class ChromecastManagerActivity : ComponentActivity() {
@Composable
fun MenuBar() {
val pickPictureLauncher = rememberLauncherForActivityResult(
- ActivityResultContracts.OpenDocument()
+ ActivityResultContracts.OpenDocumentTree()
) { fileUri ->
if (fileUri != null) {
- val cacheHelper = FileCacheHelper(applicationContext)
- cacheHelper.cacheThis(listOf(fileUri))
- viewModel.castFromCache(cacheHelper.tryFileName(fileUri))
-
+ DocumentFile.fromTreeUri(applicationContext, fileUri)
+ ?.let { viewModel.addFolder(it) }
}
}
TopAppBar(
@@ -222,7 +334,7 @@ class ChromecastManagerActivity : ComponentActivity() {
actions = {
Row {
IconButton(onClick = {
- pickPictureLauncher.launch(arrayOf("image/*", "video/*"))
+ pickPictureLauncher.launch(Uri.EMPTY)
}) {
Icon(
Icons.Filled.Folder,
@@ -230,7 +342,9 @@ class ChromecastManagerActivity : ComponentActivity() {
)
}
}
- }
+ },
+ backgroundColor = colorScheme.primary,
+ contentColor = colorScheme.onPrimary
)
}
@@ -361,4 +475,172 @@ class ChromecastManagerActivity : ComponentActivity() {
}
return super.onKeyDown(keyCode, event)
}
+
+ @Composable
+ fun HomeScreen() {
+ // Column Composable,
+ Column(
+ modifier = Modifier
+ .fillMaxSize(),
+ // Parameters set to place the items in center
+ horizontalAlignment = Alignment.CenterHorizontally,
+ verticalArrangement = Arrangement.Center
+ ) {
+ viewModel =
+ ViewModelProvider(this@ChromecastManagerActivity).get(ChromecastManageViewmodel::class.java)
+ viewModel.chromeCast = chromeCast
+ // A surface container using the 'background' color from the theme
+ //viewModel.fetchMediaStatus()
+
+ Column(
+ modifier = Modifier
+ .padding(6.dp)
+ .fillMaxWidth()
+ .fillMaxHeight(),
+ horizontalAlignment = Alignment.CenterHorizontally
+ ) {
+ var text by remember { mutableStateOf("") }
+
+ OutlinedTextField(
+ value = text,
+ onValueChange = {
+ text = it
+ },
+ label = { Text(stringResource(id = R.string.cast_url)) },
+ modifier = Modifier.padding(vertical = 4.dp),
+ colors = TextFieldDefaults.outlinedTextFieldColors(
+ cursorColor = colorScheme.primary,
+ focusedBorderColor =
+ colorScheme.primary.copy(alpha = ContentAlpha.high),
+ focusedLabelColor = colorScheme.primary
+ )
+ )
+ var castEnabled by remember { mutableStateOf(true) }
+ Button(
+ enabled = castEnabled,
+ colors = ButtonDefaults.buttonColors(
+ backgroundColor = colorScheme.primary,
+ contentColor = colorScheme.onPrimary
+ ),
+ onClick = {
+ castEnabled = false
+ val link = text
+ viewModel.castLink(link) {
+ if (prefs.getBoolean("sponsorblock_enabled", false)) {
+ val matcher: Matcher = compiledPattern.matcher(link)
+ if (matcher.find()) {
+ val id = matcher.group(1)
+ Log.e("Caster_SB", id)
+ val intent = Intent(
+ applicationContext,
+ ChromecastManagerService::class.java
+ ).apply {
+ action = ChromecastManagerService.ACTION_SET_YT_ID
+ putExtra("yt_id", id)
+ putExtra("media_url", link)
+ }
+ application.startService(intent);
+ }
+ }
+ castEnabled = true
+
+ }
+ },
+ modifier = Modifier.padding(vertical = 4.dp)
+ ) {
+ Text(text = stringResource(id = R.string.cast))
+ }
+ if (castEnabled.not()) {
+ CircularProgressIndicator(color = colorScheme.secondary)
+ }
+ PlayBackControl()
+ mediaStatus()
+ }
+ }
+ }
+
+
+ @Composable
+ fun SettingsScreen() {
+ // Column Composable,
+ Column(
+ modifier = Modifier
+ .fillMaxSize(),
+ // parameters set to place the items in center
+ ) {
+ Row() {
+ Text("Enable SponsorBlock")
+ var switched by remember {
+ mutableStateOf(
+ prefs.getBoolean(
+ "sponsorblock_enabled",
+ false
+ )
+ )
+ }
+ Switch(checked = switched, onCheckedChange = {
+ switched = !switched
+ prefs.edit().putBoolean("sponsorblock_enabled", switched).apply()
+ })
+ }
+ }
+ }
+
+ @Composable
+ fun ImageScreen() {
+ // Column Composable,
+ Column(
+ modifier = Modifier
+ .fillMaxSize(),
+ ) {
+
+ val folders = viewModel.folderListState.value
+ val cacheHelper = FileCacheHelper(applicationContext)
+ LazyColumn() {
+ items(folders.size) { index ->
+ Card(modifier = Modifier
+ .fillMaxWidth()
+ .clickable {
+ val intent = Intent(applicationContext, FolderViewActivity::class.java)
+ FolderViewActivity.chromecastRecv = chromeCast
+ FolderViewActivity.folderRecv = folders[index]
+ startActivity(intent)
+ }) {
+ Row(
+ modifier = Modifier
+ .fillMaxSize(),
+ // parameters set to place the items in center
+ verticalAlignment = Alignment.CenterVertically
+ ) {
+ val image = folders[index].listFiles()
+ .firstOrNull {
+ it.name?.endsWith(".png") ?: false || it.name?.endsWith(
+ ".jpg"
+ ) ?: false
+ }
+ if (image != null) {
+ cacheHelper.cacheThis(listOf(image.uri))
+
+ val imageFile = File(cacheDir, cacheHelper.tryFileName(image.uri))
+ Column(
+ modifier = Modifier
+ .height(80.dp)
+ .width(80.dp)
+ .padding(10.dp)
+ ) {
+ Log.e(null, imageFile.name.toString())
+ Image(
+ painter = rememberImagePainter(imageFile),
+ contentDescription = null,
+ modifier = Modifier.fillMaxSize()
+ )
+ }
+ }
+ Text(text = folders[index].name!!)
+ }
+ }
+ }
+ }
+ }
+ }
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/toldi/balazs/caster/FolderViewActivity.kt b/app/src/main/java/eu/toldi/balazs/caster/FolderViewActivity.kt
new file mode 100644
index 0000000..846064d
--- /dev/null
+++ b/app/src/main/java/eu/toldi/balazs/caster/FolderViewActivity.kt
@@ -0,0 +1,201 @@
+package eu.toldi.balazs.caster
+
+import android.annotation.SuppressLint
+import android.os.Bundle
+import android.util.Log
+import androidx.activity.ComponentActivity
+import androidx.activity.compose.setContent
+import androidx.compose.foundation.lazy.LazyColumn
+import androidx.compose.material.*
+import androidx.compose.material.icons.Icons
+import androidx.compose.material.icons.filled.ArrowBack
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.Modifier
+import androidx.compose.foundation.Image
+import androidx.compose.foundation.layout.*
+import androidx.compose.ui.layout.ContentScale
+import androidx.compose.ui.res.stringResource
+import androidx.compose.ui.semantics.Role.Companion.Image
+import androidx.compose.ui.tooling.preview.Preview
+import androidx.compose.ui.unit.dp
+import androidx.documentfile.provider.DocumentFile
+import coil.compose.AsyncImage
+import coil.compose.SubcomposeAsyncImage
+import coil.compose.rememberImagePainter
+import coil.size.OriginalSize
+
+import coil.transform.CircleCropTransformation
+import com.google.accompanist.pager.ExperimentalPagerApi
+import com.google.accompanist.pager.HorizontalPager
+import com.google.accompanist.pager.rememberPagerState
+import eu.toldi.balazs.caster.helpers.FileCacheHelper
+import eu.toldi.balazs.caster.services.ChromecastManagerService
+import eu.toldi.balazs.caster.ui.theme.CasterTheme
+import kotlinx.coroutines.Dispatchers
+import kotlinx.coroutines.GlobalScope
+import kotlinx.coroutines.launch
+import su.litvak.chromecast.api.v2.ChromeCast
+import java.io.File
+
+class FolderViewActivity : ComponentActivity() {
+
+ companion object {
+ var chromecastRecv: ChromeCast? = null
+ var folderRecv: DocumentFile? = null
+ }
+
+ @SuppressLint("CoroutineCreationDuringComposition")
+ @OptIn(ExperimentalPagerApi::class)
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+ if (chromecastRecv == null || folderRecv == null) finish()
+ val chromeCast: ChromeCast = chromecastRecv!!
+ val folder: DocumentFile = folderRecv!!
+ val cacheHelper = FileCacheHelper(applicationContext)
+ setContent {
+ CasterTheme {
+ // A surface container using the 'background' color from the theme
+ Surface(color = MaterialTheme.colors.background) {
+ Column(modifier = Modifier.fillMaxSize()) {
+ MenuBar(title = folder.name!!)
+ val images = folder.listFiles()
+ .filter { it.name?.endsWith(".png") ?: false || it.name?.endsWith(".jpg") ?: false }
+ //cacheHelper.cacheThis(images.map { it.uri })
+ Log.e("CastFile", images.size.toString())
+ val row_count = images.size / 3 + 1
+ /*LazyColumn(modifier = Modifier.fillMaxWidth()) {
+ items(row_count) { index ->
+ Row(modifier = Modifier.fillMaxWidth()){
+ for (i in 0 until 3){
+ if(index+i >= images.size) break
+ val imageFile = if (images[index+i].exists()) {
+ cacheHelper.cacheThis(listOf(images[index+i].uri))
+ File(cacheDir, cacheHelper.tryFileName(images[index+i].uri))
+ } else {
+ File(cacheDir, cacheHelper.tryFileName(images[index+i].uri))
+ }
+ Card(modifier = Modifier
+ .padding(all = 5.dp)
+ .weight(1f)) {
+ Column {
+ Column(
+ modifier = Modifier
+ .height(100.dp)
+ .width(100.dp)
+ .padding(10.dp)
+ ) {
+ Image(
+ painter = rememberImagePainter(
+ data = imageFile
+
+ ),
+ contentDescription = null,
+ modifier = Modifier.fillMaxWidth()
+ )
+ }
+ //Text(imageFile.name)
+ }
+ }
+ }
+ }
+ }
+ }*/
+ val pagerState = rememberPagerState()
+ if (!pagerState.isScrollInProgress) {
+ val currentPage = pagerState.currentPage
+ Log.e("Caster_IMG", currentPage.toString())
+ GlobalScope.launch(Dispatchers.IO) {
+ Thread.sleep(1000)
+ if (pagerState.currentPage == currentPage && !pagerState.isScrollInProgress) {
+ val imageFile = if (images[currentPage].exists()) {
+ cacheHelper.cacheThis(listOf(images[currentPage].uri))
+ File(cacheDir, cacheHelper.tryFileName(images[currentPage].uri))
+ } else {
+ File(cacheDir, cacheHelper.tryFileName(images[currentPage].uri))
+ }
+ val link = "http:/${ChromeCastHelper.getIPv4Address()}:${ChromecastManagerService.PORT}/assets/${
+ imageFile.name
+ }"
+ Log.e("Caster_IMG","Casting image with url: $link")
+ val result = ChromeCastHelper.castLink(
+ link
+ ){ Log.e("Caster_IMG","Casting done")}
+ }
+ }
+ }
+ HorizontalPager(
+ modifier = Modifier.fillMaxSize(),
+ count = images.size,
+ state = pagerState
+
+ ) { index ->
+ val imageFile = if (images[index].exists()) {
+ cacheHelper.cacheThis(listOf(images[index].uri))
+ File(cacheDir, cacheHelper.tryFileName(images[index].uri))
+ } else {
+ File(cacheDir, cacheHelper.tryFileName(images[index].uri))
+ }
+
+ Card(
+ modifier = Modifier
+ .fillMaxWidth()
+ .padding(all = 5.dp)
+ ) {
+ Column(modifier = Modifier.fillMaxWidth()) {
+ Column(
+ modifier = Modifier
+ .fillMaxWidth()
+ .height(240.dp)
+ // .padding(10.dp)
+ ) {
+ Log.e(
+ "Caster_IMG",
+ "http:/${ChromeCastHelper.getIPv4Address()}:${ChromecastManagerService.PORT}/assets/${imageFile.name}"
+ )
+ SubcomposeAsyncImage(
+ model = imageFile,
+ contentDescription = imageFile.name,
+ loading = {
+ CircularProgressIndicator()
+ },
+ modifier = Modifier.fillMaxWidth()
+ )
+
+ }
+ Text(imageFile.name)
+ }
+
+ }
+ }
+ }
+ }
+ }
+ }
+ }
+
+ @Composable
+ fun MenuBar(title: String) {
+ TopAppBar(
+ title = {
+ Text(title)
+ },
+ navigationIcon = {
+ IconButton(onClick = { finish() }) {
+ Icon(
+ Icons.Filled.ArrowBack,
+ contentDescription = stringResource(id = R.string.back)
+ )
+ }
+ }
+ )
+ }
+
+ @Preview(showBackground = true)
+ @Composable
+ fun DefaultPreview() {
+ CasterTheme {
+
+ }
+ }
+}
+
diff --git a/app/src/main/java/eu/toldi/balazs/caster/MainActivity.kt b/app/src/main/java/eu/toldi/balazs/caster/MainActivity.kt
index 0f072f8..84e0d2e 100644
--- a/app/src/main/java/eu/toldi/balazs/caster/MainActivity.kt
+++ b/app/src/main/java/eu/toldi/balazs/caster/MainActivity.kt
@@ -2,24 +2,39 @@ package eu.toldi.balazs.caster
import android.annotation.SuppressLint
import android.content.Intent
+import android.graphics.ColorSpace.adapt
import android.net.wifi.WifiManager
import android.net.wifi.WifiManager.MulticastLock
+import android.os.Build
import android.os.Bundle
import android.util.Log
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.compose.foundation.Image
+import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
+import androidx.compose.foundation.isSystemInDarkTheme
import androidx.compose.foundation.layout.*
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.material.*
+import androidx.compose.material.Button
+import androidx.compose.material.ButtonColors
+import androidx.compose.material.ButtonDefaults
+import androidx.compose.material.Card
+import androidx.compose.material.CircularProgressIndicator
+import androidx.compose.material.Icon
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material.OutlinedTextField
+import androidx.compose.material.Text
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Add
import androidx.compose.material.icons.filled.Refresh
+import androidx.compose.material3.*
import androidx.compose.runtime.*
import androidx.compose.runtime.livedata.observeAsState
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
+import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.tooling.preview.Preview
@@ -29,14 +44,17 @@ import com.google.accompanist.swiperefresh.SwipeRefresh
import com.google.accompanist.swiperefresh.rememberSwipeRefreshState
import eu.toldi.balazs.caster.model.ChromeCastViewModel
import eu.toldi.balazs.caster.ui.theme.CasterTheme
+import eu.toldi.balazs.caster.ui.theme.getColorScheme
import su.litvak.chromecast.api.v2.ChromeCast
+
+
open class MainActivity : ComponentActivity() {
protected lateinit var viewModel: ChromeCastViewModel
protected var multicastLock: MulticastLock? = null
-
+ protected lateinit var colorScheme: ColorScheme
protected fun isViewModelInitialised() = ::viewModel.isInitialized
override fun onStart() {
@@ -67,13 +85,13 @@ open class MainActivity : ComponentActivity() {
setContent {
CasterTheme {
-
+ colorScheme = getColorScheme()
viewModel = ViewModelProvider(this).get(ChromeCastViewModel::class.java)
val chromeCastState = viewModel.chromeCasts.observeAsState(initial = emptyList())
val chromeCasts = chromeCastState.value
Log.e(null, chromeCasts.toString())
// A surface container using the 'background' color from the theme
- Surface(color = MaterialTheme.colors.background) {
+ Surface(color = colorScheme.background, contentColor = colorScheme.onBackground) {
Column(modifier = Modifier
.fillMaxWidth()
.fillMaxHeight()) {
@@ -123,14 +141,14 @@ open class MainActivity : ComponentActivity() {
) {
item {
if (chromeCasts.isNotEmpty())
- Text(text = stringResource(id = R.string.available_chromecasts))
+ Text(text = stringResource(id = R.string.available_chromecasts),color =colorScheme.onBackground)
else {
Column(
modifier = Modifier.fillMaxSize(),
horizontalAlignment = Alignment.CenterHorizontally
) {
- Text(stringResource(id = R.string.looking_for_devices))
- CircularProgressIndicator()
+ Text(text = stringResource(id = R.string.looking_for_devices),color= colorScheme.onBackground)
+ CircularProgressIndicator(color = colorScheme.secondary)
}
}
}
@@ -152,7 +170,7 @@ open class MainActivity : ComponentActivity() {
}
AlertDialog(onDismissRequest = dismiss,
title = {
- Text(text = stringResource(id = R.string.add_chromecast))
+ Text(text = stringResource(id = R.string.add_chromecast),color= colorScheme.onBackground)
},
text = {
OutlinedTextField(
@@ -160,7 +178,7 @@ open class MainActivity : ComponentActivity() {
onValueChange = {
ipaddress = it
},
- label = { Text(stringResource(id = R.string.ip_address)) },
+ label = { Text(stringResource(id = R.string.ip_address),color= colorScheme.onBackground) },
modifier = Modifier.padding(vertical = 4.dp)
)
}, buttons = {
@@ -173,11 +191,16 @@ open class MainActivity : ComponentActivity() {
add(ipaddress)
dismiss()
},
- modifier = Modifier.padding(all = 8.dp)
+ colors = ButtonDefaults.buttonColors(backgroundColor = colorScheme.primary, contentColor = colorScheme.onPrimary),
+ modifier = Modifier.padding(all = 8.dp),
+
) {
Text(text = stringResource(id = R.string.add_chromecast))
}
- Button(onClick = dismiss, modifier = Modifier.padding(all = 8.dp)) {
+ Button(
+ onClick = dismiss,
+ colors = ButtonDefaults.buttonColors(backgroundColor = colorScheme.primary, contentColor = colorScheme.onPrimary),
+ modifier = Modifier.padding(all = 8.dp)) {
Text(stringResource(id = R.string.cancel))
}
}
@@ -221,7 +244,9 @@ open class MainActivity : ComponentActivity() {
)
}
- Column(modifier = Modifier.fillMaxWidth().height(80.dp),
+ Column(modifier = Modifier
+ .fillMaxWidth()
+ .height(80.dp),
verticalArrangement = Arrangement.Center) {
Text(
text = "Name: " + when {
@@ -265,7 +290,9 @@ open class MainActivity : ComponentActivity() {
)
}
}
- }
+ },
+ backgroundColor = colorScheme.primary,
+ contentColor = colorScheme.onPrimary
)
}
diff --git a/app/src/main/java/eu/toldi/balazs/caster/model/ChromecastManageViewmodel.kt b/app/src/main/java/eu/toldi/balazs/caster/model/ChromecastManageViewmodel.kt
index 2850bb1..95950f2 100644
--- a/app/src/main/java/eu/toldi/balazs/caster/model/ChromecastManageViewmodel.kt
+++ b/app/src/main/java/eu/toldi/balazs/caster/model/ChromecastManageViewmodel.kt
@@ -4,6 +4,7 @@ import android.net.Uri
import android.util.Log
import androidx.compose.runtime.State
import androidx.compose.runtime.mutableStateOf
+import androidx.documentfile.provider.DocumentFile
import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import eu.toldi.balazs.caster.ChromeCastHelper
@@ -20,6 +21,9 @@ class ChromecastManageViewmodel : ViewModel() {
private val _mediaState = mutableStateOf(null)
val mediaStatus: State
get() = _mediaState
+ private val _folderListState = mutableStateOf>(listOf())
+ val folderListState : State>
+ get() = _folderListState
lateinit var chromeCast: ChromeCast
@@ -107,6 +111,12 @@ class ChromecastManageViewmodel : ViewModel() {
}
}
+ fun addFolder(folder: DocumentFile){
+ if(folder.isDirectory){
+ _folderListState.value += folder
+ }
+ }
+
fun getIPv4Address(): InetAddress? {
NetworkInterface.getNetworkInterfaces().toList().forEach { interf ->
interf.inetAddresses.toList().forEach { inetAddress ->
diff --git a/app/src/main/java/eu/toldi/balazs/caster/model/SponsorBlock.kt b/app/src/main/java/eu/toldi/balazs/caster/model/SponsorBlock.kt
new file mode 100644
index 0000000..ed41f41
--- /dev/null
+++ b/app/src/main/java/eu/toldi/balazs/caster/model/SponsorBlock.kt
@@ -0,0 +1,32 @@
+package eu.toldi.balazs.caster.model
+
+import com.beust.klaxon.*
+import java.net.URL
+
+private val klaxon = Klaxon()
+
+class SponsorBlock(elements: Collection) : ArrayList(elements) {
+ fun toJson() = klaxon.toJsonString(this)
+
+
+ companion object {
+ fun fromJson(json: String) = SponsorBlock(klaxon.parseArray(json)!!)
+ fun fromID(id: String) = SponsorBlock(klaxon.parseArray(URL(url+id).readText())!!)
+ private const val url = "https://sponsor.ajay.app/api/skipSegments?videoID="
+ }
+}
+
+data class SponsorBlockElement (
+ val category: String,
+ val actionType: String,
+ val segment: List,
+
+ @Json(name = "UUID")
+ val uuid: String,
+
+ val locked: Long,
+ val votes: Long,
+ val videoDuration: Double,
+ val userID: String,
+ val description: String
+)
\ No newline at end of file
diff --git a/app/src/main/java/eu/toldi/balazs/caster/services/ChromecastManagerService.kt b/app/src/main/java/eu/toldi/balazs/caster/services/ChromecastManagerService.kt
index 90db981..26b0335 100644
--- a/app/src/main/java/eu/toldi/balazs/caster/services/ChromecastManagerService.kt
+++ b/app/src/main/java/eu/toldi/balazs/caster/services/ChromecastManagerService.kt
@@ -4,6 +4,7 @@ import android.app.Notification
import android.app.NotificationManager
import android.app.PendingIntent
import android.app.Service
+import android.content.Context
import android.content.Intent
import android.media.MediaPlayer
import android.os.Build
@@ -15,7 +16,9 @@ import androidx.annotation.RequiresApi
import androidx.core.app.NotificationCompat
import eu.toldi.balazs.caster.App.Companion.CHANNEL_ID
import eu.toldi.balazs.caster.ChromeCastHelper
+import eu.toldi.balazs.caster.ChromecastManagerActivity
import eu.toldi.balazs.caster.R
+import eu.toldi.balazs.caster.model.SponsorBlock
import io.ktor.application.*
import io.ktor.features.*
import io.ktor.http.*
@@ -40,6 +43,7 @@ class ChromecastManagerService : Service() {
const val ACTION_PAUSE = "action_pause"
const val ACTION_REWIND = "action_rewind"
const val ACTION_FAST_FORWARD = "action_fast_foward"
+ const val ACTION_SET_YT_ID = "action_set_yt_id"
const val ACTION_NEXT = "action_next"
const val ACTION_PREVIOUS = "action_previous"
const val ACTION_STOP = "action_stop"
@@ -54,6 +58,7 @@ class ChromecastManagerService : Service() {
private lateinit var pendingIntent : PendingIntent
private var mediaStatus: MediaStatus? = null
private var file : File? = null
+ private var yt_video :YoutubeVideo? = null
override fun onBind(p0: Intent?): IBinder? = null
private lateinit var chromeCast: ChromeCast
@@ -89,11 +94,25 @@ class ChromecastManagerService : Service() {
ACTION_PAUSE -> mController.transportControls.pause()
ACTION_FAST_FORWARD -> mController.transportControls.fastForward()
ACTION_REWIND -> mController.transportControls.rewind()
+ ACTION_SET_YT_ID -> setYTID(intent)
}
return START_REDELIVER_INTENT
}
+ private fun setYTID(intent: Intent) {
+ CoroutineScope(IO).launch {
+ try {
+ val id = intent.getStringExtra("yt_id")!!
+ val url = intent.getStringExtra("media_url")!!
+ yt_video = YoutubeVideo(id,url, SponsorBlock.fromID(id))
+ Log.e(null,yt_video.toString())
+ }catch (e: Exception) {
+ Log.e("CasterService",e.stackTraceToString())
+ }
+ }
+ }
+
private fun initAction(intent: Intent?){
initMediaSessions()
@@ -104,42 +123,91 @@ class ChromecastManagerService : Service() {
Log.e(null, address)
chromeCast = ChromeCast(address)
chromeCast.name = name
+ ChromecastManagerActivity.chromeCast_ = chromeCast
val notificationIntent = Intent(this, ChromecastManagerService::class.java)
pendingIntent =
- PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_MUTABLE)
+ PendingIntent.getActivity(this, 0, notificationIntent, PendingIntent.FLAG_IMMUTABLE)
val notification: Notification = NotificationCompat.Builder(this, CHANNEL_ID)
.setContentTitle("Caster for ${chromeCast.name}@${address}")
- .setSmallIcon(R.drawable.ic_caster_logo)
+ .setSmallIcon(R.drawable.ic_new_caster_monochrome_foreground)
.setContentIntent(pendingIntent).build()
startForeground(1, notification)
CoroutineScope(IO).launch {
- server.start(wait = true)
+ var started = false
+ while (!started) {
+ try {
+ server.start(wait = true)
+ started = true
+ Thread.sleep(1000)
+ } catch (e: Exception) {
+ Log.e(null,e.stackTraceToString())
+ }
+ }
}
+ val prefs = getSharedPreferences(
+ "eu.toldi.Caster", Context.MODE_PRIVATE)
CoroutineScope(IO).launch {
-
+ var errorCount = 0
while (true) {
try {
ChromeCastHelper.chromeCast = chromeCast
var status = chromeCast.status
Log.d(null, status.applications.toString())
-
+ if(status.runningApp.id == ChromeCastHelper.APP_BACKDROP)
+ errorCount++
val status_message = if (status.runningApp != null) {
ChromeCastHelper.getApplicationDisplayName(status.runningApp.id)
} else {
"${chromeCast.name} is ready for casting"
}
mediaStatus = ChromeCastHelper.fetchMediaStatus()
+ Log.e("Caster_Service","Sb enabled: "+prefs.getBoolean("sponsorblock_enabled",false).toString()+ " yt: ${yt_video.toString()}")
+ if(prefs.getBoolean("sponsorblock_enabled",false) && yt_video != null){
+ try {
+ if(yt_video!!.url.startsWith("https://youtu.be/")){
+ yt_video!!.url = mediaStatus!!.media.url
+ }
+ if (mediaStatus!!.media.url != yt_video!!.url) {
+ yt_video = null
+ }
+ checkForSponsorBlock()
+ } catch (e:Exception){
+ //yt_video = null
+ }
+ }else {
+ yt_video = null
+ }
+
+ errorCount = 0
buildNotification()
} catch (e: Exception) {
Log.e(null,e.stackTraceToString())
+ errorCount++
}
- Thread.sleep(10500)
+ if(errorCount > 120){
+ stopForeground(true)
+ break
+ }
+ Thread.sleep(1000)
}
}
}
+ private fun checkForSponsorBlock() {
+ try {
+
+
+ yt_video!!.sponsorBlock.forEach {
+ if (mediaStatus!!.currentTime >= it.segment[0] && mediaStatus!!.currentTime <= it.segment[1])
+ chromeCast.seek(it.segment[1])
+ }
+ }catch (e: Exception) {
+ Log.e(null,e.stackTraceToString())
+ }
+ }
+
private fun generateAction(
icon: Int,
title: String,
@@ -147,7 +215,7 @@ class ChromecastManagerService : Service() {
): NotificationCompat.Action {
val intent = Intent(applicationContext, ChromecastManagerService::class.java)
intent.action = intentAction
- val pendingIntent = PendingIntent.getService(applicationContext, 1, intent, 0)
+ val pendingIntent = PendingIntent.getService(applicationContext, 1, intent, PendingIntent.FLAG_IMMUTABLE)
return NotificationCompat.Action(icon,title,pendingIntent)
}
@@ -313,4 +381,10 @@ class ChromecastManagerService : Service() {
}
)
}
+
+ data class YoutubeVideo(
+ val id: String,
+ var url: String,
+ val sponsorBlock: SponsorBlock
+ )
}
\ No newline at end of file
diff --git a/app/src/main/java/eu/toldi/balazs/caster/ui/theme/ColorScheme.kt b/app/src/main/java/eu/toldi/balazs/caster/ui/theme/ColorScheme.kt
new file mode 100644
index 0000000..4f2478e
--- /dev/null
+++ b/app/src/main/java/eu/toldi/balazs/caster/ui/theme/ColorScheme.kt
@@ -0,0 +1,35 @@
+package eu.toldi.balazs.caster.ui.theme
+
+import android.os.Build
+import androidx.compose.foundation.isSystemInDarkTheme
+import androidx.compose.material.MaterialTheme
+import androidx.compose.material3.*
+
+import androidx.compose.runtime.Composable
+import androidx.compose.ui.platform.LocalContext
+
+@Composable
+fun getColorScheme(): ColorScheme {
+ val LightColorScheme = lightColorScheme(
+ primary = MaterialTheme.colors.primary,
+ secondary = MaterialTheme.colors.secondary,
+ tertiary = MaterialTheme.colors.secondaryVariant,
+ // error, primaryContainer, onSecondary, etc.
+ )
+ val DarkColorScheme = darkColorScheme(
+ primary = MaterialTheme.colors.primary,
+ secondary = MaterialTheme.colors.secondary,
+ tertiary = MaterialTheme.colors.secondaryVariant,
+ // error, primaryContainer, onSecondary, etc.
+ )
+
+ val darkTheme = isSystemInDarkTheme()
+
+ val dynamicColor = Build.VERSION.SDK_INT >= Build.VERSION_CODES.S
+ return when {
+ dynamicColor && darkTheme -> dynamicDarkColorScheme(LocalContext.current)
+ dynamicColor && !darkTheme -> dynamicLightColorScheme(LocalContext.current)
+ darkTheme -> DarkColorScheme
+ else -> LightColorScheme
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/res/drawable/ic_new_caster_foreground.xml b/app/src/main/res/drawable/ic_new_caster_foreground.xml
new file mode 100644
index 0000000..d7f2538
--- /dev/null
+++ b/app/src/main/res/drawable/ic_new_caster_foreground.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/drawable/ic_new_caster_monochrome_foreground.xml b/app/src/main/res/drawable/ic_new_caster_monochrome_foreground.xml
new file mode 100644
index 0000000..0ec7927
--- /dev/null
+++ b/app/src/main/res/drawable/ic_new_caster_monochrome_foreground.xml
@@ -0,0 +1,53 @@
+
+
+
+
+
+
+
+
+
+
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
deleted file mode 100644
index eca70cf..0000000
--- a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
+++ /dev/null
@@ -1,5 +0,0 @@
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_new_caster.xml b/app/src/main/res/mipmap-anydpi-v26/ic_new_caster.xml
new file mode 100644
index 0000000..12d6403
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_new_caster.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_new_caster_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_new_caster_round.xml
new file mode 100644
index 0000000..12d6403
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_new_caster_round.xml
@@ -0,0 +1,6 @@
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_new_caster.png b/app/src/main/res/mipmap-hdpi/ic_new_caster.png
new file mode 100644
index 0000000..d6e561f
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_new_caster.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_new_caster_round.png b/app/src/main/res/mipmap-hdpi/ic_new_caster_round.png
new file mode 100644
index 0000000..99e8096
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_new_caster_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_new_caster.png b/app/src/main/res/mipmap-mdpi/ic_new_caster.png
new file mode 100644
index 0000000..47a0b6a
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_new_caster.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_new_caster_round.png b/app/src/main/res/mipmap-mdpi/ic_new_caster_round.png
new file mode 100644
index 0000000..6f6914a
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_new_caster_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_new_caster.png b/app/src/main/res/mipmap-xhdpi/ic_new_caster.png
new file mode 100644
index 0000000..273aef9
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_new_caster.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_new_caster_round.png b/app/src/main/res/mipmap-xhdpi/ic_new_caster_round.png
new file mode 100644
index 0000000..6b11b18
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_new_caster_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_new_caster.png b/app/src/main/res/mipmap-xxhdpi/ic_new_caster.png
new file mode 100644
index 0000000..9ac071f
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_new_caster.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_new_caster_round.png b/app/src/main/res/mipmap-xxhdpi/ic_new_caster_round.png
new file mode 100644
index 0000000..7d61bec
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_new_caster_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_new_caster.png b/app/src/main/res/mipmap-xxxhdpi/ic_new_caster.png
new file mode 100644
index 0000000..14fb869
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_new_caster.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_new_caster_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_new_caster_round.png
new file mode 100644
index 0000000..b8f0c53
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_new_caster_round.png differ
diff --git a/app/src/main/res/values/ic_launcher_adaptive_background.xml b/app/src/main/res/values/ic_launcher_adaptive_background.xml
new file mode 100644
index 0000000..88dcf97
--- /dev/null
+++ b/app/src/main/res/values/ic_launcher_adaptive_background.xml
@@ -0,0 +1,4 @@
+
+
+ #AAD400
+
\ No newline at end of file
diff --git a/app/src/main/res/values/ic_new_caster_background.xml b/app/src/main/res/values/ic_new_caster_background.xml
new file mode 100644
index 0000000..478e0e5
--- /dev/null
+++ b/app/src/main/res/values/ic_new_caster_background.xml
@@ -0,0 +1,4 @@
+
+
+ #AAD400
+
\ No newline at end of file
diff --git a/app/src/main/res/values/ic_new_caster_monochrome_background.xml b/app/src/main/res/values/ic_new_caster_monochrome_background.xml
new file mode 100644
index 0000000..cc62054
--- /dev/null
+++ b/app/src/main/res/values/ic_new_caster_monochrome_background.xml
@@ -0,0 +1,4 @@
+
+
+ #AAD400
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index f379844..b6c82ef 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -18,5 +18,6 @@
Back
Now playing
Cast URL
+ FolderViewActivity
\ No newline at end of file