A commit, that was needed...
6
.directory
Normal file
|
@ -0,0 +1,6 @@
|
|||
[Dolphin]
|
||||
Timestamp=2022,1,4,18,4,55.221
|
||||
Version=4
|
||||
|
||||
[Settings]
|
||||
HiddenFilesShown=true
|
123
.idea/codeStyles/Project.xml
Normal 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>
|
5
.idea/codeStyles/codeStyleConfig.xml
Normal file
|
@ -0,0 +1,5 @@
|
|||
<component name="ProjectCodeStyleConfiguration">
|
||||
<state>
|
||||
<option name="USE_PER_PROJECT_SETTINGS" value="true" />
|
||||
</state>
|
||||
</component>
|
|
@ -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" />
|
||||
|
|
|
@ -13,7 +13,6 @@
|
|||
<option value="$PROJECT_DIR$/app" />
|
||||
</set>
|
||||
</option>
|
||||
<option name="resolveModulePerSourceSet" value="false" />
|
||||
</GradleProjectSettings>
|
||||
</option>
|
||||
</component>
|
||||
|
|
|
@ -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>
|
||||
|
|
|
@ -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"
|
||||
|
|
|
@ -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>
|
BIN
app/src/main/ic_launcher_adaptive-playstore.png
Normal file
After Width: | Height: | Size: 11 KiB |
BIN
app/src/main/ic_new_caster-playstore.png
Normal file
After Width: | Height: | Size: 12 KiB |
BIN
app/src/main/ic_new_caster_monochrome-playstore.png
Normal file
After Width: | Height: | Size: 12 KiB |
|
@ -132,6 +132,7 @@ object ChromeCastHelper {
|
|||
}
|
||||
if (_streamInfo == null) {
|
||||
exitStatus = false
|
||||
Log.e("Caster_ChromecastHelper","Failed to fetch stream!")
|
||||
callBack.invoke()
|
||||
} else {
|
||||
try {
|
||||
|
|
|
@ -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!!)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
201
app/src/main/java/eu/toldi/balazs/caster/FolderViewActivity.kt
Normal 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 {
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -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
|
||||
)
|
||||
}
|
||||
|
||||
|
|
|
@ -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 ->
|
||||
|
|
|
@ -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
|
||||
)
|
|
@ -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
|
||||
)
|
||||
}
|
|
@ -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
|
||||
}
|
||||
}
|
53
app/src/main/res/drawable/ic_new_caster_foreground.xml
Normal 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>
|
|
@ -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>
|
|
@ -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>
|
6
app/src/main/res/mipmap-anydpi-v26/ic_new_caster.xml
Normal 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>
|
|
@ -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>
|
BIN
app/src/main/res/mipmap-hdpi/ic_new_caster.png
Normal file
After Width: | Height: | Size: 1.6 KiB |
BIN
app/src/main/res/mipmap-hdpi/ic_new_caster_round.png
Normal file
After Width: | Height: | Size: 3.5 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_new_caster.png
Normal file
After Width: | Height: | Size: 1.1 KiB |
BIN
app/src/main/res/mipmap-mdpi/ic_new_caster_round.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_new_caster.png
Normal file
After Width: | Height: | Size: 2.2 KiB |
BIN
app/src/main/res/mipmap-xhdpi/ic_new_caster_round.png
Normal file
After Width: | Height: | Size: 4.9 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_new_caster.png
Normal file
After Width: | Height: | Size: 3.4 KiB |
BIN
app/src/main/res/mipmap-xxhdpi/ic_new_caster_round.png
Normal file
After Width: | Height: | Size: 7.8 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_new_caster.png
Normal file
After Width: | Height: | Size: 4.6 KiB |
BIN
app/src/main/res/mipmap-xxxhdpi/ic_new_caster_round.png
Normal file
After Width: | Height: | Size: 11 KiB |
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_adaptive_background">#AAD400</color>
|
||||
</resources>
|
4
app/src/main/res/values/ic_new_caster_background.xml
Normal file
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_new_caster_background">#AAD400</color>
|
||||
</resources>
|
|
@ -0,0 +1,4 @@
|
|||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_new_caster_monochrome_background">#AAD400</color>
|
||||
</resources>
|
|
@ -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>
|