commit c0f4bbebcc626ff3c087829f68d2550f0017b2fc Author: Toldi Balázs Ádám Date: Thu Oct 7 14:10:44 2021 +0200 Initial commit diff --git a/.drone.yml b/.drone.yml new file mode 100644 index 0000000..3bccfbf --- /dev/null +++ b/.drone.yml @@ -0,0 +1,45 @@ +kind: pipeline +type: docker +name: default + + +steps: +- name: gradleBuild + image: androidsdk/android-30:latest + commands: + - export VERSION_CODE=$(git rev-list --first-parent --count HEAD) + - export PN=$PACKAGE_NAME + - export V=$VERSION + - export APK=$${PN}_$${V}$${VERSION_CODE}.apk + - echo $APK + - ./gradlew assemble + - cp app/build/outputs/apk/release/app-release-unsigned.apk $${APK} + environment: + PACKAGE_NAME: eu.toldi.balazs.anotherfeedreader + VERSION: 010 +- name: scp files to fdroid repo + image: appleboy/drone-scp + settings: + host: git.toldi.eu + username: + from_secret: fdroid_username + key: + from_secret: fdroid_key + port: 22 + rm: true + target: /home/fdroid-builder/unsigned + source: "*.apk" +- name: update fdroid repo + image: appleboy/drone-ssh + settings: + host: git.toldi.eu + username: + from_secret: fdroid_username + key: + from_secret: fdroid_key + port: 22 + script: + - cd /home/fdroid-builder/ + - fdroid publish + - fdroid update + - fdroid deploy diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..aa724b7 --- /dev/null +++ b/.gitignore @@ -0,0 +1,15 @@ +*.iml +.gradle +/local.properties +/.idea/caches +/.idea/libraries +/.idea/modules.xml +/.idea/workspace.xml +/.idea/navEditor.xml +/.idea/assetWizardSettings.xml +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/.idea/.gitignore b/.idea/.gitignore new file mode 100644 index 0000000..26d3352 --- /dev/null +++ b/.idea/.gitignore @@ -0,0 +1,3 @@ +# Default ignored files +/shelf/ +/workspace.xml diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 0000000..fb7f4a8 --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 0000000..526b4c2 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,20 @@ + + + + + + + \ No newline at end of file diff --git a/.idea/inspectionProfiles/Project_Default.xml b/.idea/inspectionProfiles/Project_Default.xml new file mode 100644 index 0000000..2842237 --- /dev/null +++ b/.idea/inspectionProfiles/Project_Default.xml @@ -0,0 +1,20 @@ + + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 0000000..c1223fb --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,27 @@ + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 0000000..94a25f7 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,6 @@ + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..9a8022f --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,72 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' +} + +android { + compileSdk 31 + + defaultConfig { + applicationId "eu.toldi.balazs.caster" + minSdk 21 + targetSdk 31 + versionCode project.getVersionCode() + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + vectorDrawables { + useSupportLibrary true + } + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + useIR = true + } + buildFeatures { + compose true + } + composeOptions { + kotlinCompilerExtensionVersion compose_version + kotlinCompilerVersion '1.5.21' + } + packagingOptions { + resources { + excludes += '/META-INF/{AL2.0,LGPL2.1}' + } + } +} + +dependencies { + + implementation 'androidx.core:core-ktx:1.6.0' + implementation 'androidx.appcompat:appcompat:1.3.1' + implementation 'com.google.android.material:material:1.4.0' + implementation "androidx.compose.ui:ui:$compose_version" + implementation "androidx.compose.material:material:$compose_version" + 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" + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:2.3.1' + implementation 'androidx.activity:activity-compose:1.3.1' + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-android:1.5.1' + implementation 'com.fasterxml.jackson.core:jackson-databind:2.10.0' + implementation 'su.litvak.chromecast:api-v2:0.11.3' + implementation 'com.github.yausername.youtubedl-android:library:0.12.+' + implementation 'com.github.yausername.youtubedl-android:ffmpeg:0.12.+' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.3' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' + androidTestImplementation "androidx.compose.ui:ui-test-junit4:$compose_version" + debugImplementation "androidx.compose.ui:ui-tooling:$compose_version" +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/androidTest/java/eu/toldi/balazs/caster/ExampleInstrumentedTest.kt b/app/src/androidTest/java/eu/toldi/balazs/caster/ExampleInstrumentedTest.kt new file mode 100644 index 0000000..3595ca1 --- /dev/null +++ b/app/src/androidTest/java/eu/toldi/balazs/caster/ExampleInstrumentedTest.kt @@ -0,0 +1,24 @@ +package eu.toldi.balazs.caster + +import androidx.test.platform.app.InstrumentationRegistry +import androidx.test.ext.junit.runners.AndroidJUnit4 + +import org.junit.Test +import org.junit.runner.RunWith + +import org.junit.Assert.* + +/** + * Instrumented test, which will execute on an Android device. + * + * See [testing documentation](http://d.android.com/tools/testing). + */ +@RunWith(AndroidJUnit4::class) +class ExampleInstrumentedTest { + @Test + fun useAppContext() { + // Context of the app under test. + val appContext = InstrumentationRegistry.getInstrumentation().targetContext + assertEquals("eu.toldi.balazs.caster", appContext.packageName) + } +} \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..2d05cec --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,44 @@ + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/java/eu/toldi/balazs/caster/ChromeCastHelper.kt b/app/src/main/java/eu/toldi/balazs/caster/ChromeCastHelper.kt new file mode 100644 index 0000000..7fabcdc --- /dev/null +++ b/app/src/main/java/eu/toldi/balazs/caster/ChromeCastHelper.kt @@ -0,0 +1,16 @@ +package eu.toldi.balazs.caster + +object ChromeCastHelper { + + const val APP_BACKDROP = "E8C28D3C" + const val APP_YOUTUBE = "233637DE" + const val APP_MEDIA_RECEIVER = "CC1AD845" + const val APP_PLEX = "06ee44ee-e7e3-4249-83b6-f5d0b6f07f34_1" + const val APP_DASHCAST = "84912283" + const val APP_HOME_ASSISTANT = "B12CE3CA" + const val APP_SUPLA = "A41B766D" + const val APP_YLEAREENA = "A9BCCB7C" + const val APP_BUBBLEUPNP = "3927FA74" + const val APP_BBCSOUNDS = "03977A48" + const val APP_BBCIPLAYER = "5E81F6DB" +} \ No newline at end of file diff --git a/app/src/main/java/eu/toldi/balazs/caster/ChromecastManagerActivity.kt b/app/src/main/java/eu/toldi/balazs/caster/ChromecastManagerActivity.kt new file mode 100644 index 0000000..1154f5b --- /dev/null +++ b/app/src/main/java/eu/toldi/balazs/caster/ChromecastManagerActivity.kt @@ -0,0 +1,312 @@ +package eu.toldi.balazs.caster + +import android.os.Bundle +import android.util.Log +import android.view.KeyEvent +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.* +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.* +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import eu.toldi.balazs.caster.ui.theme.CasterTheme +import kotlinx.coroutines.Dispatchers.IO +import kotlinx.coroutines.GlobalScope +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch +import su.litvak.chromecast.api.v2.Application +import su.litvak.chromecast.api.v2.ChromeCast +import com.yausername.youtubedl_android.YoutubeDL + +import com.yausername.youtubedl_android.YoutubeDLRequest +import com.yausername.youtubedl_android.YoutubeDLException +import com.yausername.youtubedl_android.mapper.VideoInfo +import su.litvak.chromecast.api.v2.MediaStatus + + +class ChromecastManagerActivity : ComponentActivity() { + companion object { + var chromeCast_: ChromeCast? = null + + fun castLink(chromeCast: ChromeCast,link : String, callBack : () -> Unit = {}) : Boolean{ + var exitStatus = true + if(link.startsWith("https://").not() && link.startsWith("http://").not()) { + callBack.invoke() + return false + } + GlobalScope.launch(IO) { + val request = + YoutubeDLRequest(link) + request.addOption("-f", "best") + + val _streamInfo = try { + YoutubeDL.getInstance().getInfo(request) + } catch (e: YoutubeDLException) { + null + } + if (_streamInfo == null) { + exitStatus = false + callBack.invoke() + } else { + + val streamInfo = _streamInfo as VideoInfo + val status = chromeCast.status + if (chromeCast.isAppAvailable("CC1AD845") && !status.isAppRunning( + "CC1AD845" + ) + ) { + val app: Application = chromeCast.launchApp("CC1AD845") + } + while (!chromeCast.status.isAppRunning("CC1AD845")) { + delay(100) + } + + chromeCast.load( + streamInfo.title, + streamInfo.thumbnail, + streamInfo.url, + null + ) + + callBack.invoke() + } + } + return exitStatus + } + + } + + private lateinit var chromeCast: ChromeCast + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (chromeCast_ == null) + finish() + chromeCast = chromeCast_ as ChromeCast + try { + YoutubeDL.getInstance().init(application) + } catch (e: YoutubeDLException) { + Log.e("Caster", "failed to initialize youtubedl-android", e) + } + setContent { + CasterTheme { + // A surface container using the 'background' color from the theme + Surface(color = MaterialTheme.colors.background) { + Column { + MenuBar() + Column( + modifier = Modifier + .padding(6.dp) + .fillMaxWidth() + .fillMaxHeight(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + var text by remember { mutableStateOf("") } + + OutlinedTextField( + value = text, + onValueChange = { + text = it + }, + label = { Text("Cast URL") }, + modifier = Modifier.padding(vertical = 4.dp) + ) + var castEnabled by remember { mutableStateOf(true)} + Button( + enabled = castEnabled, + onClick = { + castEnabled = false + castLink(chromeCast,text) { + castEnabled = true + } + + }, + modifier = Modifier.padding(vertical = 4.dp) + ) { + Text(text = "Cast") + } + playBackControl() + } + } + } + } + } + } + + + @Composable + fun MenuBar() { + TopAppBar( + title = { + Text("Caster") + }, + navigationIcon = { + IconButton(onClick = { finish() }) { + Icon( + Icons.Filled.ArrowBack, + contentDescription = "Back" + ) + } + }, + actions = { + Row { + + } + } + ) + } + + @Composable + fun playBackControl(){ + Row() { + + IconButton(onClick = { + GlobalScope.launch(IO) { + if(chromeCast.mediaStatus != null) { + chromeCast.seek(chromeCast.mediaStatus.currentTime - 10) + } + } + }) { + Icon( + Icons.Filled.FastRewind, + contentDescription = "FastRewind" + ) + } + + var playBackState by remember { mutableStateOf(MediaStatus.PlayerState.IDLE) } + GlobalScope.launch(IO) { + if(chromeCast.mediaStatus != null) + playBackState = chromeCast.mediaStatus.playerState + } + IconButton(onClick = { + GlobalScope.launch(IO) { + if(chromeCast.mediaStatus != null) + playBackState = chromeCast.mediaStatus.playerState + if(playBackState == MediaStatus.PlayerState.PLAYING) { + chromeCast.pause() + } + if(playBackState == MediaStatus.PlayerState.PAUSED) { + chromeCast.play() + } + if(chromeCast.mediaStatus != null) + playBackState = chromeCast.mediaStatus.playerState + } + }) { + when(playBackState) { + MediaStatus.PlayerState.PLAYING -> Icon( + Icons.Filled.Pause, + contentDescription = "Pause" + ) + MediaStatus.PlayerState.PAUSED -> Icon( + Icons.Filled.PlayArrow, + contentDescription = "Resume" + ) + else -> Icon( + Icons.Filled.PlayArrow, + contentDescription = "Resume" + ) + } + + } + IconButton(onClick = { + GlobalScope.launch(IO) { + chromeCast.stopApp() + } + }) { + Icon( + Icons.Filled.Stop, + contentDescription = "Stop" + ) + } + + IconButton(onClick = { + GlobalScope.launch(IO) { + if(chromeCast.mediaStatus != null) { + chromeCast.seek(chromeCast.mediaStatus.currentTime + 10) + } + } + }) { + Icon( + Icons.Filled.FastForward, + contentDescription = "FastForward" + ) + } + } + } + + @Composable + fun showNowPlaying() { + /*var sliderPosition by remember { mutableStateOf() } + Text(text = sliderPosition.toString()) + Slider(value = sliderPosition, onValueChange = { sliderPosition = it })*/ + } + + @Preview(showBackground = true) + @Composable + fun DefaultPreview2() { + CasterTheme { + Column { + MenuBar() + Column( + modifier = Modifier + .padding(6.dp) + .fillMaxWidth() + .fillMaxHeight(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + var text by remember { mutableStateOf("") } + + OutlinedTextField( + value = text, + onValueChange = { + text = it + }, + label = { Text("Cast URL") }, + modifier = Modifier.padding(vertical = 4.dp) + ) + Button( + onClick = { + GlobalScope.launch(IO) { + val status = chromeCast.getStatus() + if (chromeCast.isAppAvailable("CC1AD845") && !status.isAppRunning( + "CC1AD845" + ) + ) { + val app: Application = chromeCast.launchApp("CC1AD845") + } + while (!status.isAppRunning("CC1AD845")) { + delay(100) + } + chromeCast.load(text) + } + }, + modifier = Modifier.padding(vertical = 4.dp) + ) { + Text(text = "Cast") + } + playBackControl() + } + } + } + } + + override fun onKeyDown(keyCode: Int, event: KeyEvent?): Boolean { + if(keyCode == KeyEvent.KEYCODE_VOLUME_UP) { + GlobalScope.launch(IO) { + chromeCast.setVolume(chromeCast.status.volume.level + 0.05f) + } + return true + } + if(keyCode == KeyEvent.KEYCODE_VOLUME_DOWN) { + GlobalScope.launch(IO) { + chromeCast.setVolume(chromeCast.status.volume.level - 0.05f) + } + return true + } + return super.onKeyDown(keyCode, event) + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/toldi/balazs/caster/MainActivity.kt b/app/src/main/java/eu/toldi/balazs/caster/MainActivity.kt new file mode 100644 index 0000000..bd63310 --- /dev/null +++ b/app/src/main/java/eu/toldi/balazs/caster/MainActivity.kt @@ -0,0 +1,143 @@ +package eu.toldi.balazs.caster + +import android.annotation.SuppressLint +import android.content.Intent +import android.os.Bundle +import android.util.Log +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.viewModels +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.filled.Share +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import eu.toldi.balazs.caster.model.ChromeCastViewModel +import eu.toldi.balazs.caster.ui.theme.CasterTheme +import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers.IO +import su.litvak.chromecast.api.v2.Application +import su.litvak.chromecast.api.v2.ChromeCast +import su.litvak.chromecast.api.v2.ChromeCasts +import java.net.Inet4Address +import java.net.InetAddress +import java.net.NetworkInterface + +class MainActivity : ComponentActivity() { + + @SuppressLint("CoroutineCreationDuringComposition") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + + setContent { + CasterTheme { + GlobalScope.launch(IO) { + ChromeCasts.startDiscovery(getIPv4Address()) + } + val model: ChromeCastViewModel by viewModels() + var chromecastCount by remember { + mutableStateOf(ChromeCasts.get().size) + } + // A surface container using the 'background' color from the theme + Surface(color = MaterialTheme.colors.background) { + Column { + MenuBar() + + Text(text = "Chromecast count $chromecastCount") + LazyColumn(modifier = Modifier + .padding(all = 4.dp) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally) { + item { + if (chromecastCount > 0) + Text(text = "Available chromecasts:") + } + items(chromecastCount) { index -> + showChromeCastButton(chromeCast = ChromeCasts.get()[index]) + } + } + } + } + SideEffect { + GlobalScope.launch(IO) { + while (true) { + chromecastCount = ChromeCasts.get().size + delay(1000) + } + } + } + } + } + } + + + @Composable + fun showChromeCastButton(chromeCast: ChromeCast) { + Button(onClick = { + ChromecastManagerActivity.chromeCast_ = chromeCast + val intent = Intent(this, ChromecastManagerActivity::class.java) + startActivity(intent) + }, + modifier = Modifier.padding(5.dp) + ) { + Text(text = chromeCast.model) + } + } + + fun getIPv4Address(): InetAddress? { + NetworkInterface.getNetworkInterfaces().toList().forEach { interf -> + interf.inetAddresses.toList().forEach { inetAddress -> + if (!inetAddress.isLoopbackAddress && inetAddress.hostAddress.indexOf(':') < 0) { + return inetAddress + } + } + } + return null + } + + @Composable + fun MenuBar() { + TopAppBar( + title = { + Text("Caster") + }, + navigationIcon = { + IconButton(onClick = { }) { + Icon( + Icons.Filled.Menu, + contentDescription = "Menu Hamburger" + ) + } + }, + actions = { + Row { + IconButton(onClick = {}) { + Icon( + Icons.Filled.Share, + contentDescription = "Search Article" + ) + } + } + } + ) + } + + + @Preview(showBackground = true) + @Composable + fun DefaultPreview() { + CasterTheme { + MenuBar() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/toldi/balazs/caster/ShareRecieverActivity.kt b/app/src/main/java/eu/toldi/balazs/caster/ShareRecieverActivity.kt new file mode 100644 index 0000000..8a35ed5 --- /dev/null +++ b/app/src/main/java/eu/toldi/balazs/caster/ShareRecieverActivity.kt @@ -0,0 +1,168 @@ +package eu.toldi.balazs.caster + +import android.annotation.SuppressLint +import android.content.Intent +import android.os.Bundle +import android.util.Log +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Row +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.padding +import androidx.compose.foundation.lazy.LazyColumn +import androidx.compose.material.* +import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Menu +import androidx.compose.material.icons.filled.Settings +import androidx.compose.material.icons.filled.Share +import androidx.compose.runtime.* +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.tooling.preview.Preview +import androidx.compose.ui.unit.dp +import com.yausername.youtubedl_android.YoutubeDL +import com.yausername.youtubedl_android.YoutubeDLException +import eu.toldi.balazs.caster.ui.theme.CasterTheme +import kotlinx.coroutines.* +import kotlinx.coroutines.Dispatchers.IO +import su.litvak.chromecast.api.v2.Application +import su.litvak.chromecast.api.v2.ChromeCast +import su.litvak.chromecast.api.v2.ChromeCasts +import java.net.Inet4Address +import java.net.InetAddress +import java.net.NetworkInterface + +class ShareRecieverActivity : ComponentActivity() { + + @SuppressLint("CoroutineCreationDuringComposition") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + if (intent?.action != Intent.ACTION_SEND || intent.type != "text/plain") { + finish() + } + val link = intent.getStringExtra(Intent.EXTRA_TEXT) as String + try { + YoutubeDL.getInstance().init(application) + } catch (e: YoutubeDLException) { + Log.e("Caster", "failed to initialize youtubedl-android", e) + } + setContent { + CasterTheme { + var chromecastCount by remember { mutableStateOf(ChromeCasts.get().size) } + GlobalScope.launch(IO) { + ChromeCasts.startDiscovery(getIPv4Address()) + } + Log.i(null, "Chromecast count: $chromecastCount") + // A surface container using the 'background' color from the theme + Surface(color = MaterialTheme.colors.background) { + Column { + MenuBar() + var enabled by remember { + mutableStateOf(true) + } + LazyColumn( + modifier = Modifier + .padding(all = 4.dp) + .fillMaxWidth(), + horizontalAlignment = Alignment.CenterHorizontally + ) { + item { + if (chromecastCount > 0) + Text(text = "Available chromecasts:") + } + items(chromecastCount) { index -> + showChromeCastButton( + enabled = enabled, + onEnableChanged = { enabled = it }, + chromeCast = ChromeCasts.get()[index], + link = link + ) + } + item{ + if(enabled.not()){ + CircularProgressIndicator() + } + } + } + } + LaunchedEffect(true) { + while (true) { + delay(2000) + chromecastCount = ChromeCasts.get().size + } + } + } + } + } + } + + + @Composable + fun showChromeCastButton( + enabled: Boolean, + onEnableChanged: (Boolean) -> Unit, + chromeCast: ChromeCast, + link: String + ) { + Button( + onClick = { + onEnableChanged(false) + ChromecastManagerActivity.castLink(chromeCast, link) { + finish() + } + }, + modifier = Modifier.padding(5.dp), + enabled = enabled + ) { + Text(text = chromeCast.model) + } + } + + fun getIPv4Address(): InetAddress? { + NetworkInterface.getNetworkInterfaces().toList().forEach { interf -> + interf.inetAddresses.toList().forEach { inetAddress -> + if (!inetAddress.isLoopbackAddress && inetAddress.hostAddress.indexOf(':') < 0) { + return inetAddress + } + } + } + return null + } + + @Composable + fun MenuBar() { + TopAppBar( + title = { + Text("Caster") + }, + navigationIcon = { + IconButton(onClick = { }) { + Icon( + Icons.Filled.Menu, + contentDescription = "Menu Hamburger" + ) + } + }, + actions = { + Row { + IconButton(onClick = {}) { + Icon( + Icons.Filled.Share, + contentDescription = "Search Article" + ) + } + } + } + ) + } + + + @Preview(showBackground = true) + @Composable + fun DefaultPreview() { + CasterTheme { + MenuBar() + } + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/toldi/balazs/caster/model/ChromeCastViewModel.kt b/app/src/main/java/eu/toldi/balazs/caster/model/ChromeCastViewModel.kt new file mode 100644 index 0000000..9a65f8e --- /dev/null +++ b/app/src/main/java/eu/toldi/balazs/caster/model/ChromeCastViewModel.kt @@ -0,0 +1,19 @@ +package eu.toldi.balazs.caster.model + +import androidx.lifecycle.LiveData +import androidx.lifecycle.MutableLiveData +import androidx.lifecycle.ViewModel +import su.litvak.chromecast.api.v2.ChromeCast +import su.litvak.chromecast.api.v2.ChromeCasts + +class ChromeCastViewModel : ViewModel() { + private val chromeCasts : MutableLiveData> by lazy { + MutableLiveData>().also { + ChromeCasts.get() + } + } + + fun getChromecasts() : LiveData> { + return chromeCasts + } +} \ No newline at end of file diff --git a/app/src/main/java/eu/toldi/balazs/caster/ui/theme/Color.kt b/app/src/main/java/eu/toldi/balazs/caster/ui/theme/Color.kt new file mode 100644 index 0000000..0f90203 --- /dev/null +++ b/app/src/main/java/eu/toldi/balazs/caster/ui/theme/Color.kt @@ -0,0 +1,8 @@ +package eu.toldi.balazs.caster.ui.theme + +import androidx.compose.ui.graphics.Color + +val Purple200 = Color(0xFFBB86FC) +val Purple500 = Color(0xFF6200EE) +val Purple700 = Color(0xFF3700B3) +val Teal200 = Color(0xFF03DAC5) \ No newline at end of file diff --git a/app/src/main/java/eu/toldi/balazs/caster/ui/theme/Shape.kt b/app/src/main/java/eu/toldi/balazs/caster/ui/theme/Shape.kt new file mode 100644 index 0000000..b968b54 --- /dev/null +++ b/app/src/main/java/eu/toldi/balazs/caster/ui/theme/Shape.kt @@ -0,0 +1,11 @@ +package eu.toldi.balazs.caster.ui.theme + +import androidx.compose.foundation.shape.RoundedCornerShape +import androidx.compose.material.Shapes +import androidx.compose.ui.unit.dp + +val Shapes = Shapes( + small = RoundedCornerShape(4.dp), + medium = RoundedCornerShape(4.dp), + large = RoundedCornerShape(0.dp) +) \ No newline at end of file diff --git a/app/src/main/java/eu/toldi/balazs/caster/ui/theme/Theme.kt b/app/src/main/java/eu/toldi/balazs/caster/ui/theme/Theme.kt new file mode 100644 index 0000000..573b9dd --- /dev/null +++ b/app/src/main/java/eu/toldi/balazs/caster/ui/theme/Theme.kt @@ -0,0 +1,44 @@ +package eu.toldi.balazs.caster.ui.theme + +import androidx.compose.foundation.isSystemInDarkTheme +import androidx.compose.material.MaterialTheme +import androidx.compose.material.darkColors +import androidx.compose.material.lightColors +import androidx.compose.runtime.Composable + +private val DarkColorPalette = darkColors( + primary = Purple200, + primaryVariant = Purple700, + secondary = Teal200 +) + +private val LightColorPalette = lightColors( + primary = Purple500, + primaryVariant = Purple700, + secondary = Teal200 + + /* Other default colors to override + background = Color.White, + surface = Color.White, + onPrimary = Color.White, + onSecondary = Color.Black, + onBackground = Color.Black, + onSurface = Color.Black, + */ +) + +@Composable +fun CasterTheme(darkTheme: Boolean = isSystemInDarkTheme(), content: @Composable() () -> Unit) { + val colors = if (darkTheme) { + DarkColorPalette + } else { + LightColorPalette + } + + MaterialTheme( + colors = colors, + typography = Typography, + shapes = Shapes, + content = content + ) +} \ No newline at end of file diff --git a/app/src/main/java/eu/toldi/balazs/caster/ui/theme/Type.kt b/app/src/main/java/eu/toldi/balazs/caster/ui/theme/Type.kt new file mode 100644 index 0000000..3b7bce0 --- /dev/null +++ b/app/src/main/java/eu/toldi/balazs/caster/ui/theme/Type.kt @@ -0,0 +1,28 @@ +package eu.toldi.balazs.caster.ui.theme + +import androidx.compose.material.Typography +import androidx.compose.ui.text.TextStyle +import androidx.compose.ui.text.font.FontFamily +import androidx.compose.ui.text.font.FontWeight +import androidx.compose.ui.unit.sp + +// Set of Material typography styles to start with +val Typography = Typography( + body1 = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 16.sp + ) + /* Other default text styles to override + button = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.W500, + fontSize = 14.sp + ), + caption = TextStyle( + fontFamily = FontFamily.Default, + fontWeight = FontWeight.Normal, + fontSize = 12.sp + ) + */ +) \ No newline at end of file diff --git a/app/src/main/res/drawable-v24/ic_launcher_foreground.xml b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml new file mode 100644 index 0000000..2b068d1 --- /dev/null +++ b/app/src/main/res/drawable-v24/ic_launcher_foreground.xml @@ -0,0 +1,30 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_launcher_background.xml b/app/src/main/res/drawable/ic_launcher_background.xml new file mode 100644 index 0000000..07d5da9 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_background.xml @@ -0,0 +1,170 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..eca70cf --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.webp b/app/src/main/res/mipmap-hdpi/ic_launcher.webp new file mode 100644 index 0000000..c209e78 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp new file mode 100644 index 0000000..b2dfe3d Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.webp b/app/src/main/res/mipmap-mdpi/ic_launcher.webp new file mode 100644 index 0000000..4f0f1d6 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp new file mode 100644 index 0000000..62b611d Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp new file mode 100644 index 0000000..948a307 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..1b9a695 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp new file mode 100644 index 0000000..28d4b77 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9287f50 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp new file mode 100644 index 0000000..aa7d642 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.webp differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp new file mode 100644 index 0000000..9126ae3 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.webp differ diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..ceae5df --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..62e982b --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,5 @@ + + Caster + ChromecastManagerActivity + Choose Chromecast + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..aedc7af --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,25 @@ + + + + + + +