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 f0fa8397..d5fce50b 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/PolycentricCreateProfileActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricCreateProfileActivity.kt @@ -3,6 +3,7 @@ package com.futo.platformplayer.activities import android.content.Context import android.content.Intent import android.os.Bundle +import android.view.View import android.widget.EditText import android.widget.ImageButton import android.widget.LinearLayout @@ -17,6 +18,7 @@ import com.futo.platformplayer.polycentric.PolycentricStorage import com.futo.platformplayer.setNavigationBarColorAndIcons import com.futo.platformplayer.states.StateApp import com.futo.platformplayer.states.StatePolycentric +import com.futo.platformplayer.views.LoaderView import com.futo.polycentric.core.ProcessHandle import com.futo.polycentric.core.Store import kotlinx.coroutines.Dispatchers @@ -27,6 +29,7 @@ class PolycentricCreateProfileActivity : AppCompatActivity() { private lateinit var _buttonHelp: ImageButton; private lateinit var _profileName: EditText; private lateinit var _buttonCreate: LinearLayout; + private lateinit var _loader: LoaderView; private val TAG = "PolycentricCreateProfileActivity"; private var _creating = false; @@ -43,6 +46,7 @@ class PolycentricCreateProfileActivity : AppCompatActivity() { _buttonHelp = findViewById(R.id.button_help); _profileName = findViewById(R.id.edit_profile_name); _buttonCreate = findViewById(R.id.button_create_profile); + _loader = findViewById(R.id.loader); findViewById(R.id.button_back).setOnClickListener { finish(); }; @@ -65,35 +69,49 @@ class PolycentricCreateProfileActivity : AppCompatActivity() { return@setOnClickListener; } + _profileName.isEnabled = false; + _buttonCreate.visibility = View.GONE; + _loader.start(); + _loader.visibility = View.VISIBLE; + lifecycleScope.launch(Dispatchers.IO) { val processHandle: ProcessHandle; - try { - processHandle = ProcessHandle.create(); - Store.instance.addProcessSecret(processHandle.processSecret); - try { - PolycentricStorage.instance.addProcessSecret(processHandle.processSecret) + 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(PolycentricCache.SERVER); + processHandle.setUsername(username); + StatePolycentric.instance.setProcessHandle(processHandle); } catch (e: Throwable) { - Logger.e(TAG, "Failed to save process secret to secret storage.", e) + Logger.e(TAG, getString(R.string.failed_to_create_profile), e); + return@launch; + } finally { + _creating = false; } - processHandle.addServer(PolycentricCache.SERVER); - processHandle.setUsername(username); - StatePolycentric.instance.setProcessHandle(processHandle); - } catch (e: Throwable) { - Logger.e(TAG, getString(R.string.failed_to_create_profile), e); - return@launch; - } finally { - _creating = false; + try { + Logger.i(TAG, "Started backfill"); + processHandle.fullyBackfillServersAnnounceExceptions(); + Logger.i(TAG, "Finished backfill"); + } catch (e: Throwable) { + Logger.e(TAG, getString(R.string.failed_to_fully_backfill_servers), e); + } } - - try { - Logger.i(TAG, "Started backfill"); - processHandle.fullyBackfillServersAnnounceExceptions(); - Logger.i(TAG, "Finished backfill"); - } catch (e: Throwable) { - Logger.e(TAG, getString(R.string.failed_to_fully_backfill_servers), e); + finally { + withContext(Dispatchers.Main) { + _profileName.isEnabled = true; + _buttonCreate.visibility = View.VISIBLE; + _loader.stop(); + _loader.visibility = View.GONE; + } } withContext(Dispatchers.Main) { diff --git a/app/src/main/java/com/futo/platformplayer/activities/PolycentricHomeActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/PolycentricHomeActivity.kt index dc9f6b2f..46223f51 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/PolycentricHomeActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricHomeActivity.kt @@ -8,6 +8,7 @@ import android.os.Bundle import android.util.TypedValue import android.widget.ImageButton import android.widget.LinearLayout +import android.widget.ScrollView import androidx.appcompat.app.AppCompatActivity import com.bumptech.glide.Glide import com.bumptech.glide.request.target.CustomTarget @@ -28,6 +29,7 @@ class PolycentricHomeActivity : AppCompatActivity() { private lateinit var _buttonNewProfile: BigButton; private lateinit var _buttonImportProfile: BigButton; private lateinit var _layoutButtons: LinearLayout; + private lateinit var _scroll: ScrollView; override fun attachBaseContext(newBase: Context?) { super.attachBaseContext(StateApp.instance.getLocaleContext(newBase)) @@ -42,6 +44,7 @@ class PolycentricHomeActivity : AppCompatActivity() { _buttonNewProfile = findViewById(R.id.button_new_profile); _buttonImportProfile = findViewById(R.id.button_import_profile); _layoutButtons = findViewById(R.id.layout_buttons); + _scroll = findViewById(R.id.scroll); findViewById(R.id.button_back).setOnClickListener { finish(); }; @@ -78,6 +81,7 @@ class PolycentricHomeActivity : AppCompatActivity() { _layoutButtons.addView(profileButton, 0); } + _scroll.invalidate(); _buttonHelp.setOnClickListener { startActivity(Intent(this, PolycentricWhyActivity::class.java)); diff --git a/app/src/main/java/com/futo/platformplayer/activities/PolycentricProfileActivity.kt b/app/src/main/java/com/futo/platformplayer/activities/PolycentricProfileActivity.kt index 825ba3d7..96025a4b 100644 --- a/app/src/main/java/com/futo/platformplayer/activities/PolycentricProfileActivity.kt +++ b/app/src/main/java/com/futo/platformplayer/activities/PolycentricProfileActivity.kt @@ -25,6 +25,7 @@ import com.futo.platformplayer.fullyBackfillServersAnnounceExceptions import com.futo.platformplayer.images.GlideHelper.Companion.crossfade import com.futo.platformplayer.logging.Logger import com.futo.platformplayer.polycentric.PolycentricCache +import com.futo.platformplayer.polycentric.PolycentricStorage import com.futo.platformplayer.selectBestImage import com.futo.platformplayer.setNavigationBarColorAndIcons import com.futo.platformplayer.states.StateApp @@ -33,6 +34,7 @@ import com.futo.platformplayer.views.buttons.BigButton import com.futo.platformplayer.views.overlays.LoaderOverlay import com.futo.polycentric.core.Store import com.futo.polycentric.core.SystemState +import com.futo.polycentric.core.systemToURLInfoSystemLinkUrl import com.futo.polycentric.core.toBase64Url import com.futo.polycentric.core.toURLInfoSystemLinkUrl import com.github.dhaval2404.imagepicker.ImagePicker @@ -47,6 +49,7 @@ class PolycentricProfileActivity : AppCompatActivity() { private lateinit var _buttonHelp: ImageButton; private lateinit var _editName: EditText; private lateinit var _buttonExport: BigButton; + private lateinit var _buttonOpenHarborProfile: BigButton; private lateinit var _buttonLogout: BigButton; private lateinit var _buttonDelete: BigButton; private lateinit var _username: String; @@ -68,6 +71,7 @@ class PolycentricProfileActivity : AppCompatActivity() { _imagePolycentric = findViewById(R.id.image_polycentric); _editName = findViewById(R.id.edit_profile_name); _buttonExport = findViewById(R.id.button_export); + _buttonOpenHarborProfile = findViewById(R.id.button_open_harbor_profile); _buttonLogout = findViewById(R.id.button_logout); _buttonDelete = findViewById(R.id.button_delete); _loaderOverlay = findViewById(R.id.loader_overlay); @@ -92,6 +96,16 @@ class PolycentricProfileActivity : AppCompatActivity() { startActivity(Intent(this, PolycentricBackupActivity::class.java)); }; + _buttonOpenHarborProfile.onClick.subscribe { + val processHandle = StatePolycentric.instance.processHandle!!; + processHandle?.let { + val systemState = SystemState.fromStorageTypeSystemState(Store.instance.getSystemState(it.system)); + val url = it.system.systemToURLInfoSystemLinkUrl(systemState.servers.asIterable()); + val navUrl = "https://harbor.social/" + url.substring("polycentric://".length) + startActivity(Intent(Intent.ACTION_VIEW, Uri.parse(navUrl))) + } + } + _buttonLogout.onClick.subscribe { StatePolycentric.instance.setProcessHandle(null); startActivity(Intent(this, PolycentricHomeActivity::class.java)); @@ -108,6 +122,7 @@ class PolycentricProfileActivity : AppCompatActivity() { StatePolycentric.instance.setProcessHandle(null); Store.instance.removeProcessSecret(processHandle.system); + PolycentricStorage.instance.removeProcessSecret(processHandle.system); startActivity(Intent(this, PolycentricHomeActivity::class.java)); finish(); }); diff --git a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelAboutFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelAboutFragment.kt index 19f1454b..8a412f75 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelAboutFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/channel/tab/ChannelAboutFragment.kt @@ -133,6 +133,10 @@ class ChannelAboutFragment : Fragment, IChannelTabFragment { Logger.w(TAG, "Failed to parse claim=$c", e) } } + if(!map.containsKey("Harbor")) + this.context?.let { + map.set("Harbor", polycentricProfile.getHarborUrl(it)); + } if (map.isNotEmpty()) setLinks(map, if (polycentricProfile.systemState.username.isNotBlank()) polycentricProfile.systemState.username else _lastChannel?.name ?: "") diff --git a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt index ad4aa84d..260c5a54 100644 --- a/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt +++ b/app/src/main/java/com/futo/platformplayer/fragment/mainactivity/main/ChannelFragment.kt @@ -1,7 +1,10 @@ package com.futo.platformplayer.fragment.mainactivity.main import android.annotation.SuppressLint +import android.content.Context +import android.content.Intent import android.graphics.drawable.Animatable +import android.net.Uri import android.os.Bundle import android.view.LayoutInflater import android.view.View @@ -12,6 +15,7 @@ import android.widget.ImageView import android.widget.LinearLayout import android.widget.TextView import androidx.appcompat.widget.AppCompatImageView +import androidx.core.content.ContextCompat.startActivity import androidx.lifecycle.lifecycleScope import androidx.viewpager2.widget.ViewPager2 import com.bumptech.glide.Glide @@ -52,7 +56,9 @@ import com.futo.platformplayer.views.overlays.slideup.SlideUpMenuOverlay import com.futo.platformplayer.views.subscriptions.SubscribeButton import com.futo.polycentric.core.OwnedClaim import com.futo.polycentric.core.PublicKey +import com.futo.polycentric.core.Store import com.futo.polycentric.core.SystemState +import com.futo.polycentric.core.systemToURLInfoSystemLinkUrl import com.futo.polycentric.core.toURLInfoSystemLinkUrl import com.google.android.material.tabs.TabLayout import com.google.android.material.tabs.TabLayoutMediator @@ -64,7 +70,13 @@ import kotlinx.serialization.Serializable @Serializable data class PolycentricProfile( val system: PublicKey, val systemState: SystemState, val ownedClaims: List -) +) { + fun getHarborUrl(context: Context): String{ + val systemState = SystemState.fromStorageTypeSystemState(Store.instance.getSystemState(system)); + val url = system.systemToURLInfoSystemLinkUrl(systemState.servers.asIterable()); + return "https://harbor.social/" + url.substring("polycentric://".length); + } +} class ChannelFragment : MainFragment() { override val isMainView: Boolean = true diff --git a/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricStorage.kt b/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricStorage.kt index ca028ac3..1782f4b5 100644 --- a/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricStorage.kt +++ b/app/src/main/java/com/futo/platformplayer/polycentric/PolycentricStorage.kt @@ -5,6 +5,7 @@ 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.PublicKey import com.futo.polycentric.core.base64ToByteArray import com.futo.polycentric.core.toBase64 import userpackage.Protocol @@ -29,6 +30,18 @@ class PolycentricStorage { return processSecrets } + fun removeProcessSecret(publicKey: PublicKey) { + for(p in _processSecrets.getAllValues()){ + try { + val key = ProcessSecret.fromProto(Protocol.StorageTypeProcessSecret.parseFrom(GEncryptionProviderV1.instance.decrypt(p.base64ToByteArray()))); + if(key.system.publicKey.equals(publicKey)) + _processSecrets.remove(p); + } catch (e: Throwable) { + Logger.i(TAG, "Failed to decrypt process secret", e); + } + } + } + companion object { val TAG = "PolycentricStorage"; private var _instance : PolycentricStorage? = null; diff --git a/app/src/main/java/com/futo/platformplayer/sync/GJSyncOpcodes.kt b/app/src/main/java/com/futo/platformplayer/sync/GJSyncOpcodes.kt new file mode 100644 index 00000000..469362a6 --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/sync/GJSyncOpcodes.kt @@ -0,0 +1,11 @@ +package com.futo.platformplayer.sync.internal + +class GJSyncOpcodes { + companion object { + val sendToDevices: UByte = 101.toUByte(); + + val syncExport: UByte = 201.toUByte(); + val syncSubscriptions: UByte = 202.toUByte(); + val syncHistory: UByte = 203.toUByte(); + } +} \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/sync/models/SendToDevicePackage.kt b/app/src/main/java/com/futo/platformplayer/sync/models/SendToDevicePackage.kt new file mode 100644 index 00000000..6c0ea9ba --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/sync/models/SendToDevicePackage.kt @@ -0,0 +1,9 @@ +package com.futo.platformplayer.sync.models + +import kotlinx.serialization.Serializable + +@Serializable +class SendToDevicePackage( + var url: String, + var position: Int = 0 +) \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/sync/models/SyncSubscriptionsPackage.kt b/app/src/main/java/com/futo/platformplayer/sync/models/SyncSubscriptionsPackage.kt new file mode 100644 index 00000000..d8a20e7a --- /dev/null +++ b/app/src/main/java/com/futo/platformplayer/sync/models/SyncSubscriptionsPackage.kt @@ -0,0 +1,12 @@ +package com.futo.platformplayer.sync.models + +import com.futo.platformplayer.models.Subscription +import kotlinx.serialization.Serializable +import java.time.OffsetDateTime +import java.util.Dictionary + +@Serializable +class SyncSubscriptionsPackage( + var subscriptions: List, + var subscriptionRemovals: Map +) \ No newline at end of file diff --git a/app/src/main/java/com/futo/platformplayer/views/platform/PlatformLinkView.kt b/app/src/main/java/com/futo/platformplayer/views/platform/PlatformLinkView.kt index 8a685dbd..3d7e42ce 100644 --- a/app/src/main/java/com/futo/platformplayer/views/platform/PlatformLinkView.kt +++ b/app/src/main/java/com/futo/platformplayer/views/platform/PlatformLinkView.kt @@ -42,11 +42,17 @@ class PlatformLinkView : LinearLayout { } fun setPlatform(name: String, url: String) { - val icon = StatePlatform.instance.getClientOrNullByUrl(url)?.icon; - if (icon != null) { - icon.setImageView(_imagePlatform, R.drawable.ic_web_white); - } else { - _imagePlatform.setImageResource(R.drawable.ic_web_white); + + if(url.startsWith("https://harbor.social")) { + _imagePlatform.setImageResource(R.drawable.neopass); + } + else { + val icon = StatePlatform.instance.getClientOrNullByUrl(url)?.icon; + if (icon != null) { + icon.setImageView(_imagePlatform, R.drawable.ic_web_white); + } else { + _imagePlatform.setImageResource(R.drawable.ic_web_white); + } } _textName.text = name; diff --git a/app/src/main/res/layout/activity_polycentric_create_profile.xml b/app/src/main/res/layout/activity_polycentric_create_profile.xml index 2343616b..787e3efd 100644 --- a/app/src/main/res/layout/activity_polycentric_create_profile.xml +++ b/app/src/main/res/layout/activity_polycentric_create_profile.xml @@ -92,5 +92,13 @@ android:textSize="16dp" android:text="@string/create_profile" /> + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_polycentric_home.xml b/app/src/main/res/layout/activity_polycentric_home.xml index 3b845099..36e206cd 100644 --- a/app/src/main/res/layout/activity_polycentric_home.xml +++ b/app/src/main/res/layout/activity_polycentric_home.xml @@ -45,33 +45,43 @@ app:layout_constraintLeft_toLeftOf="@id/image_polycentric" app:layout_constraintRight_toRightOf="@id/image_polycentric" /> - + app:layout_constraintTop_toBottomOf="@id/text_polycentric"> - + android:paddingTop="20dp" + android:paddingBottom="20dp" + android:orientation="vertical"> - - + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_polycentric_profile.xml b/app/src/main/res/layout/activity_polycentric_profile.xml index 97c9af8d..c0660189 100644 --- a/app/src/main/res/layout/activity_polycentric_profile.xml +++ b/app/src/main/res/layout/activity_polycentric_profile.xml @@ -65,6 +65,22 @@ app:layout_constraintTop_toBottomOf="@id/edit_profile_name" app:layout_constraintStart_toStartOf="parent" app:layout_constraintEnd_toEndOf="parent" /> + + +