diff --git a/app/build.gradle b/app/build.gradle index ecde6342..8d66c56c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -162,14 +162,14 @@ dependencies { implementation "org.jetbrains.kotlinx:kotlinx-coroutines-android:1.7.3" //HTTP - implementation "com.squareup.okhttp3:okhttp:4.11.0" + implementation "com.squareup.okhttp3:okhttp:4.12.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) //JS - implementation("com.caoccao.javet:javet-android:3.0.2") + implementation("com.caoccao.javet:javet-android:3.0.3") //Exoplayer implementation 'androidx.media3:media3-exoplayer:1.2.1' @@ -179,8 +179,8 @@ dependencies { 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.navigation:navigation-fragment-ktx:2.7.7' + implementation 'androidx.navigation:navigation-ui-ktx:2.7.7' implementation 'androidx.media:media:1.7.0' //Other @@ -189,7 +189,7 @@ dependencies { implementation 'com.google.android.flexbox:flexbox:3.0.0' implementation 'androidx.swiperefreshlayout:swiperefreshlayout:1.1.0' implementation 'com.arthenica:ffmpeg-kit-full:5.1' - implementation 'org.jetbrains.kotlin:kotlin-reflect:1.9.0' + implementation 'org.jetbrains.kotlin:kotlin-reflect:1.9.21' implementation 'com.github.dhaval2404:imagepicker:2.1' implementation 'com.google.zxing:core:3.4.1' implementation 'com.journeyapps:zxing-android-embedded:4.3.0' @@ -214,7 +214,7 @@ dependencies { 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.jetbrains.kotlin:kotlin-test:1.9.21" testImplementation "org.xmlunit:xmlunit-core:2.9.1" testImplementation "org.mockito:mockito-core:5.4.0" androidTestImplementation 'androidx.test.ext:junit:1.1.5' diff --git a/app/src/main/java/com/futo/platformplayer/activities/PolycentricCreateProfileActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/PolycentricCreateProfileActivity.kt index 4986a186..078c692d 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/PolycentricCreateProfileActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricCreateProfileActivity.kt @@ -12,6 +12,7 @@ import com.futo.platformplayer.R import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.polycentric.PolycentricStorage import com.futo.platformplayer.setNavigationBarColorAndIcons import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StatePolycentric @@ -70,6 +71,12 @@ class PolycentricCreateProfileActivity : AppCompatActivity() { processHandle = ProcessHandle.create(); Store.instance.addProcessSecret(processHandle.processSecret); + try { + PolycentricStorage.instance.addProcessSecret(processHandle.processSecret) + } catch (e: Throwable) { + Logger.e(TAG, "Failed to save process secret to secret storage.", e) + } + processHandle.addServer("https://srv1-stg.polycentric.io"); processHandle.setUsername(username); StatePolycentric.instance.setProcessHandle(processHandle); diff --git a/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt index d4a98f58..825463b3 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricImportProfileActivity.kt @@ -13,6 +13,7 @@ import com.futo.platformplayer.R import com.futo.platformplayer.UIDialogs import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.polycentric.PolycentricCache +import com.futo.platformplayer.polycentric.PolycentricStorage import com.futo.platformplayer.setNavigationBarColorAndIcons import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StatePolycentric @@ -126,6 +127,12 @@ class PolycentricImportProfileActivity : AppCompatActivity() { val processSecret = ProcessSecret(keyPair, Process.random()); Store.instance.addProcessSecret(processSecret); + try { + PolycentricStorage.instance.addProcessSecret(processSecret) + } catch (e: Throwable) { + Logger.e(TAG, "Failed to save process secret to secret storage.", e) + } + val processHandle = processSecret.toProcessHandle(); for (e in exportBundle.events.eventsList) { diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt index fa56598c..b08b86e6 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/VideoDetailView.kt @@ -12,7 +12,6 @@ import android.graphics.drawable.BitmapDrawable import android.graphics.drawable.Drawable import android.graphics.drawable.Icon import android.net.Uri -import android.provider.Browser import android.support.v4.media.session.PlaybackStateCompat import android.text.Spanned import android.util.AttributeSet diff --git a/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricStorage.kt b/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricStorage.kt new file mode 100644 index 00000000..ca028ac3 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricStorage.kt @@ -0,0 +1,42 @@ +package com.futo.platformplayer.polycentric + +import com.futo.platformplayer.encryption.GEncryptionProviderV1 +import com.futo.platformplayer.logging.Logger +import com.futo.platformplayer.stores.FragmentedStorage +import com.futo.platformplayer.stores.StringArrayStorage +import com.futo.polycentric.core.ProcessSecret +import com.futo.polycentric.core.base64ToByteArray +import com.futo.polycentric.core.toBase64 +import userpackage.Protocol + +class PolycentricStorage { + private val _processSecrets = FragmentedStorage.get("processSecrets"); + + fun addProcessSecret(processSecret: ProcessSecret) { + _processSecrets.addDistinct(GEncryptionProviderV1.instance.encrypt(processSecret.toProto().toByteArray()).toBase64()) + _processSecrets.saveBlocking() + } + + fun getProcessSecrets(): List { + val processSecrets = arrayListOf() + for (p in _processSecrets.getAllValues()) { + try { + processSecrets.add(ProcessSecret.fromProto(Protocol.StorageTypeProcessSecret.parseFrom(GEncryptionProviderV1.instance.decrypt(p.base64ToByteArray())))) + } catch (e: Throwable) { + Logger.i(TAG, "Failed to decrypt process secret", e); + } + } + return processSecrets + } + + companion object { + val TAG = "PolycentricStorage"; + private var _instance : PolycentricStorage? = null; + val instance : PolycentricStorage + get(){ + if(_instance == null) + _instance = PolycentricStorage(); + return _instance!!; + }; + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt b/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt index b81b08c0..bed1ba3b 100644 --- a/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt +++ b/app/src/main/java/com/futo/platformplayer/states/StatePolycentric.kt @@ -23,6 +23,7 @@ import com.futo.platformplayer.dp import com.futo.platformplayer.fragment.mainactivity.main.PolycentricProfile import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.polycentric.PolycentricCache +import com.futo.platformplayer.polycentric.PolycentricStorage import com.futo.platformplayer.resolveChannelUrl import com.futo.platformplayer.selectBestImage import com.futo.platformplayer.stores.FragmentedStorage @@ -67,28 +68,40 @@ class StatePolycentric { return } - try { - val db = SqlLiteDbHelper(context); - Store.initializeSqlLiteStore(db); + for (i in 0 .. 1) { + try { + val db = SqlLiteDbHelper(context); + Store.initializeSqlLiteStore(db); - val activeProcessHandleString = _activeProcessHandle.value; - if (activeProcessHandleString.isNotEmpty()) { - try { - val system = PublicKey.fromProto(Protocol.PublicKey.parseFrom(activeProcessHandleString.base64ToByteArray())); - setProcessHandle(Store.instance.getProcessSecret(system)?.toProcessHandle()); - } catch (e: Throwable) { - db.upgradeOldSecrets(db.writableDatabase); + val activeProcessHandleString = _activeProcessHandle.value; + if (activeProcessHandleString.isNotEmpty()) { + try { + val system = PublicKey.fromProto(Protocol.PublicKey.parseFrom(activeProcessHandleString.base64ToByteArray())); + setProcessHandle(Store.instance.getProcessSecret(system)?.toProcessHandle()); + } catch (e: Throwable) { + db.upgradeOldSecrets(db.writableDatabase); - val system = PublicKey.fromProto(Protocol.PublicKey.parseFrom(activeProcessHandleString.base64ToByteArray())); - setProcessHandle(Store.instance.getProcessSecret(system)?.toProcessHandle()); + val system = PublicKey.fromProto(Protocol.PublicKey.parseFrom(activeProcessHandleString.base64ToByteArray())); + setProcessHandle(Store.instance.getProcessSecret(system)?.toProcessHandle()); + Log.i(TAG, "Failed to initialize Polycentric.", e) + } + } + + getProcessHandles() + + break; + } catch (e: Throwable) { + if (i == 0) { + Logger.i(TAG, "Clearing Polycentric database due to corruption"); + val db = SqlLiteDbHelper(context); + db.recreate() + } else { + _transientEnabled = false + UIDialogs.showGeneralErrorDialog(context, "Failed to initialize Polycentric.", e); Log.i(TAG, "Failed to initialize Polycentric.", e) } } - } catch (e: Throwable) { - _transientEnabled = false - UIDialogs.showGeneralErrorDialog(context, "Failed to initialize Polycentric.", e); - Log.i(TAG, "Failed to initialize Polycentric.", e) } } @@ -103,7 +116,32 @@ class StatePolycentric { return listOf() } - return Store.instance.getProcessSecrets().map { it.toProcessHandle(); }; + val storeProcessSecrets = Store.instance.getProcessSecrets().toMutableList() + val processSecrets = PolycentricStorage.instance.getProcessSecrets() + + for (processSecret in processSecrets) + { + if (!storeProcessSecrets.contains(processSecret)) { + try { + Store.instance.addProcessSecret(processSecret) + } catch (e: Throwable) { + Logger.e(TAG, "Failed to backfill process secret.") + } + } + } + + for (processSecret in storeProcessSecrets) + { + if (!processSecrets.contains(processSecret)) { + try { + PolycentricStorage.instance.addProcessSecret(processSecret) + } catch (e: Throwable) { + Logger.e(TAG, "Failed to backfill process secret.") + } + } + } + + return (storeProcessSecrets + processSecrets).distinct().map { it.toProcessHandle() } } fun setProcessHandle(processHandle: ProcessHandle?) { diff --git a/app/src/main/java/com/futo/platformplayer/views/overlays/RepliesOverlay.kt b/app/src/main/java/com/futo/platformplayer/views/overlays/RepliesOverlay.kt index 19c5dd22..9d9ef35e 100644 --- a/app/src/main/java/com/futo/platformplayer/views/overlays/RepliesOverlay.kt +++ b/app/src/main/java/com/futo/platformplayer/views/overlays/RepliesOverlay.kt @@ -51,9 +51,11 @@ class RepliesOverlay : LinearLayout { private var _onCommentAdded: ((comment: IPlatformComment) -> Unit)? = null; private val _loaderOverlay: LoaderOverlay private val _client = ManagedHttpClient() + private val _layoutItems: LinearLayout constructor(context: Context, attrs: AttributeSet? = null) : super(context, attrs) { inflate(context, R.layout.overlay_replies, this) + _layoutItems = findViewById(R.id.layout_items) _topbar = findViewById(R.id.topbar); _commentsList = findViewById(R.id.comments_list); _addCommentView = findViewById(R.id.add_comment_view); @@ -65,6 +67,9 @@ class RepliesOverlay : LinearLayout { _loaderOverlay = findViewById(R.id.loader_overlay) setLoading(false); + _layoutItems.removeView(_layoutParentComment) + _commentsList.setPrependedView(_layoutParentComment) + _addCommentView.onCommentAdded.subscribe { _commentsList.addComment(it); _onCommentAdded?.invoke(it); diff --git a/app/src/main/java/com/futo/platformplayer/views/segments/CommentsList.kt b/app/src/main/java/com/futo/platformplayer/views/segments/CommentsList.kt index 57732acb..0eb95434 100644 --- a/app/src/main/java/com/futo/platformplayer/views/segments/CommentsList.kt +++ b/app/src/main/java/com/futo/platformplayer/views/segments/CommentsList.kt @@ -71,6 +71,9 @@ class CommentsList : ConstraintLayout { override fun onScrolled(recyclerView: RecyclerView, dx: Int, dy: Int) { super.onScrolled(recyclerView, dx, dy); onScrolled(); + + val totalScrollDistance = recyclerView.computeVerticalScrollOffset() + _layoutScrollToTop.visibility = if (totalScrollDistance > recyclerView.height) View.VISIBLE else View.GONE } }; @@ -82,6 +85,7 @@ class CommentsList : ConstraintLayout { private var _loading = false; private val _prependedView: FrameLayout; private var _readonly: Boolean = false; + private val _layoutScrollToTop: FrameLayout; var onRepliesClick = Event1(); var onCommentsLoaded = Event1(); @@ -90,6 +94,13 @@ class CommentsList : ConstraintLayout { LayoutInflater.from(context).inflate(R.layout.view_comments_list, this, true); _recyclerComments = findViewById(R.id.recycler_comments); + + _layoutScrollToTop = findViewById(R.id.layout_scroll_to_top); + _layoutScrollToTop.setOnClickListener { + _recyclerComments.smoothScrollToPosition(0) + } + _layoutScrollToTop.visibility = View.GONE + _textMessage = TextView(context).apply { layoutParams = FrameLayout.LayoutParams(FrameLayout.LayoutParams.MATCH_PARENT, FrameLayout.LayoutParams.WRAP_CONTENT).apply { setMargins(0, 30, 0, 0) diff --git a/app/src/main/res/layout/overlay_replies.xml b/app/src/main/res/layout/overlay_replies.xml index 51bb4b4c..f788c826 100644 --- a/app/src/main/res/layout/overlay_replies.xml +++ b/app/src/main/res/layout/overlay_replies.xml @@ -1,111 +1,108 @@ - + xmlns:app="http://schemas.android.com/apk/res-auto" + android:layout_width="match_parent" + android:layout_height="match_parent"> - + - + - - - + android:layout_marginTop="12dp" + android:layout_marginStart="12dp" + android:layout_marginEnd="12dp" /> - + android:layout_width="match_parent" + android:layout_marginStart="12dp" + android:layout_marginEnd="12dp" + android:layout_marginBottom="12dp" + android:padding="12dp" + android:background="@drawable/background_16_round_4dp"> - + - + - + - + + + + + + - - \ No newline at end of file + \ No newline at end of file diff --git a/app/src/main/res/layout/view_comments_list.xml b/app/src/main/res/layout/view_comments_list.xml index 48318415..b51feb50 100644 --- a/app/src/main/res/layout/view_comments_list.xml +++ b/app/src/main/res/layout/view_comments_list.xml @@ -7,5 +7,25 @@ android:id="@+id/recycler_comments" android:layout_width="match_parent" android:layout_height="match_parent" /> - + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 5dc24aef..fea3d914 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -751,6 +751,7 @@ Select Zoom Check to see if an update is available. + Scroll to top Recommendations Subscriptions diff --git a/dep/polycentricandroid b/dep/polycentricandroid index 7695198e..5a61f4d0 160000 --- a/dep/polycentricandroid +++ b/dep/polycentricandroid @@ -1 +1 @@ -Subproject commit 7695198eeaeaaea4726712c460081c411ef67866 +Subproject commit 5a61f4d02527f299097a3cbea5395c5532594a24