diff --git a/app/build.gradle b/app/build.gradle index 501231f9..57a3a761 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -1,8 +1,8 @@ plugins { id 'com.android.application' id 'org.jetbrains.kotlin.android' - id 'org.jetbrains.kotlin.plugin.serialization' version '1.9.21' - id 'org.ajoberstar.grgit' version '5.2.2' + id 'org.jetbrains.kotlin.plugin.serialization' version '2.2.21' + id 'org.ajoberstar.grgit' version '5.3.3' id 'com.google.protobuf' id 'kotlin-parcelize' id 'com.google.devtools.ksp' @@ -97,7 +97,7 @@ android { defaultConfig { minSdk 28 - targetSdk 35 + targetSdk 36 versionCode gitVersionCode versionName gitVersionName @@ -155,82 +155,80 @@ android { dependencies { //implementation 'com.google.dagger:dagger:2.48' - implementation 'androidx.test:monitor:1.7.2' - implementation 'com.google.android.material:material:1.12.0' + implementation 'androidx.test:monitor:1.8.0' + implementation 'com.google.android.material:material:1.13.0' //annotationProcessor 'com.google.dagger:dagger-compiler:2.48' //Core - implementation 'androidx.core:core-ktx:1.12.0' - implementation 'androidx.appcompat:appcompat:1.6.1' - implementation 'com.google.android.material:material:1.11.0' - implementation 'androidx.constraintlayout:constraintlayout:2.1.4' + implementation 'androidx.core:core-ktx:1.17.0' + implementation 'androidx.appcompat:appcompat:1.7.1' + implementation 'androidx.constraintlayout:constraintlayout:2.2.1' + implementation 'androidx.documentfile:documentfile:1.1.0' //Images - annotationProcessor 'com.github.bumptech.glide:compiler:4.16.0' - implementation 'com.github.bumptech.glide:glide:4.16.0' + annotationProcessor 'com.github.bumptech.glide:compiler:5.0.5' + implementation 'com.github.bumptech.glide:glide:5.0.5' //Async - implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" + implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.10.2" //HTTP - implementation "com.squareup.okhttp3:okhttp:4.11.0" + implementation "com.squareup.okhttp3:okhttp:5.3.0" //JSON - implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.6.2" //Used for structured json - implementation 'com.google.code.gson:gson:2.10.1' //Used for complex/anonymous cases like during development conversions (eg. V8RemoteObject) + implementation "org.jetbrains.kotlinx:kotlinx-serialization-json:1.9.0" //Used for structured json + implementation 'com.google.code.gson:gson:2.13.2' //Used for complex/anonymous cases like during development conversions (eg. V8RemoteObject) //JS - implementation("com.caoccao.javet:javet-android:3.0.2") - //implementation 'com.caoccao.javet:javet-v8-android:4.1.4' //Change after extensive testing the freezing edge cases are solved. + implementation 'com.caoccao.javet:javet-v8-android:5.0.1' //Exoplayer - implementation 'androidx.media3:media3-exoplayer:1.2.1' - implementation 'androidx.media3:media3-exoplayer-dash:1.2.1' - implementation 'androidx.media3:media3-ui:1.2.1' - implementation 'androidx.media3:media3-exoplayer-hls:1.2.1' - implementation 'androidx.media3:media3-exoplayer-rtsp:1.2.1' - implementation 'androidx.media3:media3-exoplayer-smoothstreaming:1.2.1' - implementation 'androidx.media3:media3-transformer:1.2.1' - implementation 'androidx.navigation:navigation-fragment-ktx:2.7.6' - implementation 'androidx.navigation:navigation-ui-ktx:2.7.6' - implementation 'androidx.media:media:1.7.0' + implementation 'androidx.media3:media3-exoplayer:1.8.0' + implementation 'androidx.media3:media3-exoplayer-dash:1.8.0' + implementation 'androidx.media3:media3-ui:1.8.0' + implementation 'androidx.media3:media3-exoplayer-hls:1.8.0' + implementation 'androidx.media3:media3-exoplayer-rtsp:1.8.0' + implementation 'androidx.media3:media3-exoplayer-smoothstreaming:1.8.0' + implementation 'androidx.media3:media3-transformer:1.8.0' + implementation 'androidx.navigation:navigation-fragment-ktx:2.9.6' + implementation 'androidx.navigation:navigation-ui-ktx:2.9.6' + implementation 'androidx.media:media:1.7.1' //Other - implementation 'org.jsoup:jsoup:1.15.3' + implementation 'org.jsoup:jsoup:1.21.2' implementation 'com.google.android.flexbox:flexbox:3.0.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation fileTree(dir: 'aar', include: ['*.aar']) implementation 'com.arthenica:smart-exception-java:0.2.1' - implementation 'org.jetbrains.kotlin:kotlin-reflect:1.9.0' + implementation 'org.jetbrains.kotlin:kotlin-reflect:2.2.0' implementation 'com.github.dhaval2404:imagepicker:2.1' - implementation 'com.google.zxing:core:3.4.1' + implementation 'com.google.zxing:core:3.5.3' implementation 'com.journeyapps:zxing-android-embedded:4.3.0' implementation 'com.caverock:androidsvg-aar:1.4' //Protobuf - implementation 'com.google.protobuf:protobuf-javalite:3.25.1' + implementation 'com.google.protobuf:protobuf-javalite:4.33.0' implementation 'com.polycentric.core:app:1.0' implementation 'com.futo.futopay:app:1.0' - implementation 'androidx.work:work-runtime-ktx:2.9.0' - implementation 'androidx.concurrent:concurrent-futures-ktx:1.1.0' + implementation 'androidx.work:work-runtime-ktx:2.11.0' + implementation 'androidx.concurrent:concurrent-futures-ktx:1.3.0' //Database - implementation("androidx.room:room-runtime:2.6.1") - annotationProcessor("androidx.room:room-compiler:2.6.1") - ksp("androidx.room:room-compiler:2.6.1") - implementation("androidx.room:room-ktx:2.6.1") + implementation("androidx.room:room-runtime:2.8.3") + ksp("androidx.room:room-compiler:2.8.3") + implementation("androidx.room:room-ktx:2.8.3") //Payment - implementation 'com.stripe:stripe-android:20.35.1' + implementation 'com.stripe:stripe-android:22.0.0' testImplementation 'junit:junit:4.13.2' - testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.7.3' - testImplementation "org.jetbrains.kotlin:kotlin-test:1.8.22" - testImplementation "org.xmlunit:xmlunit-core:2.9.1" - testImplementation "org.mockito:mockito-core:5.4.0" - androidTestImplementation 'androidx.test.ext:junit:1.1.5' - androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.1' + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:1.10.2' + testImplementation "org.jetbrains.kotlin:kotlin-test:2.0.21" + testImplementation "org.xmlunit:xmlunit-core:2.11.0" + testImplementation "org.mockito:mockito-core:5.20.0" + androidTestImplementation 'androidx.test.ext:junit:1.3.0' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.7.0' //Rust casting SDK implementation('org.futo.gitlab.videostreaming.fcast-sdk-jitpack:sender-sdk-minimal:0.3.1') { diff --git a/app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt index 81be84ea..eaf33dea 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/AddSourceActivity.kt @@ -107,10 +107,9 @@ class AddSourceActivity : AppCompatActivity() { onNewIntent(intent); } - override fun onNewIntent(intent: Intent?) { + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent) - var url = intent?.dataString; - + var url = intent.dataString; if(url == null) UIDialogs.showDialog(this, R.drawable.ic_error, getString(R.string.no_valid_url_provided), null, null, 0, UIDialogs.Action(getString(R.string.ok), { finish() }, UIDialogs.ActionStyle.PRIMARY)); diff --git a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt index 2d56cb35..dc2caa63 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/MainActivity.kt @@ -708,17 +708,13 @@ class MainActivity : AppCompatActivity, IWithResultLauncher { _wasStopped = true; } - override fun onNewIntent(intent: Intent?) { + override fun onNewIntent(intent: Intent) { super.onNewIntent(intent); handleIntent(intent); } - private fun handleIntent(intent: Intent?) { - if (intent == null) - return; + private fun handleIntent(intent: Intent) { Logger.i(TAG, "handleIntent started by " + intent.action); - - var targetData: String? = null; when (intent.action) { diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/PlatformAuthorLink.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/PlatformAuthorLink.kt index 831f8ef7..e163df79 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/PlatformAuthorLink.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/PlatformAuthorLink.kt @@ -54,14 +54,16 @@ interface IPlatformChannelContent : IPlatformContent { val subscribers: Long? } -open class JSChannelContent : JSContent, IPlatformChannelContent { - override val contentType: ContentType get() = ContentType.CHANNEL - override val thumbnail: String? - override val subscribers: Long? +open class JSChannelContent( + config: SourcePluginConfig, + obj: V8ValueObject +) : JSContent(config, obj), IPlatformChannelContent { - constructor(config: SourcePluginConfig, obj: V8ValueObject) : super(config, obj) { - val contextName = "Channel"; - thumbnail = obj.getOrDefault(config, "thumbnail", contextName, null) - subscribers = if(obj.has("subscribers")) obj.getOrThrow(config,"subscribers", contextName) else null - } -} \ No newline at end of file + final override val contentType: ContentType = ContentType.CHANNEL + + override val thumbnail: String? = + _content.getOrDefault(_pluginConfig, "thumbnail", "Channel", null) + + override val subscribers: Long? = + _content.getOrDefault(_pluginConfig, "subscribers", "Channel", null)?.toLong() +} diff --git a/app/src/main/java/com/futo/platformplayer/api/media/models/comments/PlatformComment.kt b/app/src/main/java/com/futo/platformplayer/api/media/models/comments/PlatformComment.kt index 7de46eb3..5b679ae6 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/models/comments/PlatformComment.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/models/comments/PlatformComment.kt @@ -6,25 +6,15 @@ import com.futo.platformplayer.api.media.models.ratings.IRating import com.futo.platformplayer.api.media.structures.IPager import java.time.OffsetDateTime -open class PlatformComment : IPlatformComment { - override val contextUrl: String; - override val author: PlatformAuthorLink; - override val message: String; - override val rating: IRating; - override val date: OffsetDateTime; +open class PlatformComment( + override val contextUrl: String, + override val author: PlatformAuthorLink, + override val message: String, + override val rating: IRating, + override val date: OffsetDateTime, + override val replyCount: Int? = null +) : IPlatformComment { - override val replyCount: Int?; - - constructor(contextUrl: String, author: PlatformAuthorLink, msg: String, rating: IRating, date: OffsetDateTime, replyCount: Int? = null) { - this.contextUrl = contextUrl; - this.author = author; - this.message = msg; - this.rating = rating; - this.date = date; - this.replyCount = replyCount; - } - - override fun getReplies(client: IPlatformClient): IPager { - return NoCommentsPager(); - } -} \ No newline at end of file + override fun getReplies(client: IPlatformClient): IPager = + NoCommentsPager() +} diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt index 2233048f..1783ed38 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/JSClient.kt @@ -103,7 +103,7 @@ open class JSClient : IPlatformClient { override val id: String get() = config.id; override val name: String get() = config.name; - override val icon: ImageVariable; + override val icon: ImageVariable get() = StatePlatform.instance.getPlatformIcon(config.id) ?: ImageVariable(config.absoluteIconUrl, null, null) override var capabilities: PlatformClientCapabilities = PlatformClientCapabilities(); private var _busyAction = ""; @@ -147,7 +147,6 @@ open class JSClient : IPlatformClient { constructor(context: Context, descriptor: SourcePluginDescriptor, saveState: String? = null) { this._context = context; this.config = descriptor.config; - icon = StatePlatform.instance.getPlatformIcon(config.id) ?: ImageVariable(config.absoluteIconUrl, null, null); this.descriptor = descriptor; _injectedSaveState = saveState; _auth = descriptor.getAuth(); @@ -178,7 +177,6 @@ open class JSClient : IPlatformClient { constructor(context: Context, descriptor: SourcePluginDescriptor, saveState: String?, script: String, withoutCredentials: Boolean = false) { this._context = context; this.config = descriptor.config; - icon = StatePlatform.instance.getPlatformIcon(config.id) ?: ImageVariable(config.absoluteIconUrl, null, null); this.descriptor = descriptor; _injectedSaveState = saveState; if(!withoutCredentials) diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSArticle.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSArticle.kt index d1fb658e..e5b9f353 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSArticle.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSArticle.kt @@ -23,17 +23,22 @@ import com.futo.platformplayer.getOrThrow import com.futo.platformplayer.getOrThrowNullableList import com.futo.platformplayer.states.StateDeveloper -open class JSArticle : JSContent, IPlatformArticle, IPluginSourced { - final override val contentType: ContentType get() = ContentType.ARTICLE; +open class JSArticle( + config: SourcePluginConfig, + obj: V8ValueObject +) : JSContent(config, obj), IPlatformArticle, IPluginSourced { - override val summary: String; - override val thumbnails: Thumbnails?; + final override val contentType: ContentType = ContentType.ARTICLE - constructor(config: SourcePluginConfig, obj: V8ValueObject): super(config, obj) { - val contextName = "PlatformArticle"; + override val summary: String = + obj.getOrDefault(config, "summary", "PlatformArticle", "") ?: "" - summary = _content.getOrDefault(config, "summary", contextName, "") ?: ""; - thumbnails = Thumbnails.fromV8(config, _content.getOrThrow(config, "thumbnails", contextName)); - - } -} \ No newline at end of file + override val thumbnails: Thumbnails? = + if (obj.has("thumbnails")) + Thumbnails.fromV8( + config, + obj.getOrThrow(config, "thumbnails", "PlatformArticle") + ) + else + null +} diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSArticleDetails.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSArticleDetails.kt index 40c63d48..1d175e5a 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSArticleDetails.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSArticleDetails.kt @@ -24,36 +24,37 @@ import com.futo.platformplayer.getOrThrowNullableList import com.futo.platformplayer.invokeV8 import com.futo.platformplayer.states.StateDeveloper -open class JSArticleDetails : JSContent, IPlatformArticleDetails, IPluginSourced, IPlatformContentDetails { - final override val contentType: ContentType get() = ContentType.ARTICLE; +open class JSArticleDetails( + private val client: JSClient, + obj: V8ValueObject +) : JSContent(client.config, obj), IPlatformArticleDetails, IPluginSourced, IPlatformContentDetails { - private val _hasGetComments: Boolean; - private val _hasGetContentRecommendations: Boolean; + final override val contentType: ContentType = ContentType.ARTICLE - override val rating: IRating; + private val _hasGetComments: Boolean = _content.has("getComments") + private val _hasGetContentRecommendations: Boolean = _content.has("getContentRecommendations") - override val summary: String; - override val thumbnails: Thumbnails?; - override val segments: List; + override val rating: IRating = + obj.getOrDefault(client.config, "rating", "PlatformArticle", null) + ?.let { IRating.fromV8(client.config, it, "PlatformArticle") } + ?: RatingLikes(0) - constructor(client: JSClient, obj: V8ValueObject): super(client.config, obj) { - val contextName = "PlatformArticle"; + override val summary: String = + _content.getOrThrow(client.config, "summary", "PlatformArticle") - rating = obj.getOrDefault(client.config, "rating", contextName, null)?.let { IRating.fromV8(client.config, it, contextName) } ?: RatingLikes(0); - summary = _content.getOrThrow(client.config, "summary", contextName); - if(_content.has("thumbnails")) - thumbnails = Thumbnails.fromV8(client.config, _content.getOrThrow(client.config, "thumbnails", contextName)); + override val thumbnails: Thumbnails? = + if (_content.has("thumbnails")) + Thumbnails.fromV8( + client.config, + _content.getOrThrow(client.config, "thumbnails", "PlatformArticle") + ) else - thumbnails = null; + null - - segments = (obj.getOrThrowNullableList(client.config, "segments", contextName) - ?.map { fromV8Segment(client, it) } - ?.filterNotNull() ?: listOf()); - - _hasGetComments = _content.has("getComments"); - _hasGetContentRecommendations = _content.has("getContentRecommendations"); - } + override val segments: List = + obj.getOrThrowNullableList(client.config, "segments", "PlatformArticle") + ?.mapNotNull { fromV8Segment(client, it) } + ?: emptyList() override fun getComments(client: IPlatformClient): IPager? { if(!_hasGetComments || _content.isClosed) diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSContent.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSContent.kt index 1e74bd0d..a3b5c34b 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSContent.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSContent.kt @@ -16,51 +16,49 @@ import java.time.LocalDateTime import java.time.OffsetDateTime import java.time.ZoneOffset -open class JSContent : IPlatformContent, IPluginSourced { - protected val _pluginConfig: SourcePluginConfig; - protected val _content : V8ValueObject; +open class JSContent( + protected val _pluginConfig: SourcePluginConfig, + protected val _content: V8ValueObject +) : IPlatformContent, IPluginSourced { - protected val _hasGetDetails: Boolean; + override val contentType: ContentType = ContentType.UNKNOWN - override val contentType: ContentType get() = ContentType.UNKNOWN; + protected val _hasGetDetails: Boolean = _content.has("getDetails") - override val id: PlatformID; - override val name: String; - override val author: PlatformAuthorLink; - override val datetime: OffsetDateTime?; + override val id: PlatformID = + PlatformID.fromV8(_pluginConfig, _content.getOrThrow(_pluginConfig, "id", CTX)) - override val url: String; - override val shareUrl: String; + override val name: String = + HtmlCompat.fromHtml( + _content.getOrThrow(_pluginConfig, "name", CTX).decodeUnicode(), + HtmlCompat.FROM_HTML_MODE_LEGACY + ).toString() - override val sourceConfig: SourcePluginConfig get() = _pluginConfig; + override val author: PlatformAuthorLink = + _content.getOrDefault(_pluginConfig, "author", CTX, null) + ?.let { PlatformAuthorLink.fromV8(_pluginConfig, it) } + ?: PlatformAuthorLink.UNKNOWN - constructor(config: SourcePluginConfig, obj: V8ValueObject) { - _pluginConfig = config; - _content = obj; + private val _epoch: Long? = + _content.getOrDefault(_pluginConfig, "datetime", CTX, null)?.toLong() - val contextName = "PlatformContent"; + override val datetime: OffsetDateTime? = + _epoch?.takeIf { it != 0L }?.let { + OffsetDateTime.of(LocalDateTime.ofEpochSecond(it, 0, ZoneOffset.UTC), ZoneOffset.UTC) + } - id = PlatformID.fromV8(_pluginConfig, _content.getOrThrow(config, "id", contextName)); - name = HtmlCompat.fromHtml(_content.getOrThrow(config, "name", contextName).decodeUnicode(), HtmlCompat.FROM_HTML_MODE_LEGACY).toString(); + override val url: String = + _content.getOrThrow(_pluginConfig, "url", CTX) - val authorObj = _content.getOrDefault(config, "author", contextName, null); - if(authorObj != null) - author = PlatformAuthorLink.fromV8(_pluginConfig, authorObj); - else - author = PlatformAuthorLink.UNKNOWN; + override val shareUrl: String = + _content.getOrDefault(_pluginConfig, "shareUrl", CTX, null) ?: url - val datetimeInt = _content.getOrDefault(config, "datetime", contextName, null)?.toLong(); - if(datetimeInt == null || datetimeInt == 0.toLong()) - datetime = null; - else - datetime = OffsetDateTime.of(LocalDateTime.ofEpochSecond(datetimeInt, 0, ZoneOffset.UTC), ZoneOffset.UTC); - url = _content.getOrThrow(config, "url", contextName); - shareUrl = _content.getOrDefault(config, "shareUrl", contextName, null) ?: url; + override val sourceConfig: SourcePluginConfig + get() = _pluginConfig - _hasGetDetails = _content.has("getDetails"); + fun getUnderlyingObject(): V8ValueObject? = _content + + companion object { + private const val CTX = "PlatformContent" } - - fun getUnderlyingObject(): V8ValueObject? { - return _content; - } -} \ No newline at end of file +} diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaylist.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaylist.kt index 787b53e4..8dd500e1 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaylist.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/JSPlaylist.kt @@ -6,14 +6,16 @@ import com.futo.platformplayer.api.media.models.playlists.IPlatformPlaylist import com.futo.platformplayer.api.media.platforms.js.SourcePluginConfig import com.futo.platformplayer.getOrDefault -open class JSPlaylist : JSContent, IPlatformPlaylist { - override val contentType: ContentType get() = ContentType.PLAYLIST; - override val thumbnail: String?; - override val videoCount: Int; +open class JSPlaylist( + config: SourcePluginConfig, + obj: V8ValueObject +) : JSContent(config, obj), IPlatformPlaylist { - constructor(config: SourcePluginConfig, obj: V8ValueObject) : super(config, obj) { - val contextName = "Playlist"; - thumbnail = obj.getOrDefault(config, "thumbnail", contextName, null); - videoCount = obj.getOrDefault(config, "videoCount", contextName, -1)!!; - } -} \ No newline at end of file + override val contentType: ContentType = ContentType.PLAYLIST + + override val thumbnail: String? = + _content.getOrDefault(_pluginConfig, "thumbnail", "Playlist", null) + + override val videoCount: Int = + _content.getOrDefault(_pluginConfig, "videoCount", "Playlist", null)?.toInt() ?: -1 +} diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioUrlSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioUrlSource.kt index 0edc4f73..416afbbb 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioUrlSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSAudioUrlSource.kt @@ -8,43 +8,44 @@ import com.futo.platformplayer.engine.V8Plugin import com.futo.platformplayer.getOrDefault import com.futo.platformplayer.getOrThrow -open class JSAudioUrlSource : IAudioUrlSource, JSSource { - override val name: String; - override val bitrate : Int; - override val container : String; - override val codec: String; - private val url : String; +open class JSAudioUrlSource( + plugin: JSClient, + obj: V8ValueObject +) : JSSource(TYPE_AUDIOURL, plugin, obj), IAudioUrlSource { - override val language: String; + private val ctx = "AudioUrlSource" + private val cfg = plugin.config - override val duration: Long?; + override val bitrate: Int = + _obj.getOrThrow(cfg, "bitrate", ctx) - override var priority: Boolean = false; + override val container: String = + _obj.getOrThrow(cfg, "container", ctx) - override var original: Boolean = false; + override val codec: String = + _obj.getOrThrow(cfg, "codec", ctx) - constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_AUDIOURL, plugin, obj) { - val contextName = "AudioUrlSource"; - val config = plugin.config; + private val url: String = + _obj.getOrThrow(cfg, "url", ctx) - bitrate = _obj.getOrThrow(config, "bitrate", contextName); - container = _obj.getOrThrow(config, "container", contextName); - codec = _obj.getOrThrow(config, "codec", contextName); - url = _obj.getOrThrow(config, "url", contextName); - language = _obj.getOrThrow(config, "language", contextName); - duration = _obj.getOrDefault(config, "duration", contextName, null); + override val language: String = + _obj.getOrThrow(cfg, "language", ctx) - name = _obj.getOrDefault(config, "name", contextName, "${container} ${bitrate}") ?: "${container} ${bitrate}"; + override val duration: Long? = + _obj.getOrDefault(cfg, "duration", ctx, null)?.toLong() - priority = if(_obj.has("priority")) obj.getOrThrow(config, "priority", contextName) else false; - original = if(_obj.has("original")) obj.getOrThrow(config, "original", contextName) else false; - } + override val name: String = + _obj.getOrDefault(cfg, "name", ctx, null) + ?: "$container $bitrate" - override fun getAudioUrl() : String { - return url; - } + override var priority: Boolean = + if (_obj.has("priority")) _obj.getOrThrow(cfg, "priority", ctx) else false - override fun toString(): String { - return "(name=$name, container=$container, bitrate=$bitrate, codec=$codec, url=$url, language=$language, duration=$duration)"; - } -} \ No newline at end of file + override var original: Boolean = + if (_obj.has("original")) _obj.getOrThrow(cfg, "original", ctx) else false + + override fun getAudioUrl(): String = url + + override fun toString(): String = + "(name=$name, container=$container, bitrate=$bitrate, codec=$codec, url=$url, language=$language, duration=$duration)" +} diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt index 9345b174..b9c08db9 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSDashManifestRawSource.kt @@ -31,42 +31,52 @@ interface IJSDashManifestRawSource { fun generateAsync(scope: CoroutineScope): Deferred; fun generate(): String?; } -open class JSDashManifestRawSource: JSSource, IVideoSource, IJSDashManifestRawSource, IStreamMetaDataSource { - override val container : String; - override val name : String; - override val width: Int; - override val height: Int; - override val codec: String; - override val bitrate: Int?; - override val duration: Long; - override val priority: Boolean; +open class JSDashManifestRawSource( + plugin: JSClient, + obj: V8ValueObject +) : JSSource(TYPE_DASH_RAW, plugin, obj), IVideoSource, IJSDashManifestRawSource, IStreamMetaDataSource { - val url: String?; - override var manifest: String?; + private val ctx = "DashRawSource" + private val cfg = plugin.config - override val hasGenerate: Boolean; - val canMerge: Boolean; + override val container: String = + _obj.getOrDefault(cfg, "container", ctx, null) ?: "application/dash+xml" - override var streamMetaData: StreamMetaData? = null; + override val name: String = + _obj.getOrThrow(cfg, "name", ctx) - constructor(plugin: JSClient, obj: V8ValueObject) : super(TYPE_DASH_RAW, plugin, obj) { - val contextName = "DashRawSource"; - val config = plugin.config; - name = _obj.getOrThrow(config, "name", contextName); - url = _obj.getOrThrow(config, "url", contextName); - container = _obj.getOrDefault(config, "container", contextName, null) ?: "application/dash+xml"; - manifest = _obj.getOrDefault(config, "manifest", contextName, null); - width = _obj.getOrDefault(config, "width", contextName, 0) ?: 0; - height = _obj.getOrDefault(config, "height", contextName, 0) ?: 0; - codec = _obj.getOrDefault(config, "codec", contextName, "") ?: ""; - bitrate = _obj.getOrDefault(config, "bitrate", contextName, 0) ?: 0; - duration = _obj.getOrDefault(config, "duration", contextName, 0) ?: 0; - priority = _obj.getOrDefault(config, "priority", contextName, false) ?: false; - canMerge = _obj.getOrDefault(config, "canMerge", contextName, false) ?: false; - hasGenerate = _obj.has("generate"); - } + override val width: Int = + _obj.getOrDefault(cfg, "width", ctx, null)?.toInt() ?: 0 - private var _pregenerate: V8Deferred? = null; + override val height: Int = + _obj.getOrDefault(cfg, "height", ctx, null)?.toInt() ?: 0 + + override val codec: String = + _obj.getOrDefault(cfg, "codec", ctx, "") ?: "" + + override val bitrate: Int? = + _obj.getOrDefault(cfg, "bitrate", ctx, null)?.toInt() + + override val duration: Long = + _obj.getOrDefault(cfg, "duration", ctx, 0)?.toLong() ?: 0L + + override val priority: Boolean = + _obj.getOrDefault(cfg, "priority", ctx, false) ?: false + + val url: String? = + _obj.getOrDefault(cfg, "url", ctx, null) + + override var manifest: String? = + _obj.getOrDefault(cfg, "manifest", ctx, null) + + override val hasGenerate: Boolean = _obj.has("generate") + + val canMerge: Boolean = + _obj.getOrDefault(cfg, "canMerge", ctx, false) ?: false + + override var streamMetaData: StreamMetaData? = null + + private var _pregenerate: V8Deferred? = null fun pregenerateAsync(scope: CoroutineScope): V8Deferred? { _pregenerate = generateAsync(scope); return _pregenerate; diff --git a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoUrlSource.kt b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoUrlSource.kt index 66246f56..dbf5242a 100644 --- a/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoUrlSource.kt +++ b/app/src/main/java/com/futo/platformplayer/api/media/platforms/js/models/sources/JSVideoUrlSource.kt @@ -5,42 +5,47 @@ import com.futo.platformplayer.api.media.models.streams.sources.IVideoUrlSource import com.futo.platformplayer.api.media.platforms.js.JSClient import com.futo.platformplayer.engine.IV8PluginConfig import com.futo.platformplayer.engine.V8Plugin +import com.futo.platformplayer.getOrDefault import com.futo.platformplayer.getOrNull import com.futo.platformplayer.getOrThrow -open class JSVideoUrlSource : IVideoUrlSource, JSSource { - override val width : Int; - override val height : Int; - override val container : String; - override val codec: String; - override val name : String; - override val bitrate : Int; - override val duration: Long; - private val url : String; +open class JSVideoUrlSource( + plugin: JSClient, + obj: V8ValueObject +) : JSSource(TYPE_VIDEOURL, plugin, obj), IVideoUrlSource { - override var priority: Boolean = false; + private val ctx = "JSVideoUrlSource" + private val cfg = plugin.config - constructor(plugin: JSClient, obj: V8ValueObject): super(TYPE_VIDEOURL, plugin, obj) { - val contextName = "JSVideoUrlSource"; - val config = plugin.config; + override val width: Int = + _obj.getOrThrow(cfg, "width", ctx) - width = _obj.getOrThrow(config, "width", contextName); - height = _obj.getOrThrow(config, "height", contextName); - container = _obj.getOrThrow(config, "container", contextName); - codec = _obj.getOrThrow(config, "codec", contextName); - name = _obj.getOrThrow(config, "name", contextName); - bitrate = _obj.getOrThrow(config, "bitrate", contextName); - duration = _obj.getOrThrow(config, "duration", contextName).toLong(); - url = _obj.getOrThrow(config, "url", contextName); + override val height: Int = + _obj.getOrThrow(cfg, "height", ctx) - priority = obj.getOrNull(config, "priority", contextName) ?: false; - } + override val container: String = + _obj.getOrThrow(cfg, "container", ctx) - override fun getVideoUrl() : String { - return url; - } + override val codec: String = + _obj.getOrThrow(cfg, "codec", ctx) - override fun toString(): String { - return "(width=$width, height=$height, container=$container, codec=$codec, name=$name, bitrate=$bitrate, duration=$duration, url=$url)" - } -} \ No newline at end of file + override val name: String = + _obj.getOrThrow(cfg, "name", ctx) + + override val bitrate: Int = + _obj.getOrThrow(cfg, "bitrate", ctx) + + override val duration: Long = + _obj.getOrThrow(cfg, "duration", ctx) + + private val url: String = + _obj.getOrThrow(cfg, "url", ctx) + + override var priority: Boolean = + _obj.getOrDefault(cfg, "priority", ctx, false) ?: false + + override fun getVideoUrl(): String = url + + override fun toString(): String = + "(width=$width, height=$height, container=$container, codec=$codec, name=$name, bitrate=$bitrate, duration=$duration, url=$url)" +} diff --git a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt index 3ebc7259..40613271 100644 --- a/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt +++ b/app/src/main/java/com/futo/platformplayer/engine/V8Plugin.kt @@ -4,6 +4,8 @@ import android.content.Context import com.caoccao.javet.exceptions.JavetCompilationException import com.caoccao.javet.exceptions.JavetException import com.caoccao.javet.exceptions.JavetExecutionException +import com.caoccao.javet.interfaces.IJavetEntityError +import com.caoccao.javet.interfaces.IJavetEntityMap import com.caoccao.javet.interop.V8Host import com.caoccao.javet.interop.V8Runtime import com.caoccao.javet.values.V8Value @@ -409,6 +411,12 @@ class V8Plugin { return _runtimeMap.getOrDefault(runtime, null); } + private fun ctxString(ctx: Any?, key: String): String? = when (ctx) { + is Map<*, *> -> ctx[key]?.toString() + is V8ValueObject -> if (ctx.has(key)) ctx.getString(key) else null + else -> null + } + fun catchScriptErrors(config: IV8PluginConfig, context: String, code: String? = null, handle: ()->T): T { var codeStripped = code; if(codeStripped != null) { //TODO: Improve code stripped @@ -442,37 +450,6 @@ class V8Plugin { throw ScriptCompilationException(config, "Compilation: [${context}]: ${scriptEx.message}\n(${scriptEx.scriptingError.lineNumber})[${scriptEx.scriptingError.startColumn}-${scriptEx.scriptingError.endColumn}]: ${scriptEx.scriptingError.sourceLine}", null, codeStripped); } catch(executeEx: JavetExecutionException) { - val obj = executeEx.scriptingError?.context - if(obj != null && obj.containsKey("plugin_type") == true) { - val pluginType = obj["plugin_type"].toString(); - - //Captcha - if (pluginType == "CaptchaRequiredException") { - throw ScriptCaptchaRequiredException(config, - obj["url"]?.toString(), - obj["body"]?.toString(), - executeEx, executeEx.scriptingError?.stack, codeStripped); - } - - //Reload Required - if (pluginType == "ReloadRequiredException") { - throw ScriptReloadRequiredException(config, - obj["msg"]?.toString(), - obj["reloadData"]?.toString(), - executeEx, executeEx.scriptingError?.stack, codeStripped); - } - - //Others - throwExceptionFromV8( - config, - pluginType, - (extractJSExceptionMessage(executeEx) ?: ""), - executeEx, - executeEx.scriptingError?.stack, - codeStripped - ); - } - /* //Required for newer V8 versions if(executeEx.scriptingError?.context is IJavetEntityError) { val obj = executeEx.scriptingError?.context as IJavetEntityError if(obj.context.containsKey("plugin_type") == true) { @@ -506,7 +483,6 @@ class V8Plugin { } } - */ throw ScriptExecutionException(config, extractJSExceptionMessage(executeEx) ?: "", null, executeEx.scriptingError?.stack, codeStripped); } catch(ex: Exception) { diff --git a/app/src/main/java/com/futo/platformplayer/views/adapters/CommentViewHolder.kt b/app/src/main/java/com/futo/platformplayer/views/adapters/CommentViewHolder.kt index 18f32096..2218e8fe 100644 --- a/app/src/main/java/com/futo/platformplayer/views/adapters/CommentViewHolder.kt +++ b/app/src/main/java/com/futo/platformplayer/views/adapters/CommentViewHolder.kt @@ -46,7 +46,6 @@ class CommentViewHolder : ViewHolder { private val _imageLikeIcon: ImageView; private val _textLikes: TextView; private val _imageDislikeIcon: ImageView; - private val _imageCopy: ImageView; private val _textDislikes: TextView; private val _buttonReplies: PillButton; private val _layoutRating: LinearLayout; @@ -77,7 +76,6 @@ class CommentViewHolder : ViewHolder { _layoutRating = itemView.findViewById(R.id.layout_rating); _pillRatingLikesDislikes = itemView.findViewById(R.id.rating); _buttonDelete = itemView.findViewById(R.id.button_delete); - _imageCopy = itemView.findViewById(R.id.button_copy) _containerComments = itemView.findViewById(R.id.comment_container); _loader = itemView.findViewById(R.id.loader); @@ -105,30 +103,13 @@ class CommentViewHolder : ViewHolder { StatePolycentric.instance.updateLikeMap(c.reference, args.hasLiked, args.hasDisliked) }; - val listener = object : GestureDetector.SimpleOnGestureListener() { - override fun onDown(e: MotionEvent): Boolean = true - - override fun onDoubleTap(e: MotionEvent): Boolean { - val clipboard = viewGroup.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager - val text = comment?.message.orEmpty() - val clip = ClipData.newPlainText("Comment", text) - clipboard.setPrimaryClip(clip) - UIDialogs.toast(viewGroup.context, "Copied", false) - return true - } - } - - val detector = GestureDetector(viewGroup.context, listener).apply { - setOnDoubleTapListener(listener) - } - - _imageCopy.apply { - isClickable = true - setOnTouchListener { v, event -> - val consumed = detector.onTouchEvent(event) - if (!consumed && event.actionMasked == MotionEvent.ACTION_UP) v.performClick() - consumed - } + _textBody.setOnLongClickListener { + val clipboard = viewGroup.context.getSystemService(Context.CLIPBOARD_SERVICE) as ClipboardManager + val text = comment?.message.orEmpty() + val clip = ClipData.newPlainText("Comment", text) + clipboard.setPrimaryClip(clip) + UIDialogs.toast(viewGroup.context, "Copied", false) + true } _creatorThumbnail.onClick.subscribe { diff --git a/app/src/main/res/layout/list_comment.xml b/app/src/main/res/layout/list_comment.xml index 1d0bd4b3..b39af456 100644 --- a/app/src/main/res/layout/list_comment.xml +++ b/app/src/main/res/layout/list_comment.xml @@ -159,13 +159,6 @@ app:pillText="55 Replies" android:layout_marginStart="15dp" /> - - diff --git a/build.gradle b/build.gradle index 4e3f83c2..402da100 100644 --- a/build.gradle +++ b/build.gradle @@ -1,8 +1,8 @@ // Top-level build file where you can add configuration options common to all sub-projects/modules. plugins { - id 'com.android.application' version '8.5.2' apply false - id 'com.android.library' version '8.5.2' apply false - id 'org.jetbrains.kotlin.android' version '1.9.0' apply false - id 'com.google.protobuf' version '0.9.4' apply false - id 'com.google.devtools.ksp' version '1.9.0-1.0.13' apply false + id 'com.android.application' version '8.13.0' apply false + id 'com.android.library' version '8.13.0' apply false + id 'org.jetbrains.kotlin.android' version '2.2.21' apply false + id 'com.google.protobuf' version '0.9.5' apply false + id 'com.google.devtools.ksp' version '2.2.21-2.0.4' apply false } diff --git a/dep/futopay b/dep/futopay index 224d6976..6857c8f0 160000 --- a/dep/futopay +++ b/dep/futopay @@ -1 +1 @@ -Subproject commit 224d69764c238c80cc21280be03b283eebbf6757 +Subproject commit 6857c8f0bc9785e29cbbe68b94ea6cc230c4e0ed diff --git a/dep/polycentricandroid b/dep/polycentricandroid index 8ee5a329..642747fa 160000 --- a/dep/polycentricandroid +++ b/dep/polycentricandroid @@ -1 +1 @@ -Subproject commit 8ee5a329e2fef65cc9ef75bf2521555195d81669 +Subproject commit 642747fae8092e5eb4fdf8c1929ab15f4125f085 diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties index 69924496..7d741713 100644 --- a/gradle/wrapper/gradle-wrapper.properties +++ b/gradle/wrapper/gradle-wrapper.properties @@ -1,6 +1,6 @@ #Fri Nov 11 13:25:09 CET 2022 distributionBase=GRADLE_USER_HOME -distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip +distributionUrl=https\://services.gradle.org/distributions/gradle-8.13-bin.zip distributionPath=wrapper/dists zipStorePath=wrapper/dists zipStoreBase=GRADLE_USER_HOME