diff --git a/.github/workflows/android-build.yml b/.github/workflows/android-build.yml
new file mode 100644
index 00000000..f9b6f624
--- /dev/null
+++ b/.github/workflows/android-build.yml
@@ -0,0 +1,42 @@
+name: Android Build
+
+on:
+ pull_request:
+ branches: [ "**" ]
+
+jobs:
+ build:
+ name: Build and Test
+ runs-on: ubuntu-latest
+
+ steps:
+ - name: Checkout code
+ uses: actions/checkout@v4
+
+ - name: Set up JDK 17
+ uses: actions/setup-java@v4
+ with:
+ distribution: 'temurin'
+ java-version: '17'
+
+ - name: Create local.properties
+ run: |
+ echo "MIXPANEL_KEY=${{ secrets.MIXPANEL_KEY }}" >> local.properties
+ echo "STOREFILE=${{ secrets.STOREFILE }}" >> local.properties
+ echo "STOREPASSWORD=${{ secrets.STOREPASSWORD }}" >> local.properties
+ echo "KEYALIAS=${{ secrets.KEYALIAS }}" >> local.properties
+ echo "KEYPASSWORD=${{ secrets.KEYPASSWORD }}" >> local.properties
+
+ - name: Setup Android SDK
+ uses: android-actions/setup-android@v3
+
+ - name: Build and Test
+ uses: gradle/gradle-build-action@v3
+ with:
+ arguments: |
+ assembleDebug
+ assembleRelease
+ lintDebug
+ lintRelease
+ test
+ cache-read-only: false
\ No newline at end of file
diff --git a/.github/workflows/detekt.yml b/.github/workflows/detekt.yml
index 89690488..1bef5e74 100644
--- a/.github/workflows/detekt.yml
+++ b/.github/workflows/detekt.yml
@@ -16,6 +16,7 @@ on:
jobs:
detekt:
+ concurrency: detekt-${{ github.ref }}
name: Static Code Analysis with Detekt
runs-on: ubuntu-latest
diff --git a/app/build.gradle.kts b/app/build.gradle.kts
index 6457c7c4..6689d091 100644
--- a/app/build.gradle.kts
+++ b/app/build.gradle.kts
@@ -64,10 +64,29 @@ android {
compose = true
}
+ signingConfigs {
+ create("release") {
+ val props = loadLocalProperties()
+ storeFile = file(props["STOREFILE"] as? String ?: "")
+ storePassword = props["STOREPASSWORD"] as? String ?: ""
+ keyAlias = props["KEYALIAS"] as? String ?: ""
+ keyPassword = props["KEYPASSWORD"] as? String ?: ""
+ }
+
+ getByName("debug") {
+ val props = loadLocalProperties()
+ storeFile = file(props["STOREFILE"] as? String ?: "")
+ storeFile = props["STOREFILE"]?.let { file(it) }
+ storePassword = props["STOREPASSWORD"] as? String ?: ""
+ keyAlias = props["KEYALIAS"] as? String ?: ""
+ keyPassword = props["KEYPASSWORD"] as? String ?: ""
+ }
+ }
+
buildTypes {
getByName("release") {
- signingConfig = signingConfigs.getByName("debug")
+ signingConfig = signingConfigs.getByName("release")
isMinifyEnabled = false
isShrinkResources = false
applicationIdSuffix = ".release"
@@ -78,16 +97,7 @@ android {
signingConfig = signingConfigs.getByName("debug")
applicationIdSuffix = ".debug"
isMinifyEnabled = false
- }
- }
-
- signingConfigs {
- getByName("debug") {
- val props = loadLocalProperties()
- storeFile = file(props["STOREFILE"] as? String ?: "")
- storePassword = props["STOREPASSWORD"] as? String ?: ""
- keyAlias = props["KEYALIAS"] as? String ?: ""
- keyPassword = props["KEYPASSWORD"] as? String ?: ""
+ buildConfigField("Boolean", "ENABLE_BUILD_CACHE", "true")
}
}
@@ -241,9 +251,9 @@ dependencies {
implementation("com.google.api-client:google-api-client-android:1.26.0")
implementation("com.google.apis:google-api-services-drive:v3-rev136-1.25.0")
- // Tor Libraries
- implementation(libs.tor.android)
- implementation(libs.jtorctl)
+ // Internal Tor Libraries
+ //implementation(libs.tor.android)
+ //implementation(libs.jtorctl)
implementation(libs.bitcoinj.core)
implementation("com.eclipsesource.j2v8:j2v8:6.2.1@aar")
@@ -275,7 +285,7 @@ dependencies {
implementation("com.github.derlio:audio-waveform:v1.0.1")
implementation(libs.clean.insights)
- implementation(libs.netcipher)
+ implementation(fileTree("libs"))
// Mixpanel analytics
implementation(libs.mixpanel)
diff --git a/app/detekt-baseline.xml b/app/detekt-baseline.xml
index e46e32ce..aa96555c 100644
--- a/app/detekt-baseline.xml
+++ b/app/detekt-baseline.xml
@@ -389,7 +389,6 @@
FinalNewline:VideoRequestHandler.kt$net.opendasharchive.openarchive.fragments.VideoRequestHandler.kt
FinalNewline:ViewExtension.kt$net.opendasharchive.openarchive.extensions.ViewExtension.kt
FinalNewline:WebDAVModel.kt$net.opendasharchive.openarchive.db.WebDAVModel.kt
- FinalNewline:WebDavActivity.kt$net.opendasharchive.openarchive.services.webdav.WebDavActivity.kt
FinalNewline:WebDavConduit.kt$net.opendasharchive.openarchive.services.webdav.WebDavConduit.kt
FinalNewline:WebDavFragment.kt$net.opendasharchive.openarchive.services.webdav.WebDavFragment.kt
FinalNewline:WebDavSetupLicenseFragment.kt$net.opendasharchive.openarchive.services.webdav.WebDavSetupLicenseFragment.kt
diff --git a/app/libs/netcipher.aar b/app/libs/netcipher.aar
new file mode 100644
index 00000000..a7543670
Binary files /dev/null and b/app/libs/netcipher.aar differ
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 990ca5c9..55a5cc9a 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -105,7 +105,8 @@
android:name=".features.main.HomeActivity"
android:exported="true"
android:screenOrientation="portrait"
- android:theme="@style/SaveAppTheme.NoActionBar">
+ android:theme="@style/SaveAppTheme.NoActionBar"
+ tools:ignore="DiscouragedApi">
@@ -142,44 +143,10 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+ android:foregroundServiceType="dataSync" />
diff --git a/app/src/main/java/info/guardianproject/netcipher/client/StrongOkHttpClientBuilder.java b/app/src/main/java/info/guardianproject/netcipher/client/StrongOkHttpClientBuilder.java
new file mode 100644
index 00000000..dea2fe0b
--- /dev/null
+++ b/app/src/main/java/info/guardianproject/netcipher/client/StrongOkHttpClientBuilder.java
@@ -0,0 +1,116 @@
+/*
+ * Copyright 2012-2016 Nathan Freitas
+ * Copyright 2015 str4d
+ * Portions Copyright (c) 2016 CommonsWare, LLC
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package info.guardianproject.netcipher.client;
+
+import android.content.Context;
+import android.content.Intent;
+import javax.net.ssl.SSLSocketFactory;
+
+import okhttp3.OkHttpClient;
+import okhttp3.Request;
+
+/**
+ * Creates an OkHttpClient using NetCipher configuration. Use
+ * build() if you have no other OkHttpClient configuration
+ * that you need to perform. Or, use applyTo() to augment an
+ * existing OkHttpClient.Builder with NetCipher.
+ */
+public class StrongOkHttpClientBuilder extends
+ StrongBuilderBase {
+ /**
+ * Creates a StrongOkHttpClientBuilder using the strongest set
+ * of options for security. Use this if the strongest set of
+ * options is what you want; otherwise, create a
+ * builder via the constructor and configure it as you see fit.
+ *
+ * @param context any Context will do
+ * @return a configured StrongOkHttpClientBuilder
+ * @throws Exception
+ */
+ static public StrongOkHttpClientBuilder forMaxSecurity(Context context)
+ throws Exception {
+ return(new StrongOkHttpClientBuilder(context)
+ .withBestProxy());
+ }
+
+ /**
+ * Creates a builder instance.
+ *
+ * @param context any Context will do; builder will hold onto
+ * Application context
+ */
+ public StrongOkHttpClientBuilder(Context context) {
+ super(context);
+ }
+
+ /**
+ * Copy constructor.
+ *
+ * @param original builder to clone
+ */
+ public StrongOkHttpClientBuilder(StrongOkHttpClientBuilder original) {
+ super(original);
+ }
+
+ /**
+ * OkHttp3 does not support SOCKS proxies:
+ * https://github.com/square/okhttp/issues/2315
+ *
+ * @return false
+ */
+ @Override
+ public boolean supportsSocksProxy() {
+ return(false);
+ }
+
+ /**
+ * {@inheritDoc}
+ */
+ @Override
+ public OkHttpClient build(Intent status) {
+ return(applyTo(new OkHttpClient.Builder(), status).build());
+ }
+
+ /**
+ * Adds NetCipher configuration to an existing OkHttpClient.Builder,
+ * in case you have additional configuration that you wish to
+ * perform.
+ *
+ * @param builder a new or partially-configured OkHttpClient.Builder
+ * @return the same builder
+ */
+ public OkHttpClient.Builder applyTo(OkHttpClient.Builder builder, Intent status) {
+ SSLSocketFactory factory=buildSocketFactory();
+
+ if (factory!=null) {
+ builder.sslSocketFactory(factory);
+ }
+
+ return(builder
+ .proxy(buildProxy(status)));
+ }
+
+ @Override
+ protected String get(Intent status, OkHttpClient connection,
+ String url) throws Exception {
+ Request request=new Request.Builder().url(TOR_CHECK_URL).build();
+
+ return(connection.newCall(request).execute().body().string());
+ }
+}
diff --git a/app/src/main/java/net/opendasharchive/openarchive/SaveApp.kt b/app/src/main/java/net/opendasharchive/openarchive/SaveApp.kt
index fd0e7d09..b1b37769 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/SaveApp.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/SaveApp.kt
@@ -22,7 +22,6 @@ import net.opendasharchive.openarchive.core.logger.AppLogger
import net.opendasharchive.openarchive.features.settings.passcode.PasscodeManager
import net.opendasharchive.openarchive.util.Analytics
import net.opendasharchive.openarchive.util.Prefs
-import net.opendasharchive.openarchive.util.Theme
import org.koin.android.ext.koin.androidContext
import org.koin.android.ext.koin.androidLogger
import org.koin.core.context.startKoin
@@ -69,7 +68,7 @@ class SaveApp : SugarApp(), SingletonImageLoader.Factory {
Prefs.load(this)
applyTheme()
- if (Prefs.useTor) initNetCipher()
+ initNetCipher()
CleanInsightsManager.init(this)
@@ -78,13 +77,17 @@ class SaveApp : SugarApp(), SingletonImageLoader.Factory {
private fun initNetCipher() {
AppLogger.d("Initializing NetCipher client")
- val oh = OrbotHelper.get(this)
- if (BuildConfig.DEBUG) {
- oh.skipOrbotValidation()
+ OrbotHelper.get(this).apply {
+ if (BuildConfig.DEBUG) {
+ skipOrbotValidation()
+ }
+ init()
}
-// oh.init()
+ if (Prefs.useTor) {
+ OrbotHelper.requestStartTor(this@SaveApp)
+ }
}
private fun createSnowbirdNotificationChannel() {
diff --git a/app/src/main/java/net/opendasharchive/openarchive/core/di/CoreModule.kt b/app/src/main/java/net/opendasharchive/openarchive/core/di/CoreModule.kt
index d3156218..ff9d46ab 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/core/di/CoreModule.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/core/di/CoreModule.kt
@@ -1,14 +1,12 @@
package net.opendasharchive.openarchive.core.di
-import android.content.Context
-import com.google.api.services.drive.Drive
-import com.thegrizzlylabs.sardineandroid.impl.OkHttpSardine
-import net.opendasharchive.openarchive.features.core.dialog.DefaultResourceProvider
import net.opendasharchive.openarchive.features.core.dialog.DialogStateManager
import net.opendasharchive.openarchive.features.core.dialog.ResourceProvider
+import net.opendasharchive.openarchive.features.core.dialog.DefaultResourceProvider
import net.opendasharchive.openarchive.features.folders.BrowseFoldersViewModel
-import net.opendasharchive.openarchive.features.main.MainViewModel
import net.opendasharchive.openarchive.features.main.ui.HomeViewModel
+import net.opendasharchive.openarchive.features.main.MainViewModel
+import net.opendasharchive.openarchive.services.servicesModule
import org.koin.android.ext.koin.androidApplication
import org.koin.core.module.dsl.viewModel
import org.koin.dsl.module
@@ -27,10 +25,9 @@ val coreModule = module {
}
viewModel {
- BrowseFoldersViewModel(
- context = get()
- )
+ BrowseFoldersViewModel(get())
}
+ includes(servicesModule)
}
diff --git a/app/src/main/java/net/opendasharchive/openarchive/core/infrastructure/client/ClientResult.kt b/app/src/main/java/net/opendasharchive/openarchive/core/infrastructure/client/ClientResult.kt
index 1ad6c532..5b82be12 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/core/infrastructure/client/ClientResult.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/core/infrastructure/client/ClientResult.kt
@@ -10,17 +10,16 @@ import java.io.IOException
import kotlin.coroutines.resume
import kotlin.coroutines.resumeWithException
-suspend fun OkHttpClient.enqueueResult(
- request: Request,
- onResume: (Response) -> T
-) = suspendCancellableCoroutine { continuation ->
+suspend fun OkHttpClient.enqueueResult(
+ request: Request
+): Result = suspendCancellableCoroutine { continuation ->
newCall(request).enqueue(object : Callback {
override fun onFailure(call: Call, e: IOException) {
continuation.resumeWithException(e)
}
override fun onResponse(call: Call, response: Response) {
- continuation.resume(onResume(response))
+ continuation.resume(Result.success(response))
}
})
diff --git a/app/src/main/java/net/opendasharchive/openarchive/db/Space.kt b/app/src/main/java/net/opendasharchive/openarchive/db/Space.kt
index 562c287f..5b5de92b 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/db/Space.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/db/Space.kt
@@ -2,17 +2,14 @@ package net.opendasharchive.openarchive.db
import android.content.Context
import android.content.Intent
-import android.graphics.drawable.BitmapDrawable
import android.graphics.drawable.Drawable
import android.widget.ImageView
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.runtime.Composable
-import androidx.compose.ui.graphics.asImageBitmap
-import androidx.compose.ui.graphics.painter.BitmapPainter
import androidx.compose.ui.graphics.painter.Painter
-import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.painterResource
import androidx.core.content.ContextCompat
+import androidx.fragment.app.FragmentActivity
import com.github.abdularis.civ.AvatarImageView
import com.orm.SugarRecord
import net.opendasharchive.openarchive.R
@@ -20,7 +17,6 @@ import net.opendasharchive.openarchive.core.logger.AppLogger
import net.opendasharchive.openarchive.features.onboarding.SpaceSetupActivity
import net.opendasharchive.openarchive.services.gdrive.GDriveConduit
import net.opendasharchive.openarchive.services.internetarchive.IaConduit
-import net.opendasharchive.openarchive.util.DrawableUtil
import net.opendasharchive.openarchive.util.Prefs
import okhttp3.HttpUrl
import okhttp3.HttpUrl.Companion.toHttpUrlOrNull
@@ -76,10 +72,6 @@ data class Space(
RAVEN(5, "DWeb Service"),
}
- enum class IconStyle {
- SOLID, OUTLINE
- }
-
companion object {
fun getAll(): Iterator {
return findAll(Space::class.java)
@@ -131,6 +123,15 @@ data class Space(
activity.startActivity(Intent(activity, SpaceSetupActivity::class.java))
}
}
+
+ fun navigate(activity: FragmentActivity) {
+ if (getAll().hasNext()) {
+ activity.finish()
+ } else {
+ activity.finishAffinity()
+ activity.startActivity(Intent(activity, SpaceSetupActivity::class.java))
+ }
+ }
}
val friendlyName: String
@@ -151,7 +152,7 @@ data class Space(
var tType: Type
get() = Type.entries.first { it.id == type }
set(value) {
- type = (value ?: Type.WEBDAV).id
+ type = value.id
}
var license: String?
@@ -199,45 +200,30 @@ data class Space(
"space_id = ? AND description = ?",
id.toString(),
description
- ).size > 0
+ ).isNotEmpty()
}
- fun getAvatar(context: Context, style: IconStyle = IconStyle.SOLID): Drawable? {
-
-
- return when (tType) {
+ fun getAvatar(context: Context): Drawable? = when (tType) {
Type.WEBDAV -> ContextCompat.getDrawable(
context,
R.drawable.ic_private_server
- ) // ?.tint(color)
+ )
Type.INTERNET_ARCHIVE -> ContextCompat.getDrawable(
context,
R.drawable.ic_internet_archive
- ) // ?.tint(color)
+ )
Type.GDRIVE -> ContextCompat.getDrawable(
context,
R.drawable.logo_gdrive_outline
- ) // ?.tint(color)
-
- Type.RAVEN -> ContextCompat.getDrawable(context, R.drawable.snowbird) // ?.tint(color)
-
- else -> {
- val color = ContextCompat.getColor(context, R.color.colorOnBackground)
- BitmapDrawable(
- context.resources,
- DrawableUtil.createCircularTextDrawable(initial, color)
- )
- }
+ )
- }
+ Type.RAVEN -> ContextCompat.getDrawable(context, R.drawable.snowbird)
}
@Composable
- fun getAvatar(): Painter {
-
- return when (tType) {
+ fun getAvatar(): Painter = when (tType) {
Type.WEBDAV -> painterResource(R.drawable.ic_space_private_server)
Type.INTERNET_ARCHIVE -> painterResource(R.drawable.ic_space_interent_archive)
@@ -245,15 +231,8 @@ data class Space(
Type.GDRIVE -> painterResource(R.drawable.logo_gdrive_outline)
Type.RAVEN -> painterResource(R.drawable.ic_space_dweb)
- null -> {
- val context = LocalContext.current
- val color = ContextCompat.getColor(context, R.color.colorOnBackground)
- val bitmap = DrawableUtil.createCircularTextDrawable(initial, color)
- val imageBitmap = bitmap.asImageBitmap()
- BitmapPainter(imageBitmap)
- }
}
- }
+
fun setAvatar(view: ImageView) {
when (tType) {
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/core/BaseActivity.kt b/app/src/main/java/net/opendasharchive/openarchive/features/core/BaseActivity.kt
index 4f49271a..63699944 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/features/core/BaseActivity.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/core/BaseActivity.kt
@@ -6,6 +6,7 @@ import android.view.ViewGroup
import android.view.WindowManager
import androidx.appcompat.app.AppCompatActivity
import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.ViewCompositionStrategy
import com.google.android.material.appbar.MaterialToolbar
import net.opendasharchive.openarchive.R
import net.opendasharchive.openarchive.core.presentation.theme.SaveAppTheme
@@ -40,6 +41,7 @@ abstract class BaseActivity : AppCompatActivity() {
// Add ComposeView if not already present
if (rootView.findViewById(R.id.compose_dialog_host) == null) {
ComposeView(this).apply {
+ setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
id = R.id.compose_dialog_host
layoutParams = ViewGroup.LayoutParams(
ViewGroup.LayoutParams.MATCH_PARENT,
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/core/NavArgument.kt b/app/src/main/java/net/opendasharchive/openarchive/features/core/NavArgument.kt
new file mode 100644
index 00000000..ca16436d
--- /dev/null
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/core/NavArgument.kt
@@ -0,0 +1,9 @@
+package net.opendasharchive.openarchive.features.core
+
+object NavArgument {
+ const val START_DESTINATION = "start_destination"
+ const val SPACE_ID = "space_id"
+ const val FOLDER_ID = "folder_id"
+ const val FOLDER_NAME = "folder_name"
+ const val SHOW_ARCHIVED_FOLDERS = "show_archived_folders"
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/folders/AddFolderActivity.kt b/app/src/main/java/net/opendasharchive/openarchive/features/folders/AddFolderActivity.kt
deleted file mode 100644
index f04c5ee0..00000000
--- a/app/src/main/java/net/opendasharchive/openarchive/features/folders/AddFolderActivity.kt
+++ /dev/null
@@ -1,105 +0,0 @@
-package net.opendasharchive.openarchive.features.folders
-
-import android.content.Intent
-import android.os.Bundle
-import android.view.MenuItem
-import androidx.activity.compose.setContent
-import androidx.activity.result.ActivityResultLauncher
-import androidx.activity.result.contract.ActivityResultContracts
-import net.opendasharchive.openarchive.core.presentation.theme.SaveAppTheme
-import net.opendasharchive.openarchive.db.Space
-import net.opendasharchive.openarchive.features.core.BaseActivity
-import net.opendasharchive.openarchive.features.onboarding.SpaceSetupActivity
-
-class AddFolderActivity : BaseActivity() {
-
- companion object {
- const val EXTRA_FOLDER_ID = "folder_id"
- const val EXTRA_FOLDER_NAME = "folder_name"
- }
-
- private lateinit var mResultLauncher: ActivityResultLauncher
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- mResultLauncher =
- registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
- if (it.resultCode == RESULT_OK) {
- setResult(RESULT_OK, it.data)
- finish()
- } else {
- val name = it.data?.getStringExtra(EXTRA_FOLDER_NAME)
-
- if (!name.isNullOrBlank()) {
- val i = Intent(this, CreateNewFolderFragment::class.java)
- i.putExtra(EXTRA_FOLDER_NAME, name)
-
- mResultLauncher.launch(i)
- }
- }
- }
-
- //mBinding = ActivityAddFolderBinding.inflate(layoutInflater)
- //setContentView(mBinding.root)
-
-
- setContent {
-
- SaveAppTheme {
-
- AddFolderScreen(
-// onCreateFolder = {
-// setFolder(browse = false)
-// },
-// onBrowseFolders = {
-// setFolder(browse = true)
-// },
-// onNavigateBack = {
-// finish()
-// }
- )
- }
- }
-
-
-
-
- // We cannot browse the Internet Archive. Directly forward to creating a project,
- // as it doesn't make sense to show a one-option menu.
- if (Space.current?.tType == Space.Type.INTERNET_ARCHIVE) {
- //mBinding.browseFolderContainer.hide()
-
- finish()
- setFolder(false)
- }
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- android.R.id.home -> {
- finish()
-
- return true
- }
- }
-
- return super.onOptionsItemSelected(item)
- }
-
- private fun setFolder(browse: Boolean) {
- if (Space.current == null) {
- finish()
- startActivity(Intent(this, SpaceSetupActivity::class.java))
-
- return
- }
-
- mResultLauncher.launch(
- Intent(
- this,
- if (browse) BrowseFoldersFragment::class.java else CreateNewFolderFragment::class.java
- )
- )
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/folders/AddFolderScreen.kt b/app/src/main/java/net/opendasharchive/openarchive/features/folders/AddFolderScreen.kt
index f7dd6932..04869121 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/features/folders/AddFolderScreen.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/folders/AddFolderScreen.kt
@@ -43,6 +43,8 @@ import net.opendasharchive.openarchive.R
import net.opendasharchive.openarchive.core.presentation.theme.DefaultScaffoldPreview
import net.opendasharchive.openarchive.core.presentation.theme.SaveAppTheme
+// This screen is used in space_setup_navigation.xml nav graph
+@Suppress("unused")
@Composable
fun AddFolderScreen() {
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/folders/BrowseFolderScreen.kt b/app/src/main/java/net/opendasharchive/openarchive/features/folders/BrowseFolderScreen.kt
index 67dd1470..930c82c5 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/features/folders/BrowseFolderScreen.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/folders/BrowseFolderScreen.kt
@@ -27,6 +27,7 @@ import net.opendasharchive.openarchive.R
import net.opendasharchive.openarchive.core.presentation.theme.DefaultScaffoldPreview
import org.koin.androidx.compose.koinViewModel
import java.util.Date
+import net.opendasharchive.openarchive.features.folders.BrowseFoldersViewModel.Folder
@Composable
fun BrowseFolderScreen(
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/folders/BrowseFoldersAdapter.kt b/app/src/main/java/net/opendasharchive/openarchive/features/folders/BrowseFoldersAdapter.kt
index 699fb3ca..e14a5f8c 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/features/folders/BrowseFoldersAdapter.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/folders/BrowseFoldersAdapter.kt
@@ -7,6 +7,7 @@ import androidx.core.content.ContextCompat
import androidx.recyclerview.widget.RecyclerView
import net.opendasharchive.openarchive.R
import net.opendasharchive.openarchive.databinding.FolderRowBinding
+import net.opendasharchive.openarchive.features.folders.BrowseFoldersViewModel.Folder
import java.text.SimpleDateFormat
class BrowseFoldersAdapter(
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/folders/BrowseFoldersFragment.kt b/app/src/main/java/net/opendasharchive/openarchive/features/folders/BrowseFoldersFragment.kt
index 042c8a81..181c5a2f 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/features/folders/BrowseFoldersFragment.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/folders/BrowseFoldersFragment.kt
@@ -1,6 +1,5 @@
package net.opendasharchive.openarchive.features.folders
-import android.app.Activity
import android.app.Activity.RESULT_OK
import android.content.Intent
import android.os.Bundle
@@ -18,7 +17,9 @@ import net.opendasharchive.openarchive.databinding.FragmentBrowseFoldersBinding
import net.opendasharchive.openarchive.db.Project
import net.opendasharchive.openarchive.db.Space
import net.opendasharchive.openarchive.features.core.BaseFragment
+import net.opendasharchive.openarchive.features.core.NavArgument
import net.opendasharchive.openarchive.features.core.dialog.showSuccessDialog
+import net.opendasharchive.openarchive.features.folders.BrowseFoldersViewModel.Folder
import net.opendasharchive.openarchive.util.extensions.toggle
import org.koin.androidx.viewmodel.ext.android.viewModel
import java.util.Date
@@ -98,7 +99,7 @@ class BrowseFoldersFragment : BaseFragment(), MenuProvider {
private fun navigateBackWithResult(projectId: Long) {
requireActivity().setResult(RESULT_OK, Intent().apply {
- putExtra(AddFolderActivity.EXTRA_FOLDER_ID, projectId)
+ putExtra(NavArgument.FOLDER_ID, projectId)
})
requireActivity().finish()
}
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/folders/BrowseFoldersViewModel.kt b/app/src/main/java/net/opendasharchive/openarchive/features/folders/BrowseFoldersViewModel.kt
index 0f1055df..de2eae3d 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/features/folders/BrowseFoldersViewModel.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/folders/BrowseFoldersViewModel.kt
@@ -11,18 +11,21 @@ import kotlinx.coroutines.withContext
import net.opendasharchive.openarchive.db.Space
import net.opendasharchive.openarchive.services.SaveClient
import net.opendasharchive.openarchive.services.gdrive.GDriveConduit
+import org.koin.java.KoinJavaComponent.inject
import timber.log.Timber
import java.io.IOException
import java.util.Date
-data class Folder(val name: String, val modified: Date)
-
class BrowseFoldersViewModel(private val context: Context) : ViewModel() {
+ data class Folder(val name: String, val modified: Date)
+
private val mFolders = MutableLiveData>()
+ private val client: SaveClient by inject(SaveClient::class.java)
+
val folders: LiveData>
get() = mFolders
@@ -35,7 +38,7 @@ class BrowseFoldersViewModel(private val context: Context) : ViewModel() {
try {
val value = withContext(Dispatchers.IO) {
when (space.tType) {
- Space.Type.WEBDAV -> getWebDavFolders(context, space)
+ Space.Type.WEBDAV -> getWebDavFolders(space)
Space.Type.GDRIVE -> getGDriveFolders(context, space)
@@ -57,10 +60,10 @@ class BrowseFoldersViewModel(private val context: Context) : ViewModel() {
}
@Throws(IOException::class)
- private suspend fun getWebDavFolders(context: Context, space: Space): List {
+ private fun getWebDavFolders(space: Space): List {
val root = space.hostUrl?.encodedPath
- return SaveClient.getSardine(context, space).list(space.host)?.mapNotNull {
+ return client.webdav(space).list(space.host)?.mapNotNull {
if (it?.isDirectory == true && it.path != root) {
Folder(it.name, it.modified ?: Date())
}
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/folders/CreateNewFolderFragment.kt b/app/src/main/java/net/opendasharchive/openarchive/features/folders/CreateNewFolderFragment.kt
index 7386f33b..08b48214 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/features/folders/CreateNewFolderFragment.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/folders/CreateNewFolderFragment.kt
@@ -18,6 +18,7 @@ import net.opendasharchive.openarchive.databinding.FragmentCreateNewFolderBindin
import net.opendasharchive.openarchive.db.Project
import net.opendasharchive.openarchive.db.Space
import net.opendasharchive.openarchive.features.core.BaseFragment
+import net.opendasharchive.openarchive.features.core.NavArgument
import net.opendasharchive.openarchive.features.core.dialog.showSuccessDialog
import net.opendasharchive.openarchive.features.settings.CreativeCommonsLicenseManager
import net.opendasharchive.openarchive.util.extensions.hide
@@ -45,7 +46,7 @@ class CreateNewFolderFragment : BaseFragment() {
super.onViewCreated(view, savedInstanceState)
val intent = requireActivity().intent
- binding.newFolder.setText(intent.getStringExtra(AddFolderActivity.EXTRA_FOLDER_NAME))
+ binding.newFolder.setText(intent.getStringExtra(NavArgument.FOLDER_NAME))
binding.newFolder.setOnEditorActionListener { _, actionId, _ ->
if (actionId == EditorInfo.IME_ACTION_DONE) {
@@ -142,7 +143,7 @@ class CreateNewFolderFragment : BaseFragment() {
private fun navigateBackWithResult(projectId: Long) {
val i = Intent()
- i.putExtra(AddFolderActivity.EXTRA_FOLDER_ID, projectId)
+ i.putExtra(NavArgument.FOLDER_ID, projectId)
requireActivity().setResult(RESULT_OK, i)
requireActivity().finish()
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/infrastructure/datasource/InternetArchiveRemoteSource.kt b/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/infrastructure/datasource/InternetArchiveRemoteSource.kt
index 3e1bb47e..1ffb77eb 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/infrastructure/datasource/InternetArchiveRemoteSource.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/infrastructure/datasource/InternetArchiveRemoteSource.kt
@@ -1,7 +1,5 @@
package net.opendasharchive.openarchive.features.internetarchive.infrastructure.datasource
-import android.content.Context
-import net.opendasharchive.openarchive.core.infrastructure.client.enqueueResult
import net.opendasharchive.openarchive.features.internetarchive.InternetArchiveGson
import net.opendasharchive.openarchive.features.internetarchive.domain.model.InternetArchive
import net.opendasharchive.openarchive.features.internetarchive.infrastructure.model.InternetArchiveLoginRequest
@@ -14,11 +12,11 @@ import okhttp3.Request
private const val LOGIN_URI = "https://archive.org/services/xauthn?op=login"
class InternetArchiveRemoteSource(
- private val context: Context,
+ private val client: SaveClient,
private val gson: InternetArchiveGson
) {
suspend fun login(request: InternetArchiveLoginRequest): Result =
- SaveClient.get(context).enqueueResult(
+ client.enqueue(
Request.Builder()
.url(LOGIN_URI)
.post(
@@ -27,22 +25,21 @@ class InternetArchiveRemoteSource(
.add("password", request.password).build()
)
.build()
- ) { response ->
- val data = gson.fromJson(
+ ).map { response ->
+ gson.fromJson(
response.body?.string(),
InternetArchiveLoginResponse::class.java
)
- Result.success(data)
}
suspend fun testConnection(auth: InternetArchive.Auth): Result =
- SaveClient.get(context).enqueueResult(
+ client.enqueue(
Request.Builder()
.url(ARCHIVE_API_ENDPOINT)
.method("GET", null)
.addHeader("Authorization", "LOW ${auth.access}:${auth.secret}")
.build()
- ) { response ->
- Result.success(response.isSuccessful)
+ ).map { response ->
+ response.isSuccessful
}
}
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/InternetArchiveActivity.kt b/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/InternetArchiveActivity.kt
deleted file mode 100644
index 87c6aef3..00000000
--- a/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/InternetArchiveActivity.kt
+++ /dev/null
@@ -1,83 +0,0 @@
-package net.opendasharchive.openarchive.features.internetarchive.presentation
-
-import android.content.Intent
-import android.os.Bundle
-import androidx.activity.compose.setContent
-import androidx.appcompat.app.AppCompatActivity
-import androidx.compose.foundation.layout.Box
-import androidx.compose.foundation.layout.fillMaxSize
-import androidx.compose.foundation.layout.padding
-import androidx.compose.material3.Scaffold
-import androidx.compose.ui.Modifier
-import androidx.compose.ui.res.stringResource
-import net.opendasharchive.openarchive.R
-import net.opendasharchive.openarchive.core.presentation.theme.SaveAppTheme
-import net.opendasharchive.openarchive.db.Space
-import net.opendasharchive.openarchive.features.core.dialog.DialogHost
-import net.opendasharchive.openarchive.features.core.dialog.DialogStateManager
-import net.opendasharchive.openarchive.features.internetarchive.presentation.components.IAResult
-import net.opendasharchive.openarchive.features.internetarchive.presentation.components.getSpace
-import net.opendasharchive.openarchive.features.internetarchive.presentation.login.ComposeAppBar
-import net.opendasharchive.openarchive.features.main.MainActivity
-import org.koin.androidx.viewmodel.ext.android.viewModel
-
-@Deprecated("use jetpack compose")
-class InternetArchiveActivity : AppCompatActivity() {
-
- val dialogManager: DialogStateManager by viewModel()
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- val (space, isNewSpace) = intent.extras.getSpace(Space.Type.INTERNET_ARCHIVE)
-
- setContent {
-
- SaveAppTheme {
-
- DialogHost(dialogManager)
-
- Scaffold(
- topBar = {
- ComposeAppBar(
- title = stringResource(R.string.internet_archive),
- onNavigationAction = { onComplete(IAResult.Cancelled) }
- )
- }
- ) { paddingValues ->
- Box(modifier = Modifier
- .fillMaxSize()
- .padding(paddingValues)) {
- InternetArchiveScreen(space, isNewSpace) {
- onComplete(it)
- }
- }
- }
- }
-
-
- }
- }
-
- private fun onComplete(result: IAResult) {
- when (result) {
- IAResult.Saved -> {
- startActivity(Intent(this, MainActivity::class.java))
- // measureNewBackend(Space.Type.INTERNET_ARCHIVE)
- }
-
- IAResult.Deleted -> Space.navigate(this)
- IAResult.Cancelled -> onBackPressed()
- }
- }
-}
-
-//fun Activity.measureNewBackend(type: Space.Type) {
-// CleanInsightsManager.getConsent(this) {
-// CleanInsightsManager.measureEvent(
-// "backend",
-// "new",
-// type.friendlyName
-// )
-// }
-//}
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/InternetArchiveFragment.kt b/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/InternetArchiveFragment.kt
deleted file mode 100644
index c2a2fb53..00000000
--- a/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/InternetArchiveFragment.kt
+++ /dev/null
@@ -1,79 +0,0 @@
-package net.opendasharchive.openarchive.features.internetarchive.presentation
-
-import android.os.Bundle
-import android.view.LayoutInflater
-import android.view.View
-import android.view.ViewGroup
-import androidx.compose.ui.platform.ComposeView
-import androidx.core.os.bundleOf
-import androidx.fragment.app.setFragmentResult
-import androidx.navigation.fragment.findNavController
-import net.opendasharchive.openarchive.R
-import net.opendasharchive.openarchive.db.Space
-import net.opendasharchive.openarchive.features.internetarchive.presentation.components.IAResult
-import net.opendasharchive.openarchive.features.internetarchive.presentation.components.bundleWithNewSpace
-import net.opendasharchive.openarchive.features.internetarchive.presentation.components.bundleWithSpaceId
-import net.opendasharchive.openarchive.features.internetarchive.presentation.components.getSpace
-import net.opendasharchive.openarchive.features.core.BaseFragment
-import net.opendasharchive.openarchive.features.core.ToolbarConfigurable
-
-@Deprecated("only used for backward compatibility")
-class InternetArchiveFragment : BaseFragment(), ToolbarConfigurable {
-
- override fun onCreateView(
- inflater: LayoutInflater,
- container: ViewGroup?,
- savedInstanceState: Bundle?
- ): View {
-
- val (space, isNewSpace) = arguments.getSpace(Space.Type.INTERNET_ARCHIVE)
-
- return ComposeView(requireContext()).apply {
- setContent {
- InternetArchiveScreen(space, isNewSpace) { result ->
- finish(result)
- }
- }
- }
- }
-
- private fun finish(result: IAResult) {
- if (isJetpackNavigation) {
- when (result) {
- IAResult.Saved -> {
- val message = getString(R.string.you_have_successfully_connected_to_the_internet_archive)
- val action = InternetArchiveFragmentDirections.actionFragmentInternetArchiveToFragmentSpaceSetupSuccess(message)
- findNavController().navigate(action)
- }
- IAResult.Deleted -> TODO()
- IAResult.Cancelled -> findNavController().popBackStack()
- }
- } else {
- setFragmentResult(result.value, bundleOf())
-
- if (result == IAResult.Saved) {
- // activity?.measureNewBackend(Space.Type.INTERNET_ARCHIVE)
- }
- }
- }
-
- companion object {
-
- val RESP_SAVED = IAResult.Saved.value
- val RESP_CANCEL = IAResult.Cancelled.value
-
- @JvmStatic
- fun newInstance(args: Bundle) = InternetArchiveFragment().apply {
- arguments = args
- }
-
- @JvmStatic
- fun newInstance(spaceId: Long) = newInstance(args = bundleWithSpaceId(spaceId))
-
- @JvmStatic
- fun newInstance() = newInstance(args = bundleWithNewSpace())
- }
-
- override fun getToolbarTitle() = getString(R.string.internet_archive)
- override fun shouldShowBackButton() = true
-}
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/InternetArchiveScreen.kt b/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/InternetArchiveScreen.kt
deleted file mode 100644
index 4429b4cd..00000000
--- a/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/InternetArchiveScreen.kt
+++ /dev/null
@@ -1,21 +0,0 @@
-package net.opendasharchive.openarchive.features.internetarchive.presentation
-
-import androidx.compose.runtime.Composable
-import net.opendasharchive.openarchive.core.presentation.theme.SaveAppTheme
-import net.opendasharchive.openarchive.db.Space
-import net.opendasharchive.openarchive.features.internetarchive.presentation.components.IAResult
-import net.opendasharchive.openarchive.features.internetarchive.presentation.details.InternetArchiveDetailsScreen
-import net.opendasharchive.openarchive.features.internetarchive.presentation.login.InternetArchiveLoginScreen
-
-@Composable
-fun InternetArchiveScreen(space: Space, isNewSpace: Boolean, onFinish: (IAResult) -> Unit) = SaveAppTheme {
- if (isNewSpace) {
- InternetArchiveLoginScreen(space) {
- onFinish(it)
- }
- } else {
- InternetArchiveDetailsScreen(space) {
- onFinish(it)
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/details/InternetArchiveDetailsFragment.kt b/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/details/InternetArchiveDetailsFragment.kt
new file mode 100644
index 00000000..fe71c7b2
--- /dev/null
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/details/InternetArchiveDetailsFragment.kt
@@ -0,0 +1,51 @@
+package net.opendasharchive.openarchive.features.internetarchive.presentation.details
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.compose.content
+import androidx.navigation.fragment.findNavController
+import net.opendasharchive.openarchive.R
+import net.opendasharchive.openarchive.core.presentation.theme.SaveAppTheme
+import net.opendasharchive.openarchive.db.Space
+import net.opendasharchive.openarchive.features.core.BaseFragment
+import net.opendasharchive.openarchive.features.internetarchive.presentation.components.IAResult
+import net.opendasharchive.openarchive.features.internetarchive.presentation.components.getSpace
+
+class InternetArchiveDetailsFragment: BaseFragment() {
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View = content {
+
+ val (space, isNewSpace) = arguments.getSpace(Space.Type.INTERNET_ARCHIVE)
+
+ SaveAppTheme {
+ InternetArchiveDetailsScreen(
+ space = space,
+ dialogManager = dialogManager,
+ onResult = { result ->
+ handleResult(result)
+ }
+ )
+ }
+
+ }
+
+ private fun handleResult(result: IAResult) {
+ when (result) {
+ IAResult.Saved -> TODO()
+ IAResult.Deleted -> {
+ findNavController().popBackStack()
+ }
+ IAResult.Cancelled -> {
+ findNavController().popBackStack()
+ }
+ }
+ }
+
+ override fun getToolbarTitle(): String = getString(R.string.internet_archive)
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/details/InternetArchiveDetailsScreen.kt b/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/details/InternetArchiveDetailsScreen.kt
index b006feb4..8cd15723 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/details/InternetArchiveDetailsScreen.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/details/InternetArchiveDetailsScreen.kt
@@ -32,13 +32,18 @@ import net.opendasharchive.openarchive.features.core.UiText
import net.opendasharchive.openarchive.features.core.dialog.DialogStateManager
import net.opendasharchive.openarchive.features.core.dialog.showDialog
import net.opendasharchive.openarchive.features.internetarchive.presentation.components.IAResult
+import net.opendasharchive.openarchive.features.internetarchive.presentation.components.InternetArchiveHeader
import net.opendasharchive.openarchive.features.internetarchive.presentation.details.InternetArchiveDetailsViewModel.Action
import net.opendasharchive.openarchive.features.internetarchive.presentation.login.CustomTextField
import org.koin.androidx.compose.koinViewModel
import org.koin.core.parameter.parametersOf
@Composable
-fun InternetArchiveDetailsScreen(space: Space, onResult: (IAResult) -> Unit) {
+fun InternetArchiveDetailsScreen(
+ space: Space,
+ onResult: (IAResult) -> Unit,
+ dialogManager: DialogStateManager,
+) {
val viewModel: InternetArchiveDetailsViewModel = koinViewModel {
parametersOf(space)
}
@@ -55,14 +60,36 @@ fun InternetArchiveDetailsScreen(space: Space, onResult: (IAResult) -> Unit) {
}
}
- InternetArchiveDetailsContent(state, viewModel::dispatch)
+ InternetArchiveDetailsContent(
+ state = state,
+ onRemove = {
+ dialogManager.showDialog(dialogManager.requireResourceProvider()) {
+ title = UiText.StringResource(R.string.remove_from_app)
+ message =
+ UiText.StringResource(R.string.are_you_sure_you_want_to_remove_this_server_from_the_app)
+ icon = UiImage.DrawableResource(R.drawable.ic_trash)
+ destructiveButton {
+ text = UiText.StringResource(R.string.remove)
+ action = {
+ viewModel.dispatch(Action.Remove)
+ }
+ }
+
+ neutralButton {
+ text = UiText.StringResource(R.string.action_cancel)
+ action = {
+ //dismiss
+ }
+ }
+ }
+ }
+ )
}
@Composable
private fun InternetArchiveDetailsContent(
state: InternetArchiveDetailsState,
- dispatch: Dispatch,
- dialogManager: DialogStateManager = koinViewModel()
+ onRemove: () -> Unit,
) {
Box(
@@ -73,7 +100,7 @@ private fun InternetArchiveDetailsContent(
Column {
- //InternetArchiveHeader()
+ Text(stringResource(R.string.account), fontSize = 18.sp, color = MaterialTheme.colorScheme.onSurface)
Spacer(Modifier.height(ThemeDimensions.spacing.large))
@@ -110,28 +137,7 @@ private fun InternetArchiveDetailsContent(
horizontalArrangement = Arrangement.Center
) {
TextButton(
- onClick = {
- //isRemoving = true
-
- dialogManager.showDialog(dialogManager.requireResourceProvider()) {
- title = UiText.StringResource(R.string.remove_from_app)
- message = UiText.StringResource(R.string.are_you_sure_you_want_to_remove_this_server_from_the_app)
- icon = UiImage.DrawableResource(R.drawable.ic_trash)
- destructiveButton {
- text = UiText.StringResource(R.string.remove)
- action = {
- dispatch(Action.Remove)
- }
- }
-
- neutralButton {
- text = UiText.StringResource(R.string.action_cancel)
- action = {
- //dismiss
- }
- }
- }
- },
+ onClick = onRemove,
colors = ButtonDefaults.textButtonColors(
contentColor = MaterialTheme.colorScheme.error
)
@@ -161,7 +167,7 @@ private fun InternetArchiveScreenPreview() {
userName = "@abc_name",
screenName = "ABC Name"
),
- dispatch = {}
+ onRemove = {}
)
}
}
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/login/InternetArchiveLoginFragment.kt b/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/login/InternetArchiveLoginFragment.kt
new file mode 100644
index 00000000..00794f8c
--- /dev/null
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/login/InternetArchiveLoginFragment.kt
@@ -0,0 +1,53 @@
+package net.opendasharchive.openarchive.features.internetarchive.presentation.login
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import androidx.fragment.compose.content
+import androidx.navigation.fragment.findNavController
+import net.opendasharchive.openarchive.R
+import net.opendasharchive.openarchive.core.presentation.theme.SaveAppTheme
+import net.opendasharchive.openarchive.db.Space
+import net.opendasharchive.openarchive.features.core.BaseFragment
+import net.opendasharchive.openarchive.features.internetarchive.presentation.components.IAResult
+import net.opendasharchive.openarchive.features.internetarchive.presentation.components.getSpace
+
+class InternetArchiveLoginFragment: BaseFragment() {
+
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View = content {
+
+ val (space, isNewSpace) = arguments.getSpace(Space.Type.INTERNET_ARCHIVE)
+ SaveAppTheme {
+ InternetArchiveLoginScreen(space) { result ->
+ handleResult(result)
+ }
+ }
+ }
+
+ private fun handleResult(result: IAResult) {
+ when (result) {
+ IAResult.Saved -> {
+ val message = getString(R.string.you_have_successfully_connected_to_the_internet_archive)
+ val action =
+ InternetArchiveLoginFragmentDirections.actionFragmentInternetArchiveLoginToFragmentSpaceSetupSuccess(
+ message = message,
+ )
+ findNavController().navigate(action)
+ }
+ IAResult.Deleted -> {
+ findNavController().popBackStack()
+ }
+ IAResult.Cancelled -> {
+ findNavController().popBackStack()
+ }
+ }
+ }
+
+ override fun getToolbarTitle(): String = getString(R.string.internet_archive)
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/login/InternetArchiveLoginState.kt b/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/login/InternetArchiveLoginState.kt
index 12bdc378..dcf2872f 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/login/InternetArchiveLoginState.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/internetarchive/presentation/login/InternetArchiveLoginState.kt
@@ -1,5 +1,6 @@
package net.opendasharchive.openarchive.features.internetarchive.presentation.login
+import androidx.compose.foundation.text.input.TextFieldState
import androidx.compose.runtime.Immutable
import net.opendasharchive.openarchive.features.internetarchive.domain.model.InternetArchive
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/main/MainActivity.kt b/app/src/main/java/net/opendasharchive/openarchive/features/main/MainActivity.kt
index 13215329..806e4bfa 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/features/main/MainActivity.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/main/MainActivity.kt
@@ -47,6 +47,7 @@ import net.opendasharchive.openarchive.db.Project
import net.opendasharchive.openarchive.db.Space
import net.opendasharchive.openarchive.extensions.getMeasurments
import net.opendasharchive.openarchive.features.core.BaseActivity
+import net.opendasharchive.openarchive.features.core.NavArgument
import net.opendasharchive.openarchive.features.core.UiImage
import net.opendasharchive.openarchive.features.core.UiText
import net.opendasharchive.openarchive.features.core.asUiImage
@@ -56,7 +57,6 @@ import net.opendasharchive.openarchive.features.core.dialog.DialogConfig
import net.opendasharchive.openarchive.features.core.dialog.DialogType
import net.opendasharchive.openarchive.features.core.dialog.showDialog
import net.opendasharchive.openarchive.features.core.dialog.showInfoDialog
-import net.opendasharchive.openarchive.features.folders.AddFolderActivity
import net.opendasharchive.openarchive.features.main.adapters.FolderDrawerAdapter
import net.opendasharchive.openarchive.features.main.adapters.FolderDrawerAdapterListener
import net.opendasharchive.openarchive.features.main.adapters.SpaceDrawerAdapter
@@ -134,7 +134,7 @@ class MainActivity : BaseActivity(), SpaceDrawerAdapterListener, FolderDrawerAda
private val mNewFolderResultLauncher =
registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if (it.resultCode == RESULT_OK) {
- refreshProjects(it.data?.getLongExtra(AddFolderActivity.EXTRA_FOLDER_ID, -1))
+ refreshProjects(it.data?.getLongExtra(NavArgument.FOLDER_ID, -1))
}
}
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/main/ui/MainMediaScreen.kt b/app/src/main/java/net/opendasharchive/openarchive/features/main/ui/MainMediaScreen.kt
index 64a64207..65cfaebb 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/features/main/ui/MainMediaScreen.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/main/ui/MainMediaScreen.kt
@@ -7,9 +7,7 @@ import android.os.Handler
import android.os.Looper
import androidx.compose.foundation.background
import androidx.compose.foundation.border
-import androidx.compose.foundation.clickable
import androidx.compose.foundation.gestures.detectTapGestures
-import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
@@ -22,9 +20,6 @@ import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
-import androidx.compose.foundation.lazy.grid.GridCells
-import androidx.compose.foundation.lazy.grid.LazyVerticalGrid
-import androidx.compose.foundation.lazy.grid.items
import androidx.compose.foundation.lazy.items
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Error
@@ -56,8 +51,6 @@ import net.opendasharchive.openarchive.db.Collection
import net.opendasharchive.openarchive.db.Media
import net.opendasharchive.openarchive.features.media.PreviewActivity
import net.opendasharchive.openarchive.upload.BroadcastManager
-import net.opendasharchive.openarchive.upload.UploadManagerActivity
-import org.koin.androidx.compose.koinViewModel
/**
* A data class representing one “section” (i.e. one Collection and its list of Media).
@@ -400,7 +393,7 @@ private fun handleMediaClick(context: Context, media: Media, onError: (Media) ->
Media.Status.Queued, Media.Status.Uploading -> {
// Start the upload manager activity
- context.startActivity(Intent(context, UploadManagerActivity::class.java))
+ //TODO: show the bottom sheet for EditUploads here
}
Media.Status.Error -> {
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/media/ReviewActivity.kt b/app/src/main/java/net/opendasharchive/openarchive/features/media/ReviewActivity.kt
index d04b1dd7..1bc50712 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/features/media/ReviewActivity.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/media/ReviewActivity.kt
@@ -2,6 +2,7 @@ package net.opendasharchive.openarchive.features.media
import android.content.Context
import android.content.Intent
+import android.net.Uri
import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
@@ -25,6 +26,7 @@ import net.opendasharchive.openarchive.util.extensions.hide
import net.opendasharchive.openarchive.util.extensions.show
import net.opendasharchive.openarchive.util.extensions.toggle
import java.text.NumberFormat
+import kotlin.arrayOf
import kotlin.math.max
import kotlin.math.min
@@ -60,6 +62,12 @@ class ReviewActivity : BaseActivity(), View.OnClickListener {
private val mMedia
get() = mStore.getOrNull(mIndex)
+ private val mPicasso by lazy {
+ Picasso.Builder(this)
+ .addRequestHandler(VideoRequestHandler(this))
+ .build()
+ }
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
@@ -323,9 +331,7 @@ class ReviewActivity : BaseActivity(), View.OnClickListener {
.into(imageView)
}
else if (media?.mimeType?.startsWith("video") == true) {
- Picasso.Builder(this)
- .addRequestHandler(VideoRequestHandler(this))
- .build()
+ mPicasso
.load(VideoRequestHandler.SCHEME_VIDEO + ":" + media.originalFilePath)
?.fit()
?.centerCrop()
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/onboarding/SpaceSetupActivity.kt b/app/src/main/java/net/opendasharchive/openarchive/features/onboarding/SpaceSetupActivity.kt
index 144d87a7..c4e732cd 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/features/onboarding/SpaceSetupActivity.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/onboarding/SpaceSetupActivity.kt
@@ -1,6 +1,9 @@
package net.opendasharchive.openarchive.features.onboarding
+import android.content.Intent
import android.os.Bundle
+import androidx.annotation.IdRes
+import androidx.core.bundle.bundleOf
import androidx.fragment.app.Fragment
import androidx.navigation.NavController
import androidx.navigation.NavGraph
@@ -11,12 +14,14 @@ import androidx.navigation.ui.setupActionBarWithNavController
import net.opendasharchive.openarchive.R
import net.opendasharchive.openarchive.databinding.ActivitySpaceSetupBinding
import net.opendasharchive.openarchive.features.core.BaseActivity
+import net.opendasharchive.openarchive.features.core.NavArgument
import net.opendasharchive.openarchive.features.core.ToolbarConfigurable
enum class StartDestination {
SPACE_TYPE,
SPACE_LIST,
DWEB_DASHBOARD,
+ FOLDER_LIST,
ADD_FOLDER,
ADD_NEW_FOLDER
}
@@ -60,31 +65,39 @@ class SpaceSetupActivity : BaseActivity() {
initSpaceSetupNavigation()
}
+ override fun onNewIntent(intent: Intent) {
+ super.onNewIntent(intent)
+ navController.handleDeepLink(intent)
+ }
+
private fun initSpaceSetupNavigation() {
- val navHostFragment =
+ val navHost =
supportFragmentManager.findFragmentById(R.id.space_nav_host_fragment) as NavHostFragment
- navController = navHostFragment.navController
+ navController = navHost.navController
navGraph = navController.navInflater.inflate(R.navigation.space_setup_navigation)
- val startDestinationString =
- intent.getStringExtra("start_destination") ?: StartDestination.SPACE_TYPE.name
- val startDestination = StartDestination.valueOf(startDestinationString)
- when (startDestination) {
- StartDestination.SPACE_LIST -> {
- navGraph.setStartDestination(R.id.fragment_space_list)
- }
- StartDestination.ADD_FOLDER -> {
- navGraph.setStartDestination(R.id.fragment_add_folder)
- }
- StartDestination.ADD_NEW_FOLDER -> {
- navGraph.setStartDestination(R.id.fragment_create_new_folder)
- }
- else -> {
- navGraph.setStartDestination(R.id.fragment_space_setup)
- }
+ val startDestName =
+ intent.getStringExtra(NavArgument.START_DESTINATION) ?: StartDestination.SPACE_TYPE.name
+ val startDestination = StartDestination.valueOf(startDestName)
+ @IdRes val startDest = when (startDestination) {
+ StartDestination.SPACE_TYPE -> R.id.fragment_space_setup
+ StartDestination.SPACE_LIST -> R.id.fragment_space_list
+ StartDestination.DWEB_DASHBOARD -> R.id.fragment_snowbird
+ StartDestination.FOLDER_LIST -> R.id.fragment_folder_list
+ StartDestination.ADD_FOLDER -> R.id.fragment_add_folder
+ StartDestination.ADD_NEW_FOLDER -> R.id.fragment_create_new_folder
}
- navController.graph = navGraph
+
+ val startArgs: Bundle? = if (startDestination == StartDestination.FOLDER_LIST) {
+ bundleOf(NavArgument.SHOW_ARCHIVED_FOLDERS to intent.getBooleanExtra(NavArgument.SHOW_ARCHIVED_FOLDERS, false))
+ bundleOf(NavArgument.SPACE_ID to intent.getLongExtra(NavArgument.SPACE_ID, -1L))
+ bundleOf(NavArgument.FOLDER_ID to intent.getLongExtra(NavArgument.FOLDER_ID, -1L))
+ } else null
+
+ navGraph.setStartDestination(startDest)
+
+ navController.setGraph(navGraph, startArgs)
appBarConfiguration = AppBarConfiguration(emptySet())
setupActionBarWithNavController(navController, appBarConfiguration)
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/settings/EditFolderActivity.kt b/app/src/main/java/net/opendasharchive/openarchive/features/settings/EditFolderActivity.kt
deleted file mode 100644
index 9f0ef994..00000000
--- a/app/src/main/java/net/opendasharchive/openarchive/features/settings/EditFolderActivity.kt
+++ /dev/null
@@ -1,134 +0,0 @@
-package net.opendasharchive.openarchive.features.settings
-
-import android.os.Bundle
-import android.view.MenuItem
-import android.view.inputmethod.EditorInfo
-import net.opendasharchive.openarchive.R
-import net.opendasharchive.openarchive.databinding.ActivityEditFolderBinding
-import net.opendasharchive.openarchive.db.Project
-import net.opendasharchive.openarchive.features.core.BaseActivity
-import net.opendasharchive.openarchive.features.core.UiImage
-import net.opendasharchive.openarchive.features.core.UiText
-import net.opendasharchive.openarchive.features.core.dialog.DialogType
-import net.opendasharchive.openarchive.features.core.dialog.showDialog
-import net.opendasharchive.openarchive.util.extensions.Position
-import net.opendasharchive.openarchive.util.extensions.setDrawable
-
-class EditFolderActivity : BaseActivity() {
-
- companion object {
- const val EXTRA_CURRENT_PROJECT_ID = "archive_extra_current_project_id"
- }
-
- private lateinit var mProject: Project
- private lateinit var mBinding: ActivityEditFolderBinding
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- val project = Project.getById(intent.getLongExtra(EXTRA_CURRENT_PROJECT_ID, -1L))
- ?: return finish()
-
- mProject = project
-
- mBinding = ActivityEditFolderBinding.inflate(layoutInflater)
- setContentView(mBinding.root)
-
-
- setupToolbar("Edit Folder")
-
- mBinding.folderName.setOnEditorActionListener { _, actionId, _ ->
- if (actionId == EditorInfo.IME_ACTION_DONE) {
- val newName = mBinding.folderName.text.toString()
-
- if (newName.isNotBlank()) {
- mProject.description = newName
- mProject.save()
-
- supportActionBar?.title = newName
- mBinding.folderName.hint = newName
-
-
- setupToolbar(newName)
- }
- }
-
- false
- }
-
- mBinding.btRemove.setOnClickListener {
- showDeleteFolderConfirmDialog()
- }
-
- mBinding.btArchive.setOnClickListener {
- archiveProject()
- }
-
- CreativeCommonsLicenseManager.initialize(mBinding.cc, null) {
- mProject.licenseUrl = it
- mProject.save()
- }
-
- updateUi()
- }
-
- private fun showDeleteFolderConfirmDialog() {
- dialogManager.showDialog(dialogManager.requireResourceProvider()) {
- type = DialogType.Error
- icon = UiImage.DrawableResource(R.drawable.ic_trash)
- title = UiText.StringResource(R.string.remove_from_app)
- message = UiText.StringResource(R.string.action_remove_project)
- destructiveButton {
- text = UiText.StringResource(R.string.remove)
- action = {
- mProject.delete()
- finish()
- }
- }
- neutralButton {
- text = UiText.StringResource(R.string.lbl_Cancel)
- action = {
- dialogManager.dismissDialog()
- }
- }
- }
- }
-
- private fun archiveProject() {
- mProject.isArchived = !mProject.isArchived
- mProject.save()
-
- updateUi()
- }
-
- private fun updateUi() {
- supportActionBar?.title = mProject.description
-
- mBinding.folderName.isEnabled = !mProject.isArchived
- mBinding.folderName.hint = mProject.description
- mBinding.folderName.setText(mProject.description)
-
- mBinding.btArchive.setText(if (mProject.isArchived)
- R.string.action_unarchive_project else
- R.string.action_archive_project)
-
- val global = mProject.space?.license != null
-
- if (global) {
- mBinding.cc.tvCcLabel.setText(R.string.set_the_same_creative_commons_license_for_all_folders_on_this_server)
- }
-
- CreativeCommonsLicenseManager.initialize(mBinding.cc, mProject.licenseUrl, !mProject.isArchived && !global)
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- android.R.id.home -> {
- finish()
- return true
- }
- }
-
- return super.onOptionsItemSelected(item)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/settings/EditFolderFragment.kt b/app/src/main/java/net/opendasharchive/openarchive/features/settings/EditFolderFragment.kt
new file mode 100644
index 00000000..16cfb915
--- /dev/null
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/settings/EditFolderFragment.kt
@@ -0,0 +1,135 @@
+package net.opendasharchive.openarchive.features.settings
+
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.View
+import android.view.ViewGroup
+import android.view.inputmethod.EditorInfo
+import androidx.navigation.fragment.findNavController
+import net.opendasharchive.openarchive.R
+import net.opendasharchive.openarchive.databinding.FragmentEditFolderBinding
+import net.opendasharchive.openarchive.db.Project
+import net.opendasharchive.openarchive.features.core.BaseFragment
+import net.opendasharchive.openarchive.features.core.NavArgument
+import net.opendasharchive.openarchive.features.core.UiImage
+import net.opendasharchive.openarchive.features.core.UiText
+import net.opendasharchive.openarchive.features.core.dialog.DialogType
+import net.opendasharchive.openarchive.features.core.dialog.showDialog
+
+class EditFolderFragment : BaseFragment() {
+
+ private lateinit var project: Project
+ private lateinit var binding: FragmentEditFolderBinding
+
+ override fun getToolbarTitle(): String = "Edit Folder"
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ arguments?.let {
+ val folderId = it.getLong(NavArgument.FOLDER_ID)
+ if (folderId == -1L) {
+ throw IllegalArgumentException("Folder ID cannot be -1")
+ }
+ project = Project.getById(folderId) ?:
+ throw IllegalArgumentException("Project not found for ID: $folderId")
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentEditFolderBinding.inflate(inflater, container, false)
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ binding.folderName.setOnEditorActionListener { _, actionId, _ ->
+ if (actionId == EditorInfo.IME_ACTION_DONE) {
+ val newName = binding.folderName.text.toString()
+
+ if (newName.isNotBlank()) {
+ project.description = newName
+ project.save()
+
+ //TODO: update toolbar title
+ //supportActionBar?.title = newName
+ binding.folderName.hint = newName
+
+
+ //setupToolbar(newName)
+ }
+ }
+
+ false
+ }
+
+ binding.btRemove.setOnClickListener {
+ showDeleteFolderConfirmDialog()
+ }
+
+ binding.btArchive.setOnClickListener {
+ archiveProject()
+ }
+
+ CreativeCommonsLicenseManager.initialize(binding.cc, null) {
+ project.licenseUrl = it
+ project.save()
+ }
+
+ updateUi()
+ }
+
+ private fun showDeleteFolderConfirmDialog() {
+ dialogManager.showDialog(dialogManager.requireResourceProvider()) {
+ type = DialogType.Error
+ icon = UiImage.DrawableResource(R.drawable.ic_trash)
+ title = UiText.StringResource(R.string.remove_from_app)
+ message = UiText.StringResource(R.string.action_remove_project)
+ destructiveButton {
+ text = UiText.StringResource(R.string.remove)
+ action = {
+ project.delete()
+ findNavController().popBackStack()
+ }
+ }
+ neutralButton {
+ text = UiText.StringResource(R.string.lbl_Cancel)
+ action = {
+ dialogManager.dismissDialog()
+ }
+ }
+ }
+ }
+
+ private fun archiveProject() {
+ project.isArchived = !project.isArchived
+ project.save()
+
+ updateUi()
+ }
+
+ private fun updateUi() {
+ //supportActionBar?.title = project.description
+
+ binding.folderName.isEnabled = !project.isArchived
+ binding.folderName.hint = project.description
+ binding.folderName.setText(project.description)
+
+ binding.btArchive.setText(if (project.isArchived)
+ R.string.action_unarchive_project else
+ R.string.action_archive_project)
+
+ val global = project.space?.license != null
+
+ if (global) {
+ binding.cc.tvCcLabel.setText(R.string.set_the_same_creative_commons_license_for_all_folders_on_this_server)
+ }
+
+ CreativeCommonsLicenseManager.initialize(binding.cc, project.licenseUrl, !project.isArchived && !global)
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/settings/FolderListFragment.kt b/app/src/main/java/net/opendasharchive/openarchive/features/settings/FolderListFragment.kt
new file mode 100644
index 00000000..3f936571
--- /dev/null
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/settings/FolderListFragment.kt
@@ -0,0 +1,91 @@
+package net.opendasharchive.openarchive.features.settings
+
+import android.content.Intent
+import android.os.Bundle
+import android.view.LayoutInflater
+import android.view.Menu
+import android.view.MenuItem
+import android.view.View
+import android.view.ViewGroup
+import androidx.navigation.fragment.findNavController
+import androidx.recyclerview.widget.LinearLayoutManager
+import net.opendasharchive.openarchive.FolderAdapter
+import net.opendasharchive.openarchive.FolderAdapterListener
+import net.opendasharchive.openarchive.R
+import net.opendasharchive.openarchive.databinding.FragmentFolderListBinding
+import net.opendasharchive.openarchive.db.Project
+import net.opendasharchive.openarchive.db.Space
+import net.opendasharchive.openarchive.features.core.BaseFragment
+import net.opendasharchive.openarchive.features.core.NavArgument
+import net.opendasharchive.openarchive.util.extensions.toggle
+
+class FolderListFragment : BaseFragment(), FolderAdapterListener {
+
+ private lateinit var binding: FragmentFolderListBinding
+ private lateinit var adapter: FolderAdapter
+
+ private var showArchived = true
+ private var selectedSpaceId = -1L
+
+ override fun onCreate(savedInstanceState: Bundle?) {
+ super.onCreate(savedInstanceState)
+
+ arguments?.let {
+ showArchived = it.getBoolean(NavArgument.SHOW_ARCHIVED_FOLDERS, false)
+ selectedSpaceId = it.getLong(NavArgument.SPACE_ID, -1L)
+ }
+ }
+
+ override fun onCreateView(
+ inflater: LayoutInflater,
+ container: ViewGroup?,
+ savedInstanceState: Bundle?
+ ): View {
+ binding = FragmentFolderListBinding.inflate(inflater, container, false)
+
+ return binding.root
+ }
+
+ override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
+ super.onViewCreated(view, savedInstanceState)
+
+ setupRecyclerView()
+ }
+
+ private fun setupRecyclerView() {
+ adapter = FolderAdapter(context = requireContext(), listener = this, isArchived = showArchived)
+ binding.rvProjects.layoutManager = LinearLayoutManager(requireContext())
+ binding.rvProjects.adapter = adapter
+ }
+
+ override fun onResume() {
+ super.onResume()
+ refreshProjects()
+ }
+
+ private fun refreshProjects() {
+ val projects = if (showArchived) {
+ Space.current?.archivedProjects
+ } else {
+ Space.current?.projects?.filter { !it.isArchived }
+ } ?: emptyList()
+
+ adapter.update(projects)
+
+ if (projects.isEmpty()) {
+ binding.rvProjects.visibility = View.GONE
+ binding.tvNoFolders.visibility = View.VISIBLE
+ } else {
+ binding.rvProjects.visibility = View.VISIBLE
+ binding.tvNoFolders.visibility = View.GONE
+ }
+ }
+
+
+ override fun projectClicked(project: Project) {
+ val action = FolderListFragmentDirections.actionFragmentFolderListToFragmentEditFolder(folderId = project.id)
+ findNavController().navigate(action)
+ }
+
+ override fun getToolbarTitle(): String = getString(R.string.archived_folders)
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/settings/FoldersActivity.kt b/app/src/main/java/net/opendasharchive/openarchive/features/settings/FoldersActivity.kt
deleted file mode 100644
index 3e1ac954..00000000
--- a/app/src/main/java/net/opendasharchive/openarchive/features/settings/FoldersActivity.kt
+++ /dev/null
@@ -1,144 +0,0 @@
-package net.opendasharchive.openarchive.features.settings
-
-import android.content.Intent
-import android.os.Bundle
-import android.view.Menu
-import android.view.MenuItem
-import android.view.View
-import androidx.recyclerview.widget.LinearLayoutManager
-import net.opendasharchive.openarchive.FolderAdapter
-import net.opendasharchive.openarchive.FolderAdapterListener
-import net.opendasharchive.openarchive.R
-import net.opendasharchive.openarchive.databinding.ActivityFoldersBinding
-import net.opendasharchive.openarchive.db.Project
-import net.opendasharchive.openarchive.db.Space
-import net.opendasharchive.openarchive.features.core.BaseActivity
-import net.opendasharchive.openarchive.util.extensions.toggle
-
-class FoldersActivity : BaseActivity(), FolderAdapterListener {
-
- companion object {
- const val EXTRA_SHOW_ARCHIVED = "show_archived"
- const val EXTRA_SELECTED_SPACE_ID = "selected_space_id"
- const val EXTRA_SELECTED_PROJECT_ID = "SELECTED_PROJECT_ID"
- }
-
- private lateinit var mBinding: ActivityFoldersBinding
- private lateinit var mAdapter: FolderAdapter
-
- private var mArchived = true
- private var mSelectedSpaceId = -1L
- private var mSelectedProjectId: Long = -1L
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- mArchived = intent.getBooleanExtra(EXTRA_SHOW_ARCHIVED, false)
- mSelectedSpaceId = intent.getLongExtra(EXTRA_SELECTED_SPACE_ID, -1L)
- mSelectedProjectId = intent.getLongExtra(EXTRA_SELECTED_PROJECT_ID, -1L)
-
- mBinding = ActivityFoldersBinding.inflate(layoutInflater)
- setContentView(mBinding.root)
-
- setupToolbar(
- title = getString(if (mArchived) R.string.archived_folders else R.string.folders),
- showBackButton = true
- )
-
- setupRecyclerView()
-
- setupButtons()
- }
-
- private fun setupRecyclerView() {
- mAdapter = FolderAdapter(context = this, listener = this, isArchived = mArchived)
- mBinding.rvProjects.layoutManager = LinearLayoutManager(this)
- mBinding.rvProjects.adapter = mAdapter
- }
-
- private fun setupButtons() {
-// mBinding.fabAdd.apply {
-// visibility = if (mArchived) View.INVISIBLE else View.VISIBLE
-// setOnClickListener { addFolder() }
-// }
-
- mBinding.btViewArchived.apply {
- toggle(!mArchived)
- setOnClickListener {
- val i = Intent(this@FoldersActivity, FoldersActivity::class.java)
- i.putExtra(EXTRA_SHOW_ARCHIVED, true)
- startActivity(i)
- }
- }
- }
-
- override fun onResume() {
- super.onResume()
- refreshProjects()
- invalidateOptionsMenu()
- }
-
- private fun refreshProjects() {
- val projects = if (mArchived) {
- Space.current?.archivedProjects
- } else {
- Space.current?.projects?.filter { !it.isArchived }
- } ?: emptyList()
-
- mAdapter.update(projects)
-
- if (projects.isEmpty()) {
- mBinding.rvProjects.visibility = View.GONE
- mBinding.tvNoFolders.visibility = View.VISIBLE
- } else {
- mBinding.rvProjects.visibility = View.VISIBLE
- mBinding.tvNoFolders.visibility = View.GONE
- }
- }
-
-
- override fun onCreateOptionsMenu(menu: Menu?): Boolean {
- menuInflater.inflate(R.menu.menu_folder_list, menu)
- return super.onCreateOptionsMenu(menu)
- }
-
- override fun onPrepareOptionsMenu(menu: Menu?): Boolean {
- val archivedCount = Space.get(mSelectedSpaceId)?.archivedProjects?.size ?: 0
- menu?.findItem(R.id.action_archived_folders)?.isVisible = (!mArchived && archivedCount > 0)
- return super.onPrepareOptionsMenu(menu)
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- return when (item.itemId) {
-
- R.id.action_archived_folders -> {
- navigateToArchivedFolders()
- true
- }
-
- else -> super.onOptionsItemSelected(item)
- }
- }
-
- private fun navigateToArchivedFolders() {
- val intent = Intent(this, FoldersActivity::class.java).apply {
- putExtra(EXTRA_SHOW_ARCHIVED, true)
- putExtra(EXTRA_SELECTED_SPACE_ID, mSelectedSpaceId)
- putExtra(EXTRA_SELECTED_PROJECT_ID, mSelectedProjectId)
- }
- startActivity(intent)
- }
-
-
- override fun projectClicked(project: Project) {
- val resultIntent = Intent()
- resultIntent.putExtra("SELECTED_FOLDER_ID", project.id)
- setResult(RESULT_OK, resultIntent)
- //finish() // Close FoldersActivity and return to MainActivity
-
- val intent = Intent(this, EditFolderActivity::class.java).apply {
- putExtra(EditFolderActivity.EXTRA_CURRENT_PROJECT_ID, project.id)
- }
- startActivity(intent)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/settings/GeneralSettingsActivity.kt b/app/src/main/java/net/opendasharchive/openarchive/features/settings/GeneralSettingsActivity.kt
deleted file mode 100644
index 82fdee99..00000000
--- a/app/src/main/java/net/opendasharchive/openarchive/features/settings/GeneralSettingsActivity.kt
+++ /dev/null
@@ -1,178 +0,0 @@
-package net.opendasharchive.openarchive.features.settings
-
-import android.app.Activity
-import android.content.Intent
-import android.os.Bundle
-import android.view.MenuItem
-import androidx.activity.result.contract.ActivityResultContracts
-import androidx.appcompat.app.AlertDialog
-import androidx.compose.ui.res.stringResource
-import androidx.preference.Preference
-import androidx.preference.PreferenceFragmentCompat
-import androidx.preference.SwitchPreferenceCompat
-import net.opendasharchive.openarchive.R
-import net.opendasharchive.openarchive.databinding.ActivitySettingsContainerBinding
-import net.opendasharchive.openarchive.features.core.BaseActivity
-import net.opendasharchive.openarchive.features.settings.passcode.PasscodeRepository
-import net.opendasharchive.openarchive.features.settings.passcode.passcode_setup.PasscodeSetupActivity
-import net.opendasharchive.openarchive.util.Prefs
-import net.opendasharchive.openarchive.util.Theme
-import org.koin.android.ext.android.inject
-
-
-class GeneralSettingsActivity: BaseActivity() {
-
- class Fragment: PreferenceFragmentCompat() {
-
- private val passcodeRepository by inject()
-
-// private var mCiConsentPref: SwitchPreferenceCompat? = null
-
- private var passcodePreference: SwitchPreferenceCompat? = null
-
- private val activityResultLauncher = registerForActivityResult(
- ActivityResultContracts.StartActivityForResult()
- ) { result ->
- if (result.resultCode == Activity.RESULT_OK) {
- val passcodeEnabled = result.data?.getBooleanExtra("passcode_enabled", false) ?: false
- passcodePreference?.isChecked = passcodeEnabled
- } else {
- passcodePreference?.isChecked = false
- }
- }
-
- override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) {
- setPreferencesFromResource(R.xml.prefs_general, rootKey)
-
- passcodePreference = findPreference(Prefs.PASSCODE_ENABLED)
-
-// findPreference(Prefs.PASSCODE_ENABLED)?.setOnPreferenceChangeListener { _, newValue ->
-// //Prefs.lockWithPasscode = newValue as Boolean
-// if (newValue as? Boolean == true) {
-//
-// val intent = Intent(context, PasscodeSetupActivity::class.java)
-// activityResultLauncher.launch(intent)
-// }
-// false
-// }
-
-
- passcodePreference?.setOnPreferenceChangeListener { _, newValue ->
- val enabled = newValue as Boolean
- if (enabled) {
- // Launch PasscodeSetupActivity
- val intent = Intent(context, PasscodeSetupActivity::class.java)
- activityResultLauncher.launch(intent)
- } else {
- // Show confirmation dialog
- AlertDialog.Builder(requireContext())
- .setTitle("Disable Passcode")
- .setMessage("Are you sure you want to disable the passcode?")
- .setPositiveButton("Yes") { _, _ ->
- passcodeRepository.clearPasscode()
- passcodePreference?.isChecked = false
-
- // Update the FLAG_SECURE dynamically
- (activity as? BaseActivity)?.updateScreenshotPrevention()
- }
- .setNegativeButton("No") { _, _ ->
- passcodePreference?.isChecked = true
- }
- .show()
- }
- // Return false to avoid the preference updating immediately
- false
- }
-
-// findPreference(Prefs.USE_TOR)?.setOnPreferenceChangeListener { _, newValue ->
-// val activity = activity ?: return@setOnPreferenceChangeListener true
-//
-// if (newValue as Boolean) {
-// if (!OrbotHelper.isOrbotInstalled(activity) && !OrbotHelper.isTorServicesInstalled(activity)) {
-// AlertHelper.show(activity,
-// R.string.prefs_install_tor_summary,
-// R.string.prefs_use_tor_title,
-// buttons = listOf(
-// AlertHelper.positiveButton(R.string.action_install) { _, _ ->
-// activity.startActivity(
-// OrbotHelper.getOrbotInstallIntent(activity))
-// },
-// AlertHelper.negativeButton(R.string.action_cancel)
-// ))
-//
-// return@setOnPreferenceChangeListener false
-// }
-// }
-//
-// true
-// }
-
- findPreference("proof_mode")?.setOnPreferenceClickListener {
- startActivity(Intent(context, ProofModeSettingsActivity::class.java))
-
- true
- }
-
- findPreference(Prefs.THEME)?.setOnPreferenceChangeListener { _, newValue ->
- Theme.set(requireActivity(), Theme.get(newValue as? String))
-
- true
- }
-
- findPreference(Prefs.PROHIBIT_SCREENSHOTS)?.setOnPreferenceClickListener { _ ->
- if (activity is BaseActivity) {
- // make sure this gets settings change gets applied instantly
- // (all other activities rely on the hook in BaseActivity.onResume())
- (activity as BaseActivity).updateScreenshotPrevention()
- }
-
- true
- }
-
-// mCiConsentPref = findPreference("health_checks")
-//
-// mCiConsentPref?.setOnPreferenceChangeListener { _, newValue ->
-// if (newValue as? Boolean == false) {
-// CleanInsightsManager.deny()
-// }
-// else {
-// startActivity(Intent(context, ConsentActivity::class.java))
-// }
-//
-// true
-// }
- }
-
-// override fun onResume() {
-// super.onResume()
-//
-// mCiConsentPref?.isChecked = CleanInsightsManager.hasConsent()
-// }
- }
-
-
- private lateinit var mBinding: ActivitySettingsContainerBinding
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- mBinding = ActivitySettingsContainerBinding.inflate(layoutInflater)
- setContentView(mBinding.root)
-
- setupToolbar(title = getString(R.string.general))
-
- supportFragmentManager
- .beginTransaction()
- .replace(mBinding.container.id, Fragment())
- .commit()
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- if (item.itemId == android.R.id.home) {
- finish()
- return true
- }
-
- return super.onOptionsItemSelected(item)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/settings/OpenOrbotPreference.java b/app/src/main/java/net/opendasharchive/openarchive/features/settings/OpenOrbotPreference.java
new file mode 100644
index 00000000..a8849a28
--- /dev/null
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/settings/OpenOrbotPreference.java
@@ -0,0 +1,38 @@
+package net.opendasharchive.openarchive.features.settings;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.View;
+import android.widget.Button;
+
+import androidx.annotation.NonNull;
+import androidx.preference.Preference;
+import androidx.preference.PreferenceViewHolder;
+
+import net.opendasharchive.openarchive.R;
+
+public class OpenOrbotPreference extends Preference {
+ public OpenOrbotPreference(Context context, AttributeSet attrs) {
+ super(context, attrs);
+ setWidgetLayoutResource(R.layout.prefs_open_orbot);
+ this.setSelectable(false);
+ this.setOnPreferenceClickListener(null);
+ }
+
+ private View.OnClickListener onClickListener = null;
+
+ public void setOnOpenOrbotListener(View.OnClickListener listener) {
+ this.onClickListener = listener;
+ }
+
+ @Override
+ public void onBindViewHolder(@NonNull PreferenceViewHolder holder) {
+ super.onBindViewHolder(holder);
+ Button button = (Button) holder.findViewById(R.id.open_orbot_button);
+ button.setOnClickListener(v -> {
+ if (onClickListener != null) {
+ onClickListener.onClick(v);
+ }
+ });
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/settings/ProofModeSettingsActivity.kt b/app/src/main/java/net/opendasharchive/openarchive/features/settings/ProofModeSettingsActivity.kt
index 7f8624e2..f9d5077b 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/features/settings/ProofModeSettingsActivity.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/settings/ProofModeSettingsActivity.kt
@@ -23,7 +23,7 @@ import com.permissionx.guolindev.PermissionX
import kotlinx.coroutines.MainScope
import kotlinx.coroutines.launch
import net.opendasharchive.openarchive.R
-import net.opendasharchive.openarchive.databinding.ActivitySettingsContainerBinding
+import net.opendasharchive.openarchive.databinding.ActivityProofmodeSettingsBinding
import net.opendasharchive.openarchive.features.core.BaseActivity
import net.opendasharchive.openarchive.util.Hbks
import net.opendasharchive.openarchive.util.Prefs
@@ -167,12 +167,12 @@ class ProofModeSettingsActivity : BaseActivity() {
}
}
- private lateinit var mBinding: ActivitySettingsContainerBinding
+ private lateinit var mBinding: ActivityProofmodeSettingsBinding
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
- mBinding = ActivitySettingsContainerBinding.inflate(layoutInflater)
+ mBinding = ActivityProofmodeSettingsBinding.inflate(layoutInflater)
setContentView(mBinding.root)
setupToolbar(getString(R.string.proofmode))
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/settings/SettingsFragment.kt b/app/src/main/java/net/opendasharchive/openarchive/features/settings/SettingsFragment.kt
index 4965a2f3..4d866716 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/features/settings/SettingsFragment.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/settings/SettingsFragment.kt
@@ -5,11 +5,17 @@ import android.content.Intent
import android.os.Bundle
import android.view.View
import androidx.activity.result.contract.ActivityResultContracts
+import androidx.navigation.NavDeepLinkBuilder
+import androidx.lifecycle.lifecycleScope
+import androidx.preference.EditTextPreference
import androidx.preference.Preference
import androidx.preference.PreferenceFragmentCompat
import androidx.preference.SwitchPreferenceCompat
+import kotlinx.coroutines.launch
import net.opendasharchive.openarchive.R
+import net.opendasharchive.openarchive.db.Space
import net.opendasharchive.openarchive.features.core.BaseActivity
+import net.opendasharchive.openarchive.features.core.NavArgument
import net.opendasharchive.openarchive.features.core.UiText
import net.opendasharchive.openarchive.features.core.dialog.DialogStateManager
import net.opendasharchive.openarchive.features.core.dialog.DialogType
@@ -18,11 +24,14 @@ import net.opendasharchive.openarchive.features.onboarding.SpaceSetupActivity
import net.opendasharchive.openarchive.features.onboarding.StartDestination
import net.opendasharchive.openarchive.features.settings.passcode.PasscodeRepository
import net.opendasharchive.openarchive.features.settings.passcode.passcode_setup.PasscodeSetupActivity
+import net.opendasharchive.openarchive.services.tor.TorStatus
+import net.opendasharchive.openarchive.services.tor.TorViewModel
import net.opendasharchive.openarchive.util.Prefs
import net.opendasharchive.openarchive.util.Theme
import net.opendasharchive.openarchive.util.extensions.getVersionName
import org.koin.android.ext.android.inject
import org.koin.androidx.viewmodel.ext.android.activityViewModel
+import org.koin.androidx.viewmodel.ext.android.viewModel
class SettingsFragment : PreferenceFragmentCompat() {
@@ -30,9 +39,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
private val dialogManager: DialogStateManager by activityViewModel()
-
private var passcodePreference: SwitchPreferenceCompat? = null
+ private val torViewModel: TorViewModel by viewModel()
+
private val activityResultLauncher = registerForActivityResult(
ActivityResultContracts.StartActivityForResult()
) { result ->
@@ -67,7 +77,6 @@ class SettingsFragment : PreferenceFragmentCompat() {
) {
setPreferencesFromResource(R.xml.prefs_general, rootKey)
-
passcodePreference = findPreference(Prefs.PASSCODE_ENABLED)
passcodePreference?.setOnPreferenceChangeListener { _, newValue ->
@@ -115,15 +124,34 @@ class SettingsFragment : PreferenceFragmentCompat() {
getPrefByKey(R.string.pref_media_servers)?.setOnPreferenceClickListener {
val intent = Intent(context, SpaceSetupActivity::class.java)
- intent.putExtra("start_destination", StartDestination.SPACE_LIST.name)
+ intent.putExtra(NavArgument.START_DESTINATION, StartDestination.SPACE_LIST.name)
startActivity(intent)
true
}
getPrefByKey(R.string.pref_media_folders)?.setOnPreferenceClickListener {
- val intent = Intent(context, FoldersActivity::class.java)
- intent.putExtra(FoldersActivity.EXTRA_SHOW_ARCHIVED, true)
+// val intent = Intent(context, FoldersActivity::class.java)
+// intent.putExtra(FoldersActivity.EXTRA_SHOW_ARCHIVED, true)
+// startActivity(intent)
+//
+ val intent = Intent(context, SpaceSetupActivity::class.java)
+ intent.putExtra(NavArgument.START_DESTINATION, StartDestination.FOLDER_LIST.name)
+ intent.putExtra(NavArgument.SHOW_ARCHIVED_FOLDERS, true)
+ intent.putExtra(NavArgument.SPACE_ID, Space.current?.id)
startActivity(intent)
+
+// val args = Bundle().apply {
+// putBoolean(NavArgument.SHOW_ARCHIVED_FOLDERS, true)
+// putLong(NavArgument.SPACE_ID, -1L)
+// putLong(NavArgument.FOLDER_ID, -1L)
+// }
+// NavDeepLinkBuilder(requireContext())
+// .setComponentName(SpaceSetupActivity::class.java)
+// .setGraph(R.navigation.space_setup_navigation)
+// .setDestination(R.id.fragment_folder_list)
+// .setArguments(args)
+// .createPendingIntent()
+// .send()
true
}
@@ -132,32 +160,57 @@ class SettingsFragment : PreferenceFragmentCompat() {
true
}
- findPreference(Prefs.USE_TOR)?.setOnPreferenceChangeListener { _, newValue ->
- Prefs.useTor = (newValue as Boolean)
- //torViewModel.updateTorServiceState()
- true
- }
-
- getPrefByKey(R.string.pref_key_use_tor)?.apply {
- isEnabled = true
+ val useTorPref = findPreference(Prefs.USE_TOR)
+ val torStatusPref = findPreference("tor_status")
+ val openOrbot = findPreference("open_orbot")
- setOnPreferenceClickListener {
- dialogManager.showDialog(dialogManager.requireResourceProvider()) {
- type = DialogType.Info
- title = UiText.StringResource(R.string.tor_disabled_title)
- message = UiText.StringResource(R.string.tor_disabled_message)
- positiveButton {
- text = UiText.StringResource(android.R.string.ok)
- }
+ val setUseTorText: (TorStatus, Boolean) -> Unit = { torStatus, enabled ->
+ if (torStatus == TorStatus.CONNECTED) {
+ if (enabled) {
+ torStatusPref?.setSummary(R.string.prefs_use_tor_enabled)
+ } else {
+ torStatusPref?.setSummary(R.string.prefs_use_tor_ready)
+ }
+ } else if (torStatus == TorStatus.CONNECTING) {
+ if (enabled) {
+ torStatusPref?.setSummary(R.string.prefs_use_tor_starting)
+ } else {
+ torStatusPref?.setSummary(R.string.prefs_use_tor_not_starting)
+ }
+ } else {
+ if (enabled) {
+ torStatusPref?.setSummary(R.string.prefs_use_tor_disabled)
+ } else {
+ torStatusPref?.setSummary(R.string.prefs_use_tor_not_ready)
}
- true
}
+ }
+ lifecycleScope.launch {
+ torViewModel.torStatus.collect { torStatus ->
+ setUseTorText(torStatus, useTorPref?.isChecked == true)
+ }
+ }
+ useTorPref?.apply {
+
+ setUseTorText(torViewModel.torStatus.value, isChecked)
setOnPreferenceChangeListener { _, newValue ->
- false
+ val enabled = newValue as Boolean
+ torViewModel.toggleTorServiceState(requireActivity(), enabled)
+ setUseTorText(torViewModel.torStatus.value, enabled)
+ torStatusPref?.isVisible = enabled
+ openOrbot?.isVisible = enabled
+ true
}
}
+ torStatusPref?.isVisible = useTorPref?.isChecked == true
+ openOrbot?.isVisible = useTorPref?.isChecked == true
+
+ openOrbot?.setOnOpenOrbotListener {
+ torViewModel.requestOpenOrInstallOrbot(requireActivity())
+ }
+
findPreference(Prefs.THEME)?.setOnPreferenceChangeListener { _, newValue ->
Theme.set(requireActivity(), Theme.get(newValue as? String))
true
@@ -202,4 +255,10 @@ class SettingsFragment : PreferenceFragmentCompat() {
override fun onViewCreated(view: View, savedInstanceState: Bundle?) {
super.onViewCreated(view, savedInstanceState)
}
+
+ override fun onResume() {
+ super.onResume()
+ //torViewModel.requestTorStatus()
+ }
+
}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/settings/SpaceSetupFragment.kt b/app/src/main/java/net/opendasharchive/openarchive/features/settings/SpaceSetupFragment.kt
index 34c0448c..2b52ec42 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/features/settings/SpaceSetupFragment.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/settings/SpaceSetupFragment.kt
@@ -33,42 +33,28 @@ class SpaceSetupFragment : BaseFragment() {
// Prepare click lambdas that use the fragment’s business logic.
val onWebDavClick = {
- if (isJetpackNavigation) {
- findNavController().navigate(R.id.action_fragment_space_setup_to_fragment_web_dav)
- } else {
- setFragmentResult(
- RESULT_REQUEST_KEY,
- bundleOf(RESULT_BUNDLE_KEY to RESULT_VAL_WEBDAV)
- )
- }
+
+ val action = SpaceSetupFragmentDirections.actionFragmentSpaceSetupToFragmentWebDav()
+ findNavController().navigate(action)
+
}
// Only enable Internet Archive if not already present
val isInternetArchiveAllowed = !Space.has(Space.Type.INTERNET_ARCHIVE)
val onInternetArchiveClick = {
- if (isJetpackNavigation) {
+
val action =
- SpaceSetupFragmentDirections.actionFragmentSpaceSetupToFragmentInternetArchive()
+ SpaceSetupFragmentDirections.actionFragmentSpaceSetupToFragmentInternetArchiveLogin()
findNavController().navigate(action)
- } else {
- setFragmentResult(
- RESULT_REQUEST_KEY,
- bundleOf(RESULT_BUNDLE_KEY to RESULT_VAL_INTERNET_ARCHIVE)
- )
- }
+
}
// Show/hide Snowbird based on config
val isDwebEnabled = appConfig.isDwebEnabled
val onDwebClicked = {
- if (isJetpackNavigation) {
+
val action =
SpaceSetupFragmentDirections.actionFragmentSpaceSetupToFragmentSnowbird()
findNavController().navigate(action)
- } else {
- setFragmentResult(
- RESULT_REQUEST_KEY,
- bundleOf(RESULT_BUNDLE_KEY to RESULT_VAL_RAVEN)
- )
- }
+
}
SaveAppTheme {
@@ -83,16 +69,6 @@ class SpaceSetupFragment : BaseFragment() {
}
- companion object {
- const val RESULT_REQUEST_KEY = "space_setup_fragment_result"
- const val RESULT_BUNDLE_KEY = "space_setup_result_key"
- const val RESULT_VAL_DROPBOX = "dropbox"
- const val RESULT_VAL_WEBDAV = "webdav"
- const val RESULT_VAL_RAVEN = "raven"
- const val RESULT_VAL_INTERNET_ARCHIVE = "internet_archive"
- const val RESULT_VAL_GDRIVE = "gdrive"
- }
-
override fun getToolbarTitle() = getString(R.string.space_setup_title)
override fun getToolbarSubtitle(): String? = null
override fun shouldShowBackButton() = true
diff --git a/app/src/main/java/net/opendasharchive/openarchive/features/spaces/SpaceListFragment.kt b/app/src/main/java/net/opendasharchive/openarchive/features/spaces/SpaceListFragment.kt
index bbac2982..e4e91af3 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/features/spaces/SpaceListFragment.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/features/spaces/SpaceListFragment.kt
@@ -6,20 +6,18 @@ import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import androidx.compose.runtime.LaunchedEffect
+import androidx.compose.ui.platform.ComposeView
+import androidx.compose.ui.platform.ViewCompositionStrategy
import androidx.navigation.fragment.findNavController
import net.opendasharchive.openarchive.R
import net.opendasharchive.openarchive.core.presentation.theme.SaveAppTheme
-import net.opendasharchive.openarchive.databinding.FragmentSpaceListBinding
import net.opendasharchive.openarchive.db.Space
import net.opendasharchive.openarchive.features.core.BaseFragment
-import net.opendasharchive.openarchive.features.internetarchive.presentation.InternetArchiveActivity
import net.opendasharchive.openarchive.services.gdrive.GDriveActivity
-import net.opendasharchive.openarchive.services.webdav.WebDavActivity
import org.koin.compose.viewmodel.koinViewModel
class SpaceListFragment : BaseFragment() {
- private lateinit var binding: FragmentSpaceListBinding
companion object {
const val EXTRA_DATA_SPACE = "space_id"
@@ -31,30 +29,27 @@ class SpaceListFragment : BaseFragment() {
savedInstanceState: Bundle?
): View {
- binding = FragmentSpaceListBinding.inflate(inflater)
+ return ComposeView(requireContext()).apply {
+ setViewCompositionStrategy(ViewCompositionStrategy.DisposeOnViewTreeLifecycleDestroyed)
+ setContent {
+ val viewModel: SpaceListViewModel = koinViewModel()
- binding.composeViewSpaceList.setContent {
+ SaveAppTheme {
- val viewModel: SpaceListViewModel = koinViewModel()
+ // Calling refresh here will update state & trigger recomposition
+ LaunchedEffect(Unit) {
+ viewModel.refreshSpaces()
+ }
- SaveAppTheme {
-
- // Calling refresh here will update state & trigger recomposition
- LaunchedEffect(Unit) {
- viewModel.refreshSpaces()
+ SpaceListScreen(
+ onSpaceClicked = { space ->
+ startSpaceAuthActivity(space.id)
+ },
+ )
}
-
- SpaceListScreen(
- onSpaceClicked = { space ->
- startSpaceAuthActivity(space.id)
- },
- )
}
-
}
-
- return binding.root
}
override fun getToolbarTitle() = getString(R.string.pref_title_media_servers)
@@ -64,9 +59,8 @@ class SpaceListFragment : BaseFragment() {
when (space.tType) {
Space.Type.INTERNET_ARCHIVE -> {
- val intent = Intent(requireContext(), InternetArchiveActivity::class.java)
- intent.putExtra(EXTRA_DATA_SPACE, space.id)
- startActivity(intent)
+ val action = SpaceListFragmentDirections.actionFragmentSpaceListToFragmentInternetArchiveDetail(spaceId = spaceId)
+ findNavController().navigate(action)
}
Space.Type.GDRIVE -> {
@@ -82,9 +76,8 @@ class SpaceListFragment : BaseFragment() {
}
else -> {
- val intent = Intent(requireContext(), WebDavActivity::class.java)
- intent.putExtra(EXTRA_DATA_SPACE, space.id)
- startActivity(intent)
+ val action = SpaceListFragmentDirections.actionFragmentSpaceListToFragmentWebDav(spaceId = spaceId)
+ findNavController().navigate(action)
}
}
diff --git a/app/src/main/java/net/opendasharchive/openarchive/services/Module.kt b/app/src/main/java/net/opendasharchive/openarchive/services/Module.kt
new file mode 100644
index 00000000..38b18006
--- /dev/null
+++ b/app/src/main/java/net/opendasharchive/openarchive/services/Module.kt
@@ -0,0 +1,11 @@
+package net.opendasharchive.openarchive.services
+
+import net.opendasharchive.openarchive.services.tor.torModule
+import org.koin.dsl.module
+
+internal val servicesModule = module {
+
+ factory { SaveClient(get()) }
+
+ includes(torModule)
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/services/SaveClient.kt b/app/src/main/java/net/opendasharchive/openarchive/services/SaveClient.kt
index 1dc4be86..e8859751 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/services/SaveClient.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/services/SaveClient.kt
@@ -3,146 +3,92 @@ package net.opendasharchive.openarchive.services
import android.content.Context
import android.content.Intent
import com.thegrizzlylabs.sardineandroid.impl.OkHttpSardine
-import info.guardianproject.netcipher.client.StrongBuilder
-import info.guardianproject.netcipher.client.StrongBuilderBase
+import info.guardianproject.netcipher.client.StrongOkHttpClientBuilder
import info.guardianproject.netcipher.proxy.OrbotHelper
-import net.opendasharchive.openarchive.R
+import info.guardianproject.netcipher.proxy.OrbotHelper.SimpleStatusCallback
+import net.opendasharchive.openarchive.core.infrastructure.client.enqueueResult
import net.opendasharchive.openarchive.db.Space
-import net.opendasharchive.openarchive.services.webdav.BasicAuthInterceptor
import net.opendasharchive.openarchive.util.Prefs
+import okhttp3.Call
import okhttp3.Interceptor
import okhttp3.OkHttpClient
import okhttp3.Protocol
import okhttp3.Request
-import okhttp3.internal.platform.Platform
+import okhttp3.Response
+import org.koin.core.component.KoinComponent
+import timber.log.Timber
import java.util.concurrent.TimeUnit
-import kotlin.coroutines.suspendCoroutine
-class SaveClient(context: Context) : StrongBuilderBase(context) {
-
- class OrbotException(message: String): Exception(message)
+class SaveClient(private val context: Context) : SimpleStatusCallback(), KoinComponent, Call.Factory {
private var okBuilder: OkHttpClient.Builder
+ private val strongBuilder: StrongOkHttpClientBuilder
init {
+ okBuilder = setup()
+ strongBuilder = StrongOkHttpClientBuilder.forMaxSecurity(context)
+ if (Prefs.useTor) {
+ OrbotHelper.get(context).apply {
+ addStatusCallback(this@SaveClient)
+ init()
+ }
+ }
+ }
+
+ private fun setup(): OkHttpClient.Builder {
val cacheInterceptor = Interceptor { chain ->
val request = chain.request().newBuilder().addHeader("Connection", "close").build()
chain.proceed(request)
}
- okBuilder = OkHttpClient.Builder()
+ var builder = OkHttpClient.Builder()
.addInterceptor(cacheInterceptor)
.connectTimeout(40L, TimeUnit.SECONDS)
.writeTimeout(40L, TimeUnit.SECONDS)
.readTimeout(40L, TimeUnit.SECONDS)
.retryOnConnectionFailure(false)
.protocols(arrayListOf(Protocol.HTTP_1_1))
- }
- /**
- * OkHttp3 [does not support SOCKS proxies.](https://github.com/square/okhttp/issues/2315)
- *
- * @return false
- */
- override fun supportsSocksProxy(): Boolean {
- return false
+ return builder
}
- /**
- * {@inheritDoc}
- */
- override fun build(status: Intent): OkHttpClient {
- if (!status.hasExtra(OrbotHelper.EXTRA_STATUS)) {
- status.putExtra(OrbotHelper.EXTRA_STATUS, OrbotHelper.STATUS_OFF)
- }
+ override fun onEnabled(statusIntent: Intent?) {
+ OrbotHelper.get(context).removeStatusCallback(this)
- return applyTo(okBuilder, status).build()
- }
+ if (Prefs.useTor.not()) return
- /**
- * Adds NetCipher configuration to an existing OkHttpClient.Builder,
- * in case you have additional configuration that you wish to
- * perform.
- *
- * @param builder a new or partially-configured OkHttpClient.Builder
- * @return the same builder
- */
- private fun applyTo(builder: OkHttpClient.Builder, status: Intent?): OkHttpClient.Builder {
- val factory = buildSocketFactory()
-
- if (factory != null) {
- val trustManager = Platform.get().trustManager(factory)
-
- if (trustManager != null) {
- builder.sslSocketFactory(factory, trustManager)
- }
+ try {
+ strongBuilder.applyTo(okBuilder, statusIntent)
+ } catch (e: Exception) {
+ Timber.e(e, "Error setting up OkHttp client")
}
-
- return builder
- .proxy(buildProxy(status))
}
- @Throws(Exception::class)
- override fun get(status: Intent, connection: OkHttpClient, url: String): String? {
- val request: Request = Request.Builder().url(TOR_CHECK_URL).build()
-
- return connection.newCall(request).execute().body?.string()
+ override fun onNotYetInstalled() {
+ OrbotHelper.get(context).removeStatusCallback(this)
+ okBuilder = okBuilder.proxy(null)
}
- companion object {
- suspend fun get(context: Context, user: String = "", password: String = ""): OkHttpClient {
-
- val strongBuilder = SaveClient(context)
+ override fun onStatusTimeout() {
+ OrbotHelper.get(context).removeStatusCallback(this)
+ okBuilder = okBuilder.proxy(null)
+ }
- if (user.isNotEmpty() || password.isNotEmpty()) {
- strongBuilder.okBuilder.addInterceptor(BasicAuthInterceptor(user, password))
- }
+ override fun newCall(request: Request): Call {
+ return okBuilder.build().newCall(request)
+ }
- return suspendCoroutine {
- val callback = object : StrongBuilder.Callback {
- override fun onConnected(connection: OkHttpClient?) {
- val result = if (connection != null) {
- Result.success(connection)
- }
- else {
- Result.failure(OrbotException(context.getString(R.string.tor_connection_exception)))
- }
-
- it.resumeWith(result)
- }
-
- override fun onConnectionException(e: java.lang.Exception?) {
- it.resumeWith(Result.failure(e ?: OrbotException(context.getString(R.string.tor_connection_exception))))
- }
-
- override fun onTimeout() {
- it.resumeWith(Result.failure(OrbotException(context.getString(R.string.tor_connection_timeout))))
- }
-
- override fun onInvalid() {
- it.resumeWith(Result.failure(OrbotException(context.getString(R.string.tor_connection_invalid))))
- }
- }
-
- if (Prefs.useTor) {
- if (!OrbotHelper.requestStartTor(context)) {
- callback.onInvalid()
- }
- else {
- strongBuilder.build(callback)
- }
- }
- else {
- callback.onConnected(strongBuilder.build(Intent()))
- }
- }
- }
+ suspend fun enqueue(request: Request): Result {
+ return okBuilder.build().enqueueResult(request)
+ }
- suspend fun getSardine(context: Context, space: Space): OkHttpSardine {
- val sardine = OkHttpSardine(get(context))
- sardine.setCredentials(space.username, space.password)
+ fun execute(request: Request): Response {
+ return okBuilder.build().newCall(request).execute()
+ }
- return sardine
- }
+ fun webdav(space: Space): OkHttpSardine {
+ val sardine = OkHttpSardine(okBuilder.build())
+ sardine.setCredentials(space.username, space.password)
+ return sardine
}
}
diff --git a/app/src/main/java/net/opendasharchive/openarchive/services/gdrive/GDriveConduit.kt b/app/src/main/java/net/opendasharchive/openarchive/services/gdrive/GDriveConduit.kt
index a28d025f..667323dc 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/services/gdrive/GDriveConduit.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/services/gdrive/GDriveConduit.kt
@@ -20,7 +20,7 @@ import com.google.api.services.drive.model.File
import info.guardianproject.netcipher.proxy.OrbotHelper
import net.opendasharchive.openarchive.R
import net.opendasharchive.openarchive.db.Media
-import net.opendasharchive.openarchive.features.folders.Folder
+import net.opendasharchive.openarchive.features.folders.BrowseFoldersViewModel.Folder
import net.opendasharchive.openarchive.services.Conduit
import net.opendasharchive.openarchive.util.Prefs
import org.apache.http.conn.ClientConnectionManager
diff --git a/app/src/main/java/net/opendasharchive/openarchive/services/internetarchive/IaConduit.kt b/app/src/main/java/net/opendasharchive/openarchive/services/internetarchive/IaConduit.kt
index 7ac8a122..e2c386cc 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/services/internetarchive/IaConduit.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/services/internetarchive/IaConduit.kt
@@ -3,19 +3,21 @@ package net.opendasharchive.openarchive.services.internetarchive
import android.content.Context
import android.net.Uri
import com.google.gson.GsonBuilder
-import kotlinx.coroutines.Dispatchers
-import kotlinx.coroutines.withContext
import net.opendasharchive.openarchive.R
import net.opendasharchive.openarchive.db.Media
import net.opendasharchive.openarchive.services.Conduit
import net.opendasharchive.openarchive.services.SaveClient
import okhttp3.*
import okhttp3.MediaType.Companion.toMediaTypeOrNull
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
import java.io.File
import java.io.IOException
+import androidx.core.net.toUri
-class IaConduit(media: Media, context: Context) : Conduit(media, context) {
+class IaConduit(media: Media, context: Context) : Conduit(media, context), KoinComponent {
+ private val client: SaveClient by inject()
companion object {
const val ARCHIVE_BASE_URL = "https://archive.org/"
@@ -39,8 +41,6 @@ class IaConduit(media: Media, context: Context) : Conduit(media, context) {
try {
val mimeType = mMedia.mimeType
- val client = SaveClient.get(mContext)
-
val fileName = getUploadFileName(mMedia, true)
val metaJson = gson.toJson(mMedia)
// Commenting out proof generation - 17th April 2025
@@ -80,14 +80,14 @@ class IaConduit(media: Media, context: Context) : Conduit(media, context) {
// Ignored. Not used here.
}
- private suspend fun OkHttpClient.uploadContent(fileName: String, mimeType: String) {
+ private fun SaveClient.uploadContent(fileName: String, mimeType: String) {
val mediaUri = mMedia.originalFilePath
val url = "${ARCHIVE_API_ENDPOINT}/${mMedia.serverUrl}/$fileName"
val requestBody = RequestBodyUtil.create(
mContext.contentResolver,
- Uri.parse(mediaUri),
+ mediaUri.toUri(),
mMedia.contentLength,
mimeType.toMediaTypeOrNull(),
createListener(
@@ -108,7 +108,7 @@ class IaConduit(media: Media, context: Context) : Conduit(media, context) {
}
@Throws(IOException::class)
- private fun OkHttpClient.uploadMetaData(content: String, fileName: String) {
+ private suspend fun SaveClient.uploadMetaData(content: String, fileName: String) {
val requestBody = RequestBodyUtil.create(
textMediaType,
content.byteInputStream(),
@@ -129,7 +129,7 @@ class IaConduit(media: Media, context: Context) : Conduit(media, context) {
/// upload proof mode
@Throws(IOException::class)
- private fun OkHttpClient.uploadProofFiles(uploadFile: File) {
+ private suspend fun SaveClient.uploadProofFiles(uploadFile: File) {
val requestBody = RequestBodyUtil.create(
mContext.contentResolver,
Uri.fromFile(uploadFile),
@@ -225,33 +225,6 @@ class IaConduit(media: Media, context: Context) : Conduit(media, context) {
.build()
}
- @Throws(Exception::class)
- private suspend fun OkHttpClient.execute(request: Request) = withContext(Dispatchers.IO) {
- val result = newCall(request)
- .execute()
-
- if (result.isSuccessful.not()) {
- throw RuntimeException("${result.code}: ${result.message}")
- }
- }
-
- @Throws(Exception::class)
- private fun OkHttpClient.enqueue(request: Request) {
- newCall(request)
- .enqueue(object : Callback {
- override fun onFailure(call: Call, e: IOException) {
- jobFailed(e)
- }
-
- override fun onResponse(call: Call, response: Response) {
- if (!response.isSuccessful) {
- jobFailed(Exception("${response.code}: ${response.message}"))
- }
- }
-
- })
- }
-
private fun sanitizeHeaderValue(value: String): String {
return value.replace("[^\\x20-\\x7E]".toRegex(), "") // Removes non-ASCII characters
}
diff --git a/app/src/main/java/net/opendasharchive/openarchive/services/tor/Module.kt b/app/src/main/java/net/opendasharchive/openarchive/services/tor/Module.kt
new file mode 100644
index 00000000..24804fdc
--- /dev/null
+++ b/app/src/main/java/net/opendasharchive/openarchive/services/tor/Module.kt
@@ -0,0 +1,10 @@
+package net.opendasharchive.openarchive.services.tor
+
+import org.koin.core.module.dsl.viewModel
+import org.koin.core.qualifier.named
+import org.koin.dsl.module
+
+internal val torModule = module {
+ single(named("tor")) { TorRepository() }
+ viewModel { TorViewModel(get(), get(named("tor"))) }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/services/tor/TorRepository.kt b/app/src/main/java/net/opendasharchive/openarchive/services/tor/TorRepository.kt
new file mode 100644
index 00000000..d5c58899
--- /dev/null
+++ b/app/src/main/java/net/opendasharchive/openarchive/services/tor/TorRepository.kt
@@ -0,0 +1,40 @@
+package net.opendasharchive.openarchive.services.tor
+
+import android.content.Intent
+import info.guardianproject.netcipher.proxy.StatusCallback
+import kotlinx.coroutines.flow.MutableStateFlow
+import kotlinx.coroutines.flow.StateFlow
+import kotlinx.coroutines.flow.asStateFlow
+
+interface ITorRepository : StatusCallback {
+ val torStatus: StateFlow
+}
+
+class TorRepository() : ITorRepository {
+ private val _torStatus = MutableStateFlow(TorStatus.DISCONNECTED)
+ override val torStatus: StateFlow = _torStatus.asStateFlow()
+ override fun onEnabled(p0: Intent?) {
+ _torStatus.value = TorStatus.CONNECTED
+ }
+
+ override fun onStarting() {
+ _torStatus.value = TorStatus.CONNECTING
+ }
+
+ override fun onStopping() {
+ _torStatus.value = TorStatus.DISCONNECTING
+ }
+
+ override fun onDisabled() {
+ _torStatus.value = TorStatus.DISCONNECTED
+ }
+
+ override fun onStatusTimeout() {
+ _torStatus.value = TorStatus.DISCONNECTED
+ }
+
+ override fun onNotYetInstalled() {
+ _torStatus.value = TorStatus.DISCONNECTED
+ }
+
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/services/tor/TorStatus.kt b/app/src/main/java/net/opendasharchive/openarchive/services/tor/TorStatus.kt
new file mode 100644
index 00000000..ea7c79ac
--- /dev/null
+++ b/app/src/main/java/net/opendasharchive/openarchive/services/tor/TorStatus.kt
@@ -0,0 +1,8 @@
+package net.opendasharchive.openarchive.services.tor
+
+enum class TorStatus {
+ DISCONNECTED,
+ CONNECTING,
+ CONNECTED,
+ DISCONNECTING,
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/services/tor/TorViewModel.kt b/app/src/main/java/net/opendasharchive/openarchive/services/tor/TorViewModel.kt
new file mode 100644
index 00000000..426663d4
--- /dev/null
+++ b/app/src/main/java/net/opendasharchive/openarchive/services/tor/TorViewModel.kt
@@ -0,0 +1,71 @@
+package net.opendasharchive.openarchive.services.tor
+
+import android.app.Activity
+import android.app.Application
+import androidx.lifecycle.AndroidViewModel
+import info.guardianproject.netcipher.proxy.OrbotHelper
+import info.guardianproject.netcipher.proxy.OrbotHelper.InstallCallback
+import kotlinx.coroutines.flow.StateFlow
+import timber.log.Timber
+
+
+class TorViewModel(
+ private val application: Application,
+ torRepository: ITorRepository,
+) : AndroidViewModel(application), InstallCallback {
+
+ init {
+ OrbotHelper.get(application).addStatusCallback(torRepository)
+ }
+ val torStatus: StateFlow = torRepository.torStatus
+
+ fun toggleTorServiceState(activity: Activity, enabled: Boolean) {
+ if (enabled) {
+ startTor(activity)
+ }
+ }
+
+ fun requestOpenOrInstallOrbot(activity: Activity) {
+ if (OrbotHelper.isOrbotInstalled(application)) {
+ requestOpenOrbot(activity)
+ } else {
+ OrbotHelper.get(application).apply {
+ addInstallCallback(this@TorViewModel)
+ installOrbot(activity)
+ }
+ }
+ }
+
+ private fun startTor(activity: Activity) {
+ if (OrbotHelper.isOrbotInstalled(application)) {
+ OrbotHelper.requestStartTor(application)
+ } else {
+ OrbotHelper.get(application).addInstallCallback(this)
+ OrbotHelper.get(application).installOrbot(activity)
+ }
+ }
+
+ private fun requestOpenOrbot(activity: Activity): Boolean {
+ if (!OrbotHelper.isOrbotInstalled(application)) {
+ return false
+ }
+ val intent =
+ activity.packageManager.getLaunchIntentForPackage(OrbotHelper.ORBOT_PACKAGE_NAME)
+ if (intent == null) {
+ Timber.e("Orbot is not installed.")
+ return false
+ }
+
+ activity.startActivity(intent)
+ return true
+ }
+
+ override fun onInstalled() {
+ OrbotHelper.get(application).removeInstallCallback(this)
+ OrbotHelper.requestStartTor(application)
+ }
+
+ override fun onInstallTimeout() {
+ Timber.e("timeout on orbot install")
+ }
+}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/services/webdav/BasicAuthInterceptor.kt b/app/src/main/java/net/opendasharchive/openarchive/services/webdav/BasicAuthInterceptor.kt
deleted file mode 100644
index 8f956d75..00000000
--- a/app/src/main/java/net/opendasharchive/openarchive/services/webdav/BasicAuthInterceptor.kt
+++ /dev/null
@@ -1,22 +0,0 @@
-package net.opendasharchive.openarchive.services.webdav
-
-import okhttp3.Credentials.basic
-import okhttp3.Interceptor
-import okhttp3.Request
-import okhttp3.Response
-import java.io.IOException
-import kotlin.Throws
-
-class BasicAuthInterceptor(user: String?, password: String?) : Interceptor {
- private val credentials: String
- @Throws(IOException::class)
- override fun intercept(chain: Interceptor.Chain): Response {
- val request: Request = chain.request()
- val authenticatedRequest = request.newBuilder()
- .header("Authorization", credentials).build()
- return chain.proceed(authenticatedRequest)
- }
- init {
- credentials = basic(user!!, password!!)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/services/webdav/WebDavActivity.kt b/app/src/main/java/net/opendasharchive/openarchive/services/webdav/WebDavActivity.kt
deleted file mode 100644
index 22625ab7..00000000
--- a/app/src/main/java/net/opendasharchive/openarchive/services/webdav/WebDavActivity.kt
+++ /dev/null
@@ -1,71 +0,0 @@
-package net.opendasharchive.openarchive.services.webdav
-
-import android.content.Intent
-import android.os.Bundle
-import android.view.MenuItem
-import androidx.fragment.app.commit
-import net.opendasharchive.openarchive.R
-import net.opendasharchive.openarchive.databinding.ActivityWebdavBinding
-import net.opendasharchive.openarchive.db.Space
-import net.opendasharchive.openarchive.features.core.BaseActivity
-import net.opendasharchive.openarchive.features.main.MainActivity
-import kotlin.properties.Delegates
-
-class WebDavActivity : BaseActivity() {
-
- companion object {
- const val FRAGMENT_TAG = "webdav_fragment"
- }
-
- private lateinit var mBinding: ActivityWebdavBinding
- private var mSpaceId by Delegates.notNull()
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- mBinding = ActivityWebdavBinding.inflate(layoutInflater)
- setContentView(mBinding.root)
-
- setupToolbar(title = getString(R.string.edit_private_server), showBackButton = true)
-
- mSpaceId = intent.getLongExtra(EXTRA_DATA_SPACE, WebDavFragment.ARG_VAL_NEW_SPACE)
-
- if (mSpaceId != WebDavFragment.ARG_VAL_NEW_SPACE) {
- supportFragmentManager.commit {
- replace(mBinding.webDavFragment.id, WebDavFragment.newInstance(mSpaceId))
- }
- }
-
- supportFragmentManager.setFragmentResultListener(WebDavFragment.RESP_SAVED, this) { _, _ ->
- finishAffinity()
- startActivity(Intent(this, MainActivity::class.java))
- }
-
- supportFragmentManager.setFragmentResultListener(WebDavFragment.RESP_DELETED, this) { _, _ ->
- Space.navigate(this)
- }
-
- supportFragmentManager.setFragmentResultListener(WebDavFragment.RESP_LICENSE, this) { _, _ ->
- // Navigate to license fragment
- // also update title with server name if available - like breadcrumb
- supportFragmentManager
- .beginTransaction()
- .setCustomAnimations(R.anim.slide_in_right, R.anim.slide_out_left)
- .replace(
- mBinding.webDavFragment.id,
- WebDavSetupLicenseFragment.newInstance(spaceId = mSpaceId, isEditing = true),
- FRAGMENT_TAG,
- )
- .commit()
- }
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- // handle appbar back button tap
- if (item.itemId == android.R.id.home) {
- finish()
- return true
- }
- return super.onOptionsItemSelected(item)
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/services/webdav/WebDavConduit.kt b/app/src/main/java/net/opendasharchive/openarchive/services/webdav/WebDavConduit.kt
index e95860e7..de631794 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/services/webdav/WebDavConduit.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/services/webdav/WebDavConduit.kt
@@ -8,21 +8,23 @@ import net.opendasharchive.openarchive.db.Media
import net.opendasharchive.openarchive.services.Conduit
import net.opendasharchive.openarchive.services.SaveClient
import okhttp3.HttpUrl
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
import java.io.IOException
-import java.util.*
-class WebDavConduit(media: Media, context: Context) : Conduit(media, context) {
+class WebDavConduit(media: Media, context: Context) : Conduit(media, context), KoinComponent {
- private lateinit var mClient: OkHttpSardine
+ private val client: SaveClient by inject()
+
+ private val space = mMedia.space!!
+
+ private var webdav = client.webdav(space)
override suspend fun upload(): Boolean {
- val space = mMedia.space ?: return false
val base = space.hostUrl ?: return false
val path = getPath() ?: return false
- mClient = SaveClient.getSardine(mContext, space)
-
sanitize()
val fileName = getUploadFileName(mMedia)
@@ -30,7 +32,7 @@ class WebDavConduit(media: Media, context: Context) : Conduit(media, context) {
try {
createFolders(base, path)
- uploadMetadata(base, path, fileName)
+ webdav.uploadMetadata(base, path, fileName)
}
catch (e: Throwable) {
jobFailed(e)
@@ -44,14 +46,14 @@ class WebDavConduit(media: Media, context: Context) : Conduit(media, context) {
AppLogger.i("Begin media file upload...")
if (mMedia.contentLength > CHUNK_FILESIZE_THRESHOLD) {
- return uploadChunked(base, path, fileName)
+ return webdav.uploadChunked(base, path, fileName)
}
val fullPath = construct(base, path, fileName)
AppLogger.i("Uploading started for single file upload...", "filePath: $fullPath")
try {
- mClient.put(mContext.contentResolver,
+ webdav.put(mContext.contentResolver,
fullPath,
mMedia.fileUri,
mMedia.contentLength,
@@ -87,16 +89,17 @@ class WebDavConduit(media: Media, context: Context) : Conduit(media, context) {
}
override suspend fun createFolder(url: String) {
- if (!mClient.exists(url)) {
- mClient.createDirectory(url)
+ if (!webdav.exists(url)) {
+ webdav.createDirectory(url)
} else {
AppLogger.i("folder already exists: ", url)
}
}
@Throws(IOException::class)
- private suspend fun uploadChunked(base: HttpUrl, path: List, fileName: String): Boolean {
+ private suspend fun OkHttpSardine.uploadChunked(base: HttpUrl, path: List, fileName: String): Boolean {
AppLogger.i("Uploading started as chunked upload...")
+
val space = mMedia.space ?: return false
val url = space.hostUrl ?: return false
@@ -135,17 +138,17 @@ class WebDavConduit(media: Media, context: Context) : Conduit(media, context) {
val total = offset + length
val chunkPath = construct(tmpBase, tmpPath, "$offset-$total")
- val chunkExists = mClient.exists(chunkPath)
+ val chunkExists = exists(chunkPath)
var chunkLengthMatches = false
if (chunkExists) {
- val dirList = mClient.list(chunkPath)
+ val dirList = list(chunkPath)
chunkLengthMatches =
!dirList.isNullOrEmpty() && dirList.first().contentLength == length.toLong()
}
if (!chunkExists || !chunkLengthMatches) {
- mClient.put(
+ put(
chunkPath,
buffer,
mMedia.mimeType,
@@ -170,7 +173,7 @@ class WebDavConduit(media: Media, context: Context) : Conduit(media, context) {
val dest = mutableListOf("files", space.username)
dest.addAll(path)
- mClient.move(construct(tmpBase, tmpPath, ".file"), construct(tmpBase, dest, fileName))
+ move(construct(tmpBase, tmpPath, ".file"), construct(tmpBase, dest, fileName))
mMedia.serverUrl = construct(base, path, fileName)
@@ -185,13 +188,13 @@ class WebDavConduit(media: Media, context: Context) : Conduit(media, context) {
}
}
- private fun uploadMetadata(base: HttpUrl, path: List, fileName: String) {
+ private fun OkHttpSardine.uploadMetadata(base: HttpUrl, path: List, fileName: String) {
AppLogger.i("Uploading metadata....")
val metadata = getMetadata()
if (mCancelled) throw Exception("Cancelled")
- mClient.put(
+ put(
construct(base, path, "$fileName.meta.json"),
metadata.toByteArray(),
"text/plain",
@@ -202,7 +205,7 @@ class WebDavConduit(media: Media, context: Context) : Conduit(media, context) {
for (file in getProof()) {
if (mCancelled) throw Exception("Cancelled")
- mClient.put(
+ put(
construct(base, path, file.name), file, "text/plain",
false, null)
}
diff --git a/app/src/main/java/net/opendasharchive/openarchive/services/webdav/WebDavFragment.kt b/app/src/main/java/net/opendasharchive/openarchive/services/webdav/WebDavFragment.kt
index ff7288df..37ad2881 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/services/webdav/WebDavFragment.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/services/webdav/WebDavFragment.kt
@@ -45,8 +45,10 @@ import net.opendasharchive.openarchive.util.extensions.makeSnackBar
import net.opendasharchive.openarchive.util.extensions.show
import okhttp3.Call
import okhttp3.Callback
+import okhttp3.Credentials
import okhttp3.Request
import okhttp3.Response
+import org.koin.java.KoinJavaComponent.inject
import java.io.IOException
import kotlin.coroutines.suspendCoroutine
@@ -60,6 +62,8 @@ class WebDavFragment : BaseFragment() {
private var originalName: String? = null
private var isNameChanged = false
+ private val client: SaveClient by inject(SaveClient::class.java)
+
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
mSpaceId = arguments?.getLong(ARG_SPACE_ID) ?: ARG_VAL_NEW_SPACE
@@ -429,10 +433,9 @@ class WebDavFragment : BaseFragment() {
private suspend fun testConnection() {
val url = mSpace.hostUrl ?: throw IOException("400 Bad Request")
- val client = SaveClient.get(requireContext(), mSpace.username, mSpace.password)
-
val request =
Request.Builder().url(url).method("GET", null).addHeader("OCS-APIRequest", "true")
+ .addHeader("Authorization", Credentials.basic(mSpace.username, mSpace.password))
.addHeader("Accept", "application/json").build()
return suspendCoroutine {
diff --git a/app/src/main/java/net/opendasharchive/openarchive/upload/UploadManagerActivity.kt b/app/src/main/java/net/opendasharchive/openarchive/upload/UploadManagerActivity.kt
deleted file mode 100644
index f56093dd..00000000
--- a/app/src/main/java/net/opendasharchive/openarchive/upload/UploadManagerActivity.kt
+++ /dev/null
@@ -1,151 +0,0 @@
-package net.opendasharchive.openarchive.upload
-
-import android.content.BroadcastReceiver
-import android.content.Context
-import android.content.Intent
-import android.os.Bundle
-import android.os.Handler
-import android.os.Looper
-import android.view.Menu
-import android.view.MenuItem
-import net.opendasharchive.openarchive.CleanInsightsManager
-import net.opendasharchive.openarchive.R
-import net.opendasharchive.openarchive.databinding.ActivityUploadManagerBinding
-import net.opendasharchive.openarchive.db.Media
-import net.opendasharchive.openarchive.features.core.BaseActivity
-
-class UploadManagerActivity : BaseActivity() {
-
- private lateinit var mBinding: ActivityUploadManagerBinding
- var mFrag: UploadManagerFragment? = null
- private var mMenuEdit: MenuItem? = null
-
- private val mMessageReceiver: BroadcastReceiver = object : BroadcastReceiver() {
- private val handler = Handler(Looper.getMainLooper())
-
- override fun onReceive(context: Context, intent: Intent) {
- val action = BroadcastManager.getAction(intent)
- val mediaId = action?.mediaId ?: return
-
- if (mediaId > -1) {
- val media = Media.get(mediaId)
-
- if (action == BroadcastManager.Action.Delete || media?.sStatus == Media.Status.Uploaded) {
- handler.post { mFrag?.removeItem(mediaId) }
- }
- else {
- handler.post { mFrag?.updateItem(mediaId) }
- }
- }
- }
- }
-
- private var mEditMode = true // Setting Edit mode as the default mode
-
- override fun onCreate(savedInstanceState: Bundle?) {
- super.onCreate(savedInstanceState)
-
- mBinding = ActivityUploadManagerBinding.inflate(layoutInflater)
- setContentView(mBinding.root)
-
- setupToolbar(
- title = getString(R.string.upload_manager_screen_title),
- subtitle = getString(R.string.upload_manager_screen_subtitle),
- showBackButton = true
- )
-
- //mFrag = supportFragmentManager.findFragmentById(R.id.fragUploadManager) as? UploadManagerFragment
-
- val bottomSheet = UploadManagerFragment()
- bottomSheet.show(supportFragmentManager, UploadManagerFragment.TAG)
- }
-
- override fun onResume() {
- super.onResume()
- mFrag?.refresh()
-
- BroadcastManager.register(this, mMessageReceiver)
-
- onStartEdit()
- }
-
- override fun onPause() {
- super.onPause()
-
- BroadcastManager.unregister(this, mMessageReceiver)
- }
-
- private fun onStartEdit() {
- UploadService.stopUploadService(this)
- }
-
- private fun onCompleteEdit() {
- UploadService.startUploadService(this)
- }
-
- private fun toggleEditMode() {
- mEditMode = !mEditMode
-
- mFrag?.refresh()
-
- if (mEditMode) {
- mMenuEdit?.setTitle(R.string.menu_done)
-
- UploadService.stopUploadService(this)
- }
- else {
- mMenuEdit?.setTitle(R.string.edit)
-
- UploadService.startUploadService(this)
- }
-
- updateTitle()
- }
-
- override fun onCreateOptionsMenu(menu: Menu): Boolean {
- menuInflater.inflate(R.menu.menu_upload, menu)
- mMenuEdit = menu.findItem(R.id.menu_done)
-
- return super.onCreateOptionsMenu(menu)
- }
-
- override fun onOptionsItemSelected(item: MenuItem): Boolean {
- when (item.itemId) {
- android.R.id.home -> {
- finish()
- return true
- }
- R.id.menu_done -> {
- onCompleteEdit()
- return true
- }
- }
- return super.onOptionsItemSelected(item)
- }
-
- override fun finish() {
- // If we're still in edit mode, restart the upload service when the user leaves.
- if (mEditMode) {
- UploadService.startUploadService(this)
- }
-
- super.finish()
- }
-
- private fun updateTitle() {
- if (mEditMode) {
- supportActionBar?.title = getString(R.string.edit_media)
- supportActionBar?.subtitle = getString(R.string.uploading_is_paused)
- } else {
- val count = mFrag?.getUploadingCounter() ?: 0
-
- supportActionBar?.title = if (count < 1) {
- getString(R.string.uploads)
- } else {
- getString(R.string.uploading_left, count)
- }
-
- supportActionBar?.subtitle = null
- }
- }
-}
\ No newline at end of file
diff --git a/app/src/main/java/net/opendasharchive/openarchive/upload/UploadService.kt b/app/src/main/java/net/opendasharchive/openarchive/upload/UploadService.kt
index 02054497..929b811c 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/upload/UploadService.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/upload/UploadService.kt
@@ -14,6 +14,7 @@ import android.os.Build
import androidx.core.app.NotificationCompat
import androidx.core.content.ContextCompat
import androidx.work.Configuration
+import info.guardianproject.netcipher.proxy.OrbotHelper
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.launch
@@ -23,11 +24,17 @@ import net.opendasharchive.openarchive.core.logger.AppLogger
import net.opendasharchive.openarchive.db.Media
import net.opendasharchive.openarchive.features.main.MainActivity
import net.opendasharchive.openarchive.services.Conduit
+import net.opendasharchive.openarchive.services.tor.ITorRepository
+import net.opendasharchive.openarchive.services.tor.TorStatus
import net.opendasharchive.openarchive.util.Prefs
+import org.koin.core.component.KoinComponent
+import org.koin.core.component.inject
+import org.koin.core.qualifier.named
+import timber.log.Timber
import java.io.IOException
import java.util.*
-class UploadService : JobService() {
+class UploadService : JobService(), KoinComponent {
companion object {
private const val MY_BACKGROUND_JOB = 0
@@ -56,6 +63,8 @@ class UploadService : JobService() {
private var mRunning = false
private var mKeepUploading = true
private val mConduits = ArrayList()
+ private lateinit var notification: Notification
+ private val torRepo: ITorRepository by inject(named("tor"))
override fun onCreate() {
super.onCreate()
@@ -200,6 +209,10 @@ class UploadService : JobService() {
return false
}
+ private fun isTorAvailable(): Boolean {
+ return torRepo.torStatus.value == TorStatus.CONNECTED
+ }
+
private fun isNetworkAvailable(requireUnmetered: Boolean): Boolean {
val cm =
getSystemService(Context.CONNECTIVITY_SERVICE) as? ConnectivityManager ?: return false
diff --git a/app/src/main/java/net/opendasharchive/openarchive/util/extensions/PackageManager.kt b/app/src/main/java/net/opendasharchive/openarchive/util/extensions/PackageManager.kt
index 59ff2771..796e9436 100644
--- a/app/src/main/java/net/opendasharchive/openarchive/util/extensions/PackageManager.kt
+++ b/app/src/main/java/net/opendasharchive/openarchive/util/extensions/PackageManager.kt
@@ -3,7 +3,7 @@ package net.opendasharchive.openarchive.util.extensions
import android.content.pm.PackageManager
import android.os.Build
-fun PackageManager.getVersionName(packageName: String): String {
+fun PackageManager.getVersionName(packageName: String): String? {
return try {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.TIRAMISU) {
getPackageInfo(packageName, PackageManager.PackageInfoFlags.of(0))
@@ -12,7 +12,7 @@ fun PackageManager.getVersionName(packageName: String): String {
getPackageInfo(packageName, 0)
}.versionName
- } catch (e: PackageManager.NameNotFoundException) {
+ } catch (_: PackageManager.NameNotFoundException) {
"unknown"
}
}
diff --git a/app/src/main/res/layout/activity_add_folder.xml b/app/src/main/res/layout/activity_add_folder.xml
deleted file mode 100644
index 42438c74..00000000
--- a/app/src/main/res/layout/activity_add_folder.xml
+++ /dev/null
@@ -1,172 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_settings_container.xml b/app/src/main/res/layout/activity_proofmode_settings.xml
similarity index 96%
rename from app/src/main/res/layout/activity_settings_container.xml
rename to app/src/main/res/layout/activity_proofmode_settings.xml
index 0392c67a..f81cec18 100644
--- a/app/src/main/res/layout/activity_settings_container.xml
+++ b/app/src/main/res/layout/activity_proofmode_settings.xml
@@ -7,8 +7,7 @@
android:layout_height="match_parent"
android:filterTouchesWhenObscured="true"
android:gravity="center_horizontal"
- android:orientation="vertical"
- tools:context=".features.settings.GeneralSettingsActivity">
+ android:orientation="vertical">
-
-
-
-
-
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/activity_webdav.xml b/app/src/main/res/layout/activity_webdav.xml
deleted file mode 100644
index 15d46d7f..00000000
--- a/app/src/main/res/layout/activity_webdav.xml
+++ /dev/null
@@ -1,41 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/content_upload_manager.xml b/app/src/main/res/layout/content_upload_manager.xml
deleted file mode 100644
index 66c4f746..00000000
--- a/app/src/main/res/layout/content_upload_manager.xml
+++ /dev/null
@@ -1,18 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/activity_edit_folder.xml b/app/src/main/res/layout/fragment_edit_folder.xml
similarity index 94%
rename from app/src/main/res/layout/activity_edit_folder.xml
rename to app/src/main/res/layout/fragment_edit_folder.xml
index 125ab7ce..db661aa5 100644
--- a/app/src/main/res/layout/activity_edit_folder.xml
+++ b/app/src/main/res/layout/fragment_edit_folder.xml
@@ -7,12 +7,7 @@
android:filterTouchesWhenObscured="true"
android:orientation="vertical"
tools:background="@color/colorBackground"
- tools:context=".features.settings.EditFolderActivity">
-
-
-
+ tools:context=".features.settings.EditFolderFragment">
+ app:layout_constraintTop_toTopOf="parent" />
-
-
-
-
\ No newline at end of file
diff --git a/app/src/main/res/layout/activity_folders.xml b/app/src/main/res/layout/fragment_folder_list.xml
similarity index 55%
rename from app/src/main/res/layout/activity_folders.xml
rename to app/src/main/res/layout/fragment_folder_list.xml
index 4ef843c1..f6cacaca 100644
--- a/app/src/main/res/layout/activity_folders.xml
+++ b/app/src/main/res/layout/fragment_folder_list.xml
@@ -1,54 +1,40 @@
+ tools:context=".features.settings.FolderListFragment">
+
+ android:orientation="vertical">
-
-
-
-
+ android:layout_margin="@dimen/activity_vertical_margin"
+ android:layout_marginHorizontal="8dp" />
+ android:textSize="18sp"
+ tools:visibility="gone" />
\ No newline at end of file
diff --git a/app/src/main/res/layout/fragment_space_list.xml b/app/src/main/res/layout/fragment_space_list.xml
deleted file mode 100644
index 396fb067..00000000
--- a/app/src/main/res/layout/fragment_space_list.xml
+++ /dev/null
@@ -1,16 +0,0 @@
-
-
-
-
-
-
diff --git a/app/src/main/res/layout/prefs_open_orbot.xml b/app/src/main/res/layout/prefs_open_orbot.xml
new file mode 100644
index 00000000..ec556ffc
--- /dev/null
+++ b/app/src/main/res/layout/prefs_open_orbot.xml
@@ -0,0 +1,14 @@
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/navigation/space_setup_navigation.xml b/app/src/main/res/navigation/space_setup_navigation.xml
index a105f47a..09666308 100644
--- a/app/src/main/res/navigation/space_setup_navigation.xml
+++ b/app/src/main/res/navigation/space_setup_navigation.xml
@@ -18,6 +18,13 @@
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
+
@@ -32,23 +39,24 @@
app:exitAnim="@anim/slide_out_left"
app:popEnterAnim="@anim/slide_in_left"
app:popExitAnim="@anim/slide_out_right" />
+
-
+ android:id="@+id/fragment_internet_archive_login"
+ android:name="net.opendasharchive.openarchive.features.internetarchive.presentation.login.InternetArchiveLoginFragment"
+ android:label="InternetArchiveLoginFragment">
+
+
-
+
+
+
+
+
+
+
+
+
+
+ android:label="@string/add_a_folder">
-
-
+ tools:layout="@layout/fragment_browse_folders" />
+ tools:layout="@layout/fragment_create_new_folder" />
+
+
+
+
\ 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 c99dfbc2..888800d8 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -400,6 +400,14 @@
Create one
No account?
Continue
+ Tor is enabled and Orbot is connected.
+ Tor is not enabled but Orbot is connected.
+ Tor is enabled and Orbot is starting…
+ Tor is not enabled but Orbot is starting…
+ Tor is enabled but Orbot is disconnected.
+ Tor is not enabled and Orbot is disconnected.
+ Tor Status:
+ Open Orbot
@@ -453,4 +461,5 @@
No servers added yet.
No archived folders found.
Internet not available
+ unavailable
diff --git a/app/src/main/res/xml/prefs_general.xml b/app/src/main/res/xml/prefs_general.xml
index 11c24600..599cc77f 100644
--- a/app/src/main/res/xml/prefs_general.xml
+++ b/app/src/main/res/xml/prefs_general.xml
@@ -95,17 +95,37 @@
android:title="Encrypt">
+ app:iconSpaceReserved="false"
+ app:key="@string/pref_key_use_tor"
+ app:singleLineTitle="false"
+ app:summary="@string/prefs_use_tor_summary"
+ app:title="@string/prefs_use_tor_title" />
+
+
+
+
+
diff --git a/gradle/libs.versions.toml b/gradle/libs.versions.toml
index bb2e1806..0dfb2d8a 100644
--- a/gradle/libs.versions.toml
+++ b/gradle/libs.versions.toml
@@ -1,6 +1,6 @@
[versions]
activity = "1.9.3"
-agp = "8.9.2"
+agp = "8.10.0"
appcompat = "1.7.0"
biometric = "1.1.0"
coil = "3.0.4"
@@ -194,7 +194,6 @@ satyan-sugar = { group = "com.github.satyan", name = "sugar", version.ref = "sat
audio-waveform = { group = "com.github.derlio", name = "audio-waveform", version.ref = "audio-waveform" }
clean-insights = { group = "org.cleaninsights.sdk", name = "clean-insights-sdk", version.ref = "clean-insights" }
-netcipher = { group = "info.guardianproject.netcipher", name = "netcipher", version.ref = "netcipher" }
mixpanel = { group = "com.mixpanel.android", name = "mixpanel-android", version.ref = "mixpanel" }
firebase-crashlytics = { group = "com.google.firebase", name = "firebase-crashlytics", version.ref = "firebase-crashlytics" }
diff --git a/settings.gradle.kts b/settings.gradle.kts
index abaf95ae..037b5ed1 100644
--- a/settings.gradle.kts
+++ b/settings.gradle.kts
@@ -76,6 +76,10 @@ dependencyResolutionManagement {
google()
mavenCentral()
+ flatDir {
+ dirs("libs")
+ }
+
gradlePluginPortal()
maven("https://raw.githubusercontent.com/guardianproject/gpmaven/master") {
content {