A commit, that was needed...

This commit is contained in:
Balazs Toldi 2022-09-06 12:01:34 +02:00
parent d90c98a82f
commit 8746f39cde
Signed by: Bazsalanszky
GPG key ID: 6C7D440036F99D58
38 changed files with 1071 additions and 101 deletions

6
.directory Normal file
View file

@ -0,0 +1,6 @@
[Dolphin]
Timestamp=2022,1,4,18,4,55.221
Version=4
[Settings]
HiddenFilesShown=true

View file

@ -0,0 +1,123 @@
<component name="ProjectCodeStyleConfiguration">
<code_scheme name="Project" version="173">
<JetCodeStyleSettings>
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</JetCodeStyleSettings>
<codeStyleSettings language="XML">
<option name="FORCE_REARRANGE_MODE" value="1" />
<indentOptions>
<option name="CONTINUATION_INDENT_SIZE" value="4" />
</indentOptions>
<arrangement>
<rules>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:android</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>xmlns:.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:id</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*:name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>name</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>style</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>^$</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>http://schemas.android.com/apk/res/android</XML_NAMESPACE>
</AND>
</match>
<order>ANDROID_ATTRIBUTE_ORDER</order>
</rule>
</section>
<section>
<rule>
<match>
<AND>
<NAME>.*</NAME>
<XML_ATTRIBUTE />
<XML_NAMESPACE>.*</XML_NAMESPACE>
</AND>
</match>
<order>BY_NAME</order>
</rule>
</section>
</rules>
</arrangement>
</codeStyleSettings>
<codeStyleSettings language="kotlin">
<option name="CODE_STYLE_DEFAULTS" value="KOTLIN_OFFICIAL" />
</codeStyleSettings>
</code_scheme>
</component>

View file

@ -0,0 +1,5 @@
<component name="ProjectCodeStyleConfiguration">
<state>
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
</state>
</component>

View file

@ -1,18 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="deploymentTargetDropDown">
<targetSelectedWithDropDown>
<runningDeviceTargetSelectedWithDropDown>
<Target>
<type value="QUICK_BOOT_TARGET" />
<type value="RUNNING_DEVICE_TARGET" />
<deviceKey>
<Key>
<type value="VIRTUAL_DEVICE_PATH" />
<value value="$USER_HOME$/.android/avd/Pixel_3a_XL_API_30.avd" />
<type value="SERIAL_NUMBER" />
<value value="adb-a3950b57-giqMub._adb-tls-connect._tcp" />
</Key>
</deviceKey>
</Target>
</targetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2021-12-05T17:39:35.648801Z" />
</runningDeviceTargetSelectedWithDropDown>
<timeTargetWasSelectedWithDropDown value="2022-09-06T09:57:52.303689Z" />
<targetsSelectedWithDialog>
<Target>
<type value="QUICK_BOOT_TARGET" />

View file

@ -13,7 +13,6 @@
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>

View file

@ -51,6 +51,43 @@
<entry key="../../../../../layout/compose-model-1641222972102.xml" value="0.46296296296296297" />
<entry key="../../../../../layout/compose-model-1641224248444.xml" value="0.46296296296296297" />
<entry key="../../../../../layout/compose-model-1641226030069.xml" value="0.46296296296296297" />
<entry key="../../../../../layout/compose-model-1642254311605.xml" value="0.3015202702702703" />
<entry key="../../../../../layout/compose-model-1642259770760.xml" value="0.22592592592592592" />
<entry key="../../../../../layout/compose-model-1642262177120.xml" value="0.22870370370370371" />
<entry key="../../../../../layout/compose-model-1642342907711.xml" value="0.1" />
<entry key="../../../../../layout/compose-model-1642342992638.xml" value="0.30833333333333335" />
<entry key="../../../../../layout/compose-model-1642342997277.xml" value="0.3015202702702703" />
<entry key="../../../../../layout/compose-model-1642873166547.xml" value="0.1" />
<entry key="../../../../../layout/compose-model-1643299808590.xml" value="2.0" />
<entry key="../../../../../layout/compose-model-1643300369296.xml" value="1.0" />
<entry key="../../../../../layout/compose-model-1643319277393.xml" value="2.0" />
<entry key="../../../../../layout/compose-model-1643836978788.xml" value="2.0" />
<entry key="../../../../../layout/compose-model-1643837012793.xml" value="0.26296296296296295" />
<entry key="../../../../../layout/compose-model-1643992156800.xml" value="0.26296296296296295" />
<entry key="../../../../../layout/compose-model-1643993639504.xml" value="0.26296296296296295" />
<entry key="../../../../../layout/compose-model-1643994288560.xml" value="2.0" />
<entry key="../../../../../layout/compose-model-1643996450918.xml" value="0.16554054054054054" />
<entry key="../../../../../layout/compose-model-1656272626813.xml" value="2.0" />
<entry key="../../../../../layout/compose-model-1657531916246.xml" value="2.0" />
<entry key="../../../../../layout/compose-model-1657531926635.xml" value="0.26296296296296295" />
<entry key="../../../../../layout/compose-model-1657532602169.xml" value="1.0" />
<entry key="../../../../../layout/compose-model-1657534975618.xml" value="0.33" />
<entry key="../../../../../layout/compose-model-1657549343896.xml" value="0.1" />
<entry key="app/src/main/res/drawable-v24/ic_launcher_foreground.xml" value="0.1" />
<entry key="app/src/main/res/drawable/forward.xml" value="0.1" />
<entry key="app/src/main/res/drawable/ic_launcher_adaptive_foreground.xml" value="0.1" />
<entry key="app/src/main/res/drawable/ic_launcher_background.xml" value="0.1" />
<entry key="app/src/main/res/drawable/ic_new_caster_foreground.xml" value="0.1" />
<entry key="app/src/main/res/drawable/ic_new_caster_monochrome_foreground.xml" value="0.101" />
<entry key="app/src/main/res/drawable/pause.xml" value="0.1" />
<entry key="app/src/main/res/drawable/play.xml" value="0.1" />
<entry key="app/src/main/res/drawable/rewind.xml" value="0.1" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml" value="0.1" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher_adaptive.xml" value="0.101" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher_adaptive_round.xml" value="0.101" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml" value="0.101" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_new_caster.xml" value="0.1" />
<entry key="app/src/main/res/mipmap-anydpi-v26/ic_new_caster_round.xml" value="0.101" />
</map>
</option>
</component>

View file

@ -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"

View file

@ -10,13 +10,18 @@
<application
android:name=".App"
android:allowBackup="true"
android:icon="@drawable/ic_caster_logo"
android:label="@string/app_name"
android:roundIcon="@drawable/ic_caster_logo"
android:supportsRtl="true"
android:extractNativeLibs="true"
android:icon="@mipmap/ic_new_caster"
android:label="@string/app_name"
android:requestLegacyExternalStorage="true"
android:roundIcon="@mipmap/ic_new_caster"
android:supportsRtl="true"
android:theme="@style/Theme.Caster">
<activity
android:name=".FolderViewActivity"
android:exported="false"
android:label="@string/title_activity_folder_view"
android:theme="@style/Theme.Caster.NoActionBar" />
<activity
android:name=".ShareRecieverActivity"
android:exported="true"
@ -24,17 +29,23 @@
android:theme="@style/Theme.Caster.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="text/plain" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="image/*" />
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.SEND" />
<category android:name="android.intent.category.DEFAULT" />
<data android:mimeType="video/*" />
</intent-filter>
</activity>
@ -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">
<intent-filter>
<action android:name="android.intent.action.MEDIA_BUTTON"/>
<action android:name="android.intent.action.MEDIA_BUTTON" />
</intent-filter>
</activity>
<activity
android:name=".MainActivity"
@ -59,7 +69,8 @@
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
<service android:name=".services.ChromecastManagerService"/>
<service android:name=".services.ChromecastManagerService" />
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 12 KiB

View file

@ -132,6 +132,7 @@ object ChromeCastHelper {
}
if (_streamInfo == null) {
exitStatus = false
Log.e("Caster_ChromecastHelper","Failed to fetch stream!")
callBack.invoke()
} else {
try {

View file

@ -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,55 +169,73 @@ 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
Surface(color = colorScheme.background, contentColor = colorScheme.onBackground) {
// Scaffold Component
Scaffold(
backgroundColor = colorScheme.background,
contentColor = colorScheme.onBackground,
// Bottom navigation
bottomBar = {
BottomNavigationBar(navController = navController)
},
label = { Text(stringResource(id = R.string.cast_url)) },
modifier = Modifier.padding(vertical = 4.dp)
content = { padding ->
// Navhost: where screens are placed
NavHostContainer(navController = navController, padding = padding)
}
)
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()
}
}
}
}
}
}
}
}
@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() {
@ -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!!)
}
}
}
}
}
}
}

View file

@ -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 {
}
}
}

View file

@ -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
)
}

View file

@ -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<MediaStatus?>(null)
val mediaStatus: State<MediaStatus?>
get() = _mediaState
private val _folderListState = mutableStateOf<List<DocumentFile>>(listOf())
val folderListState : State<List<DocumentFile>>
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 ->

View file

@ -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<SponsorBlockElement>) : ArrayList<SponsorBlockElement>(elements) {
fun toJson() = klaxon.toJsonString(this)
companion object {
fun fromJson(json: String) = SponsorBlock(klaxon.parseArray<SponsorBlockElement>(json)!!)
fun fromID(id: String) = SponsorBlock(klaxon.parseArray<SponsorBlockElement>(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<Double>,
@Json(name = "UUID")
val uuid: String,
val locked: Long,
val votes: Long,
val videoDuration: Double,
val userID: String,
val description: String
)

View file

@ -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,39 +123,88 @@ 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 {
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())
}
}
@ -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
)
}

View file

@ -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
}
}

View file

@ -0,0 +1,53 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.101828575"
android:scaleY="0.101828575"
android:translateX="27.835714"
android:translateY="29.485714">
<path
android:pathData="M125.67,37.64h361.61v220.93h-361.61z"
android:strokeLineJoin="round"
android:strokeWidth="20"
android:fillColor="#ffe680"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M217.94,298.36h177.75v54.12h-177.75z"
android:strokeLineJoin="round"
android:strokeWidth="20"
android:fillColor="#b3b3b3"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M282.15,261.97h48.97v36.25h-48.97z"
android:strokeLineJoin="round"
android:strokeWidth="20"
android:fillColor="#999999"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="m27.62,354.13a63.86,63.86 0,0 1,82.15 3.48,63.86 63.86,0 0,1 10.64,81.53"
android:strokeLineJoin="round"
android:strokeWidth="25"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="m34.3,401.3a27.68,30.75 90,0 1,39.56 1.51,27.68 30.75,90 0,1 5.12,35.34"
android:strokeLineJoin="round"
android:strokeWidth="25"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="m31.57,307.13a97.94,97.94 45,0 1,125.99 5.33,97.94 97.94,0 0,1 16.32,125.04"
android:strokeLineJoin="round"
android:strokeWidth="25"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</group>
</vector>

View file

@ -0,0 +1,53 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="108dp"
android:height="108dp"
android:viewportWidth="108"
android:viewportHeight="108">
<group android:scaleX="0.101828575"
android:scaleY="0.101828575"
android:translateX="27.835714"
android:translateY="29.485714">
<path
android:pathData="M125.67,37.64h361.61v220.93h-361.61z"
android:strokeLineJoin="round"
android:strokeWidth="20"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M217.94,298.36h177.75v54.12h-177.75z"
android:strokeLineJoin="round"
android:strokeWidth="20"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="M282.15,261.97h48.97v36.25h-48.97z"
android:strokeLineJoin="round"
android:strokeWidth="20"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="m27.62,354.13a63.86,63.86 0,0 1,82.15 3.48,63.86 63.86,0 0,1 10.64,81.53"
android:strokeLineJoin="round"
android:strokeWidth="25"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="m34.3,401.3a27.68,30.75 90,0 1,39.56 1.51,27.68 30.75,90 0,1 5.12,35.34"
android:strokeLineJoin="round"
android:strokeWidth="25"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
<path
android:pathData="m31.57,307.13a97.94,97.94 45,0 1,125.99 5.33,97.94 97.94,0 0,1 16.32,125.04"
android:strokeLineJoin="round"
android:strokeWidth="25"
android:fillColor="#00000000"
android:strokeColor="#000000"
android:strokeLineCap="round"/>
</group>
</vector>

View file

@ -1,5 +0,0 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@drawable/ic_launcher_background" />
<foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_new_caster_background"/>
<foreground android:drawable="@drawable/ic_new_caster_foreground"/>
<monochrome android:drawable="@drawable/ic_new_caster_monochrome_foreground" />
</adaptive-icon>

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
<background android:drawable="@color/ic_new_caster_background"/>
<foreground android:drawable="@drawable/ic_new_caster_foreground"/>
<monochrome android:drawable="@drawable/ic_new_caster_monochrome_foreground" />
</adaptive-icon>

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.2 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.9 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_launcher_adaptive_background">#AAD400</color>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_new_caster_background">#AAD400</color>
</resources>

View file

@ -0,0 +1,4 @@
<?xml version="1.0" encoding="utf-8"?>
<resources>
<color name="ic_new_caster_monochrome_background">#AAD400</color>
</resources>

View file

@ -18,5 +18,6 @@
<string name="back">Back</string>
<string name="now_playing">Now playing</string>
<string name="cast_url">Cast URL</string>
<string name="title_activity_folder_view">FolderViewActivity</string>
</resources>