1
0
Fork 0
mirror of https://github.com/anyproto/anytype-kotlin.git synced 2025-06-08 05:47:05 +09:00

Tech | API 31 (#2031)

* Tech | API 30, Local Network Address (#2004)

* 30 api + java 11 + scoped storage

* update network address handler

* fix

* fix

* put back permission

* fix

* fix

* fix

* ci

* set tests off

Co-authored-by: konstantiniiv <ki@anytype.io>

* Fix | JVM version (#2033)

* Tech | Update to API 31 (#2032)

* update to API 31

* fix

Co-authored-by: konstantiniiv <ki@anytype.io>

* Fix | Network address handler (#2034)

* Tech | Editor, update permissions (#2039)

* add permission

* remove legacy

* fix permissions

* fix

* fix

* add ext fun

Co-authored-by: konstantiniiv <ki@anytype.io>

* Tech | Permissions in Relations Value (#2041)

* add permission

* remove legacy

* fix permissions

* fix

* fix

* add ext fun

* permissions in relations

Co-authored-by: konstantiniiv <ki@anytype.io>

* Tech | Update file logic to SAF (#2048)

* update file logic

* fixes

* fix

* fix

* fixes

* fixes

* fix

* fix

* rename

* fix

* fix

* style

* fix

* delete legacy test

* add kdoc

Co-authored-by: konstantiniiv <ki@anytype.io>

* Tech | Add signing (#2047)

* add signing

* rename

* fixes

* debug signing

Co-authored-by: E. Kozlov <ubuphobos@gmail.com>
Co-authored-by: konstantiniiv <ki@anytype.io>

* Tech | API 31, files refactoring (#2053)

* refactoring

* fix

* fix

* fix

* fix

Co-authored-by: konstantiniiv <ki@anytype.io>

* put back create page

* fix tests

* temporary turn off signing

* fix viewmodel nullable

* ci off

Co-authored-by: konstantiniiv <ki@anytype.io>
Co-authored-by: E. Kozlov <ubuphobos@gmail.com>
This commit is contained in:
Konstantin Ivanov 2022-01-26 16:14:35 +02:00 committed by GitHub
parent b5ff81394c
commit 312289768a
Signed by: github
GPG key ID: 4AEE18F83AFDEB23
100 changed files with 1022 additions and 492 deletions

1
.gitignore vendored
View file

@ -13,3 +13,4 @@
.externalNativeBuild
ktlint
.idea/*.xml
signing.properties

View file

@ -25,8 +25,8 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
}

View file

@ -12,6 +12,10 @@ def apikeyPropertiesFile = rootProject.file("apikeys.properties")
def apikeyProperties = new Properties()
apikeyProperties.load(new FileInputStream(apikeyPropertiesFile))
//def keystorePropertiesFile = rootProject.file("signing.properties")
//def keystoreProperties = new Properties()
//keystoreProperties.load(new FileInputStream(keystorePropertiesFile))
android {
def config = rootProject.extensions.getByName("ext")
@ -26,15 +30,13 @@ android {
versionName getBuildVersionName()
testInstrumentationRunner config["test_runner"]
}
packagingOptions {
exclude 'LICENSE.txt'
exclude 'META-INF/DEPENDENCIES'
exclude 'META-INF/ASL2.0'
exclude 'META-INF/NOTICE'
exclude 'META-INF/LICENSE'
resources {
excludes += ['LICENSE.txt', 'META-INF/DEPENDENCIES', 'META-INF/ASL2.0', 'META-INF/NOTICE', 'META-INF/LICENSE']
}
}
lintOptions {
quiet true
abortOnError false
@ -45,18 +47,39 @@ android {
disable 'IconMissingDensityFolder' //For testing purpose. This is safe to remove.
}
// signingConfigs {
// //For proper signing, use debuggable false for a build.
// release {
// keyAlias keystoreProperties['RELEASE_KEY_ALIAS']
// keyPassword keystoreProperties['RELEASE_KEY_PASSWORD']
// storeFile file(keystoreProperties['RELEASE_STORE_FILE'])
// storePassword keystoreProperties['RELEASE_STORE_PASSWORD']
// v1SigningEnabled true
// v2SigningEnabled true
// }
// debug {
// keyAlias keystoreProperties['DEBUG_KEY_ALIAS']
// keyPassword keystoreProperties['DEBUG_KEY_PASSWORD']
// storeFile file(keystoreProperties['DEBUG_STORE_FILE'])
// storePassword keystoreProperties['DEBUG_STORE_PASSWORD']
// v1SigningEnabled true
// v2SigningEnabled true
// }
// }
buildTypes {
release {
minifyEnabled false
useProguard false
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
buildConfigField("String", "AMPLITUDE_KEY", apikeyProperties['amplitude.release'])
//signingConfig signingConfigs.release
}
debug {
applicationIdSuffix ".debug"
debuggable true
buildConfigField("String", "AMPLITUDE_KEY", apikeyProperties['amplitude.debug'])
//signingConfig signingConfigs.debug
}
}
@ -90,12 +113,12 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = JavaVersion.VERSION_11
}
androidExtensions {
@ -131,7 +154,6 @@ dependencies {
//Compile time dependencies
kapt applicationDependencies.daggerCompiler
kapt applicationDependencies.glideCompiler
kapt applicationDependencies.permissionDispCompiler
compileOnly applicationDependencies.javaxAnnotation
compileOnly applicationDependencies.javaxInject
@ -149,7 +171,6 @@ dependencies {
implementation applicationDependencies.dagger
implementation applicationDependencies.timber
implementation applicationDependencies.gson
implementation applicationDependencies.permissionDisp
implementation applicationDependencies.pickT
implementation applicationDependencies.emojiCompat

View file

@ -22,6 +22,7 @@
<activity
android:name="com.anytypeio.anytype.ui.main.MainActivity"
android:screenOrientation="portrait"
android:exported="true"
android:launchMode="singleTop"
android:windowSoftInputMode="stateHidden|adjustResize">
<intent-filter android:label="filter">
@ -43,6 +44,7 @@
<activity
android:name="com.journeyapps.barcodescanner.CaptureActivity"
android:screenOrientation="fullSensor"
android:exported="false"
tools:replace="screenOrientation" />
</application>

View file

@ -13,6 +13,7 @@ import com.anytypeio.anytype.di.common.ComponentManager
import com.anytypeio.anytype.di.main.ContextModule
import com.anytypeio.anytype.di.main.DaggerMainComponent
import com.anytypeio.anytype.di.main.MainComponent
import com.anytypeio.anytype.middleware.interactor.LocalNetworkAddressHandler
import timber.log.Timber
import javax.inject.Inject
@ -21,6 +22,9 @@ class AndroidApplication : Application() {
@Inject
lateinit var amplitudeTracker: AmplitudeTracker
@Inject
lateinit var localNetworkAddressHandler: LocalNetworkAddressHandler
private val main: MainComponent by lazy {
DaggerMainComponent
.builder()
@ -38,6 +42,7 @@ class AndroidApplication : Application() {
setupAnalytics()
setupEmojiCompat()
setupTimber()
setupLocalNetworkAddressHandler()
}
private fun setupEmojiCompat() {
@ -61,4 +66,8 @@ class AndroidApplication : Application() {
private fun setupAnalytics() {
Amplitude.getInstance().initialize(this, BuildConfig.AMPLITUDE_KEY)
}
private fun setupLocalNetworkAddressHandler() {
localNetworkAddressHandler.start()
}
}

View file

@ -1,5 +1,6 @@
package com.anytypeio.anytype.di.feature
import android.content.Context
import com.anytypeio.anytype.analytics.base.Analytics
import com.anytypeio.anytype.core_models.Block
import com.anytypeio.anytype.core_models.Id
@ -52,6 +53,8 @@ import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer
import com.anytypeio.anytype.presentation.editor.selection.SelectionStateHolder
import com.anytypeio.anytype.presentation.editor.toggle.ToggleStateHolder
import com.anytypeio.anytype.presentation.relations.providers.*
import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
import com.anytypeio.anytype.presentation.util.DefaultCopyFileToCacheDirectory
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.providers.DefaultCoverImageHashProvider
import com.anytypeio.anytype.ui.editor.EditorFragment
@ -147,7 +150,8 @@ object EditorSessionModule {
objectTypesProvider: ObjectTypesProvider,
searchObjects: SearchObjects,
getDefaultEditorType: GetDefaultEditorType,
findObjectSetForType: FindObjectSetForType
findObjectSetForType: FindObjectSetForType,
copyFileToCacheDirectory: CopyFileToCacheDirectory
): EditorViewModelFactory = EditorViewModelFactory(
openPage = openPage,
closeObject = closePage,
@ -173,7 +177,8 @@ object EditorSessionModule {
searchObjects = searchObjects,
getDefaultEditorType = getDefaultEditorType,
findObjectSetForType = findObjectSetForType,
createObjectSet = createObjectSet
createObjectSet = createObjectSet,
copyFileToCacheDirectory = copyFileToCacheDirectory
)
@JvmStatic
@ -752,4 +757,11 @@ object EditorUseCaseModule {
fun provideCreateObjectSetUseCase(
repo: BlockRepository
): CreateObjectSet = CreateObjectSet(repo = repo)
@JvmStatic
@Provides
@PerScreen
fun provideCopyFileToCache(
context: Context
): CopyFileToCacheDirectory = DefaultCopyFileToCacheDirectory(context)
}

View file

@ -1,5 +1,6 @@
package com.anytypeio.anytype.di.feature
import android.content.Context
import com.anytypeio.anytype.core_models.Payload
import com.anytypeio.anytype.core_utils.di.scope.PerModal
import com.anytypeio.anytype.domain.`object`.ObjectTypesProvider
@ -14,6 +15,8 @@ import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProv
import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvider
import com.anytypeio.anytype.presentation.sets.RelationValueDVViewModel
import com.anytypeio.anytype.presentation.sets.RelationValueViewModel
import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
import com.anytypeio.anytype.presentation.util.DefaultCopyFileToCacheDirectory
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.ui.relations.RelationValueDVFragment
import com.anytypeio.anytype.ui.relations.RelationValueFragment
@ -88,6 +91,13 @@ object ObjectRelationValueModule {
@Module
object ObjectSetObjectRelationValueModule {
@JvmStatic
@Provides
@PerModal
fun provideCopyFileToCache(
context: Context
): CopyFileToCacheDirectory = DefaultCopyFileToCacheDirectory(context)
@JvmStatic
@Provides
@PerModal
@ -99,9 +109,9 @@ object ObjectSetObjectRelationValueModule {
removeTagFromDataViewRecord: RemoveTagFromDataViewRecord,
removeStatusFromDataViewRecord: RemoveStatusFromDataViewRecord,
urlBuilder: UrlBuilder,
dispatcher: Dispatcher<Payload>,
updateDataViewRecord: UpdateDataViewRecord,
addFileToRecord: AddFileToRecord
addFileToRecord: AddFileToRecord,
copyFileToCacheDirectory: CopyFileToCacheDirectory
): RelationValueDVViewModel.Factory = RelationValueDVViewModel.Factory(
relations = relations,
values = values,
@ -110,9 +120,9 @@ object ObjectSetObjectRelationValueModule {
removeTagFromRecord = removeTagFromDataViewRecord,
removeStatusFromDataViewRecord = removeStatusFromDataViewRecord,
urlBuilder = urlBuilder,
dispatcher = dispatcher,
updateDataViewRecord = updateDataViewRecord,
addFileToRecord = addFileToRecord
addFileToRecord = addFileToRecord,
copyFileToCache = copyFileToCacheDirectory
)
}
@ -130,7 +140,8 @@ object ObjectObjectRelationValueModule {
urlBuilder: UrlBuilder,
dispatcher: Dispatcher<Payload>,
updateDetail: UpdateDetail,
addFileToObject: AddFileToObject
addFileToObject: AddFileToObject,
copyFileToCacheDirectory: CopyFileToCacheDirectory
): RelationValueViewModel.Factory = RelationValueViewModel.Factory(
relations = relations,
values = values,
@ -139,6 +150,7 @@ object ObjectObjectRelationValueModule {
urlBuilder = urlBuilder,
dispatcher = dispatcher,
updateDetail = updateDetail,
addFileToObject = addFileToObject
addFileToObject = addFileToObject,
copyFileToCache = copyFileToCacheDirectory
)
}

View file

@ -0,0 +1,14 @@
package com.anytypeio.anytype.di.main
import com.anytypeio.anytype.middleware.interactor.LocalNetworkAddressHandler
import dagger.Module
import dagger.Provides
import javax.inject.Singleton
@Module
class LocalNetworkAddressModule {
@Singleton
@Provides
fun provideHandler(): LocalNetworkAddressHandler = LocalNetworkAddressHandler()
}

View file

@ -17,7 +17,8 @@ import javax.inject.Singleton
UtilModule::class,
EmojiModule::class,
ClipboardModule::class,
AnalyticsModule::class
AnalyticsModule::class,
LocalNetworkAddressModule::class
]
)
interface MainComponent {

View file

@ -1,6 +1,6 @@
package com.anytypeio.anytype.ui.editor
import android.Manifest
import android.Manifest.permission.READ_EXTERNAL_STORAGE
import android.animation.ObjectAnimator
import android.app.Activity
import android.app.ProgressDialog
@ -22,7 +22,7 @@ import android.widget.Button
import android.widget.ProgressBar
import android.widget.TextView
import androidx.activity.addCallback
import androidx.annotation.StringRes
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.constraintlayout.widget.ConstraintLayout
import androidx.constraintlayout.widget.ConstraintSet
@ -67,6 +67,8 @@ import com.anytypeio.anytype.core_ui.reactive.clicks
import com.anytypeio.anytype.core_ui.tools.*
import com.anytypeio.anytype.core_ui.widgets.text.TextInputWidget
import com.anytypeio.anytype.core_utils.common.EventWrapper
import com.anytypeio.anytype.core_utils.const.FileConstants.REQUEST_FILE_SAF_CODE
import com.anytypeio.anytype.core_utils.const.FileConstants.REQUEST_MEDIA_CODE
import com.anytypeio.anytype.core_utils.ext.*
import com.anytypeio.anytype.core_utils.ext.PopupExtensions.calculateRectInWindow
import com.anytypeio.anytype.di.common.componentManager
@ -84,6 +86,7 @@ import com.anytypeio.anytype.presentation.editor.editor.sam.ScrollAndMoveTarget
import com.anytypeio.anytype.presentation.editor.editor.sam.ScrollAndMoveTargetDescriptor
import com.anytypeio.anytype.presentation.editor.markup.MarkupColorView
import com.anytypeio.anytype.presentation.editor.model.EditorFooter
import com.anytypeio.anytype.presentation.util.CopyFileStatus
import com.anytypeio.anytype.ui.alert.AlertUpdateAppFragment
import com.anytypeio.anytype.ui.base.NavigationFragment
import com.anytypeio.anytype.ui.editor.cover.SelectCoverObjectFragment
@ -115,15 +118,12 @@ import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.isActive
import kotlinx.coroutines.launch
import permissions.dispatcher.*
import timber.log.Timber
import java.util.ArrayList
import javax.inject.Inject
import kotlin.math.abs
const val REQUEST_FILE_CODE = 745
@RuntimePermissions
open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
OnFragmentInteractionListener,
TurnIntoActionReceiver,
SelectProgrammingLanguageReceiver,
@ -349,76 +349,6 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_FILE_CODE -> {
data?.data?.let {
pickiT.getPath(it, Build.VERSION.SDK_INT)
} ?: run {
toast("Error while getting file")
}
}
else -> toast("Unknown Request Code:$requestCode")
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
onRequestPermissionsResult(requestCode, grantResults)
}
@NeedsPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE)
fun startDownload(id: String) {
vm.startDownloadingFile(id)
}
@NeedsPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
fun openGallery(type: String) {
try {
startActivityForResult(getVideoFileIntent(type), REQUEST_FILE_CODE)
} catch (e: Exception) {
Timber.e(e, "Failed to open gallery")
}
}
@OnShowRationale(Manifest.permission.READ_EXTERNAL_STORAGE)
fun showRationaleForReadExternalStoragePermission(request: PermissionRequest) {
showRationaleDialog(R.string.permission_read_rationale, request)
}
@OnPermissionDenied(Manifest.permission.READ_EXTERNAL_STORAGE)
fun onReadExternalStoragePermissionDenied() {
toast(getString(R.string.permission_read_denied))
}
@OnNeverAskAgain(Manifest.permission.READ_EXTERNAL_STORAGE)
fun onReadExternalStoragePermissionNeverAskAgain() {
toast(getString(R.string.permission_read_never_ask_again))
}
@OnShowRationale(Manifest.permission.WRITE_EXTERNAL_STORAGE)
fun showRationaleForWriteExternalStoragePermission(request: PermissionRequest) {
showRationaleDialog(R.string.permission_write_rationale, request)
}
@OnPermissionDenied(Manifest.permission.WRITE_EXTERNAL_STORAGE)
fun onWriteExternalStoragePermissionDenied() {
toast(getString(R.string.permission_write_denied))
}
@OnNeverAskAgain(Manifest.permission.WRITE_EXTERNAL_STORAGE)
fun onWriteExternalStoragePermissionNeverAskAgain() {
toast(getString(R.string.permission_write_never_ask_again))
}
@Inject
lateinit var factory: EditorViewModelFactory
@ -465,6 +395,9 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
}
}
}
jobs += subscribe(vm.copyFileStatus) { command ->
onCopyFileCommand(command)
}
}
vm.onStart(id = extractDocumentId())
super.onStart()
@ -746,8 +679,7 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
if (isVisible) {
behavior.state = BottomSheetBehavior.STATE_EXPANDED
behavior.addBottomSheetCallback(onHideBottomSheetCallback)
}
else {
} else {
behavior.removeBottomSheetCallback(onHideBottomSheetCallback)
behavior.state = BottomSheetBehavior.STATE_HIDDEN
}
@ -779,6 +711,7 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
override fun onDestroy() {
pickiT.deleteTemporaryFile(requireContext())
clearOnCopyFile()
super.onDestroy()
}
@ -849,10 +782,7 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
).show(childFragmentManager, null)
}
is Command.OpenGallery -> {
openGalleryWithPermissionCheck(command.mediaType)
}
is Command.RequestDownloadPermission -> {
startDownloadWithPermissionCheck(command.id)
openFilePicker(command.mimeType)
}
is Command.PopBackStack -> {
childFragmentManager.popBackStack()
@ -1720,15 +1650,6 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
hideSoftInput()
}
private fun showRationaleDialog(@StringRes messageResId: Int, request: PermissionRequest) {
AlertDialog.Builder(requireContext())
.setPositiveButton(R.string.button_allow) { _, _ -> request.proceed() }
.setNegativeButton(R.string.button_deny) { _, _ -> request.cancel() }
.setCancelable(false)
.setMessage(messageResId)
.show()
}
private fun extractDocumentId(): String {
return requireArguments()
.getString(ID_KEY)
@ -1756,7 +1677,7 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
private fun getEditorSettings() {
}
// ----------- PickiT Listeners ------------------------------
//region PICK IT
private var pickitProgressDialog: ProgressDialog? = null
private var pickitProgressBar: ProgressBar? = null
@ -1770,11 +1691,14 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
* are waiting for the file to be returned.
*/
override fun PickiTonUriReturned() {
pickitProgressDialog = ProgressDialog(requireContext()).apply {
setMessage(getString(R.string.pickit_waiting))
setCancelable(false)
Timber.d("PickiTonUriReturned")
if (pickitProgressDialog == null || pickitProgressDialog?.isShowing == false) {
pickitProgressDialog = ProgressDialog(requireContext()).apply {
setMessage(getString(R.string.pickit_waiting))
setCancelable(false)
}
pickitProgressDialog?.show()
}
pickitProgressDialog?.show()
}
/**
@ -1791,6 +1715,7 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
* if the selected file is not local
*/
override fun PickiTonStartListener() {
Timber.d("PickiTonStartListener")
if (pickitProgressDialog?.isShowing == true) {
pickitProgressDialog?.cancel()
}
@ -1829,6 +1754,9 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
if (pickitAlertDialog?.isShowing == true) {
pickitAlertDialog?.cancel()
}
if (pickitProgressDialog?.isShowing == true) {
pickitProgressDialog?.dismiss()
}
if (BuildConfig.DEBUG) {
when {
wasDriveFile -> toast(getString(R.string.pickit_drive))
@ -1842,8 +1770,23 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
}
}
override fun PickiTonMultipleCompleteListener(
paths: ArrayList<String>?,
wasSuccessful: Boolean,
Reason: String?
) {
toast("Not implemented yet")
}
/**
* Called when a file was picked from file picker.
*/
private fun onFilePathReady(filePath: String?) {
vm.onProceedWithFilePath(filePath)
if (filePath != null) {
vm.onProceedWithFilePath(filePath = filePath)
} else {
Timber.e("onFilePathReady, filePath is null")
}
}
private fun clearPickit() {
@ -1852,6 +1795,8 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
pickitProgressDialog?.dismiss()
}
//endregion
override fun onExitToDesktopClicked() {
vm.navigateToDesktop()
}
@ -1899,10 +1844,6 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
vm.onLayoutClicked()
}
override fun onDownloadClicked() {
vm.onDownloadClicked()
}
override fun onTextValueChanged(ctx: Id, text: String, objectId: Id, relationId: Id) {
vm.onRelationTextValueChanged(
ctx = ctx,
@ -2350,6 +2291,100 @@ open class EditorFragment : NavigationFragment(R.layout.fragment_editor),
//endregion
//region READ PERMISSION
private fun takeReadStoragePermission() {
if (requireActivity().shouldShowRequestPermissionRationaleCompat(READ_EXTERNAL_STORAGE)) {
root.showSnackbar(
R.string.permission_read_rationale,
Snackbar.LENGTH_INDEFINITE,
R.string.button_ok
) {
permissionReadStorage.launch(arrayOf(READ_EXTERNAL_STORAGE))
}
} else {
permissionReadStorage.launch(arrayOf(READ_EXTERNAL_STORAGE))
}
}
private val permissionReadStorage =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { grantResults ->
val readResult = grantResults[READ_EXTERNAL_STORAGE]
if (readResult == true) {
startFilePicker(mMimeType)
} else {
root.showSnackbar(R.string.permission_read_denied, Snackbar.LENGTH_SHORT)
}
}
//endregion
//region UPLOAD FILE LOGIC
private var mMimeType = ""
private var mSnackbar: Snackbar? = null
private fun openFilePicker(mimeType: String) {
mMimeType = mimeType
if (requireContext().isPermissionGranted(mimeType)) {
startFilePicker(mimeType)
} else {
takeReadStoragePermission()
}
}
private fun onCopyFileCommand(command: CopyFileStatus) {
when (command) {
is CopyFileStatus.Error -> {
mSnackbar?.dismiss()
activity?.toast("Error while loading file:${command.msg}")
}
is CopyFileStatus.Completed -> {
mSnackbar?.dismiss()
onFilePathReady(command.result)
}
CopyFileStatus.Started -> {
mSnackbar = root.showSnackbar(
R.string.loading_file,
Snackbar.LENGTH_INDEFINITE,
R.string.cancel
) {
vm.onCancelCopyFileToCacheDir()
}
}
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_MEDIA_CODE -> {
data?.data?.let { uri ->
pickiT.getPath(uri, Build.VERSION.SDK_INT)
}
}
REQUEST_FILE_SAF_CODE -> {
data?.data?.let { uri ->
vm.onStartCopyFileToCacheDir(uri)
} ?: run {
Timber.e("onActivityResult error, data is null")
toast("Error while getting file")
}
}
else -> {
Timber.e("onActivityResult error, Unknown Request Code:$requestCode")
toast("Unknown Request Code:$requestCode")
}
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
private fun clearOnCopyFile() {
vm.onCancelCopyFileToCacheDir()
mSnackbar?.dismiss()
mSnackbar = null
}
//endregion
//------------ End of Anytype Custom Context Menu ------------
companion object {

View file

@ -198,7 +198,6 @@ abstract class ObjectMenuBaseFragment : BaseBottomSheetFragment() {
fun onAddCoverClicked()
fun onSetIconClicked()
fun onLayoutClicked()
fun onDownloadClicked()
fun onUndoRedoClicked()
}
}

View file

@ -1,6 +1,6 @@
package com.anytypeio.anytype.ui.relations
import android.Manifest
import android.Manifest.permission.READ_EXTERNAL_STORAGE
import android.app.Activity
import android.app.ProgressDialog
import android.content.Intent
@ -11,7 +11,7 @@ import android.view.View
import android.view.ViewGroup
import android.widget.Button
import android.widget.ProgressBar
import androidx.annotation.StringRes
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AlertDialog
import androidx.core.os.bundleOf
import androidx.fragment.app.viewModels
@ -27,6 +27,8 @@ import com.anytypeio.anytype.core_models.Id
import com.anytypeio.anytype.core_ui.features.sets.RelationValueAdapter
import com.anytypeio.anytype.core_ui.reactive.clicks
import com.anytypeio.anytype.core_ui.tools.DefaultDragAndDropBehavior
import com.anytypeio.anytype.core_utils.const.FileConstants.REQUEST_FILE_SAF_CODE
import com.anytypeio.anytype.core_utils.const.FileConstants.REQUEST_MEDIA_CODE
import com.anytypeio.anytype.core_utils.ext.*
import com.anytypeio.anytype.core_utils.ui.BaseBottomSheetFragment
import com.anytypeio.anytype.core_utils.ui.DragAndDropViewHolder
@ -36,17 +38,19 @@ import com.anytypeio.anytype.presentation.navigation.AppNavigation
import com.anytypeio.anytype.presentation.sets.RelationValueBaseViewModel
import com.anytypeio.anytype.presentation.sets.RelationValueDVViewModel
import com.anytypeio.anytype.presentation.sets.RelationValueViewModel
import com.anytypeio.anytype.presentation.util.CopyFileStatus
import com.anytypeio.anytype.ui.editor.EditorFragment
import com.anytypeio.anytype.ui.editor.REQUEST_FILE_CODE
import com.anytypeio.anytype.ui.sets.ObjectSetFragment
import com.google.android.material.snackbar.Snackbar
import com.hbisoft.pickit.PickiT
import com.hbisoft.pickit.PickiTCallbacks
import kotlinx.android.synthetic.main.fragment_relation_value.*
import permissions.dispatcher.*
import kotlinx.android.synthetic.main.fragment_relation_value.recycler
import kotlinx.android.synthetic.main.fragment_relation_value.root
import timber.log.Timber
import java.util.ArrayList
import javax.inject.Inject
@RuntimePermissions
abstract class RelationValueBaseFragment : BaseBottomSheetFragment(),
OnStartDragListener,
RelationObjectValueAddFragment.ObjectValueAddReceiver,
@ -145,6 +149,7 @@ abstract class RelationValueBaseFragment : BaseBottomSheetFragment(),
jobs += lifecycleScope.subscribe(vm.name) { tvTagOrStatusRelationHeader.text = it }
jobs += lifecycleScope.subscribe(vm.navigation) { command -> navigate(command) }
jobs += lifecycleScope.subscribe(vm.isLoading) { isLoading -> observeLoading(isLoading) }
jobs += lifecycleScope.subscribe(vm.copyFileStatus) { command -> onCopyFileCommand(command) }
super.onStart()
vm.onStart(relationId = relation, objectId = target)
}
@ -212,48 +217,10 @@ abstract class RelationValueBaseFragment : BaseBottomSheetFragment(),
}
}
//region READ STORAGE
//region PICK IT
abstract fun onFilePathReady(filePath: String?)
@NeedsPermission(Manifest.permission.READ_EXTERNAL_STORAGE)
fun openGallery(type: String) {
startActivityForResult(getVideoFileIntent(type), REQUEST_FILE_CODE)
}
@OnShowRationale(Manifest.permission.READ_EXTERNAL_STORAGE)
fun showRationaleForReadExternalStoragePermission(request: PermissionRequest) {
showRationaleDialog(R.string.permission_read_rationale, request)
}
@OnPermissionDenied(Manifest.permission.READ_EXTERNAL_STORAGE)
fun onReadExternalStoragePermissionDenied() {
toast(getString(R.string.permission_read_denied))
}
@OnNeverAskAgain(Manifest.permission.READ_EXTERNAL_STORAGE)
fun onReadExternalStoragePermissionNeverAskAgain() {
toast(getString(R.string.permission_read_never_ask_again))
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
onRequestPermissionsResult(requestCode, grantResults)
}
private fun showRationaleDialog(@StringRes messageResId: Int, request: PermissionRequest) {
AlertDialog.Builder(requireContext())
.setPositiveButton(R.string.button_allow) { _, _ -> request.proceed() }
.setNegativeButton(R.string.button_deny) { _, _ -> request.cancel() }
.setCancelable(false)
.setMessage(messageResId)
.show()
}
private lateinit var pickiT: PickiT
override fun onCreate(savedInstanceState: Bundle?) {
@ -261,33 +228,18 @@ abstract class RelationValueBaseFragment : BaseBottomSheetFragment(),
pickiT = PickiT(requireContext(), this, requireActivity())
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_FILE_CODE -> {
data?.data?.let {
pickiT.getPath(it, Build.VERSION.SDK_INT)
} ?: run {
toast("Error while getting file")
}
}
else -> toast("Unknown Request Code:$requestCode")
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
private var pickitProgressDialog: ProgressDialog? = null
private var pickitProgressBar: ProgressBar? = null
private var pickitAlertDialog: AlertDialog? = null
override fun PickiTonUriReturned() {
pickitProgressDialog = ProgressDialog(requireContext()).apply {
setMessage(getString(R.string.pickit_waiting))
setCancelable(false)
if (pickitProgressDialog == null || pickitProgressDialog?.isShowing == false) {
pickitProgressDialog = ProgressDialog(requireContext()).apply {
setMessage(getString(R.string.pickit_waiting))
setCancelable(false)
}
pickitProgressDialog?.show()
}
pickitProgressDialog?.show()
}
override fun PickiTonStartListener() {
@ -327,6 +279,9 @@ abstract class RelationValueBaseFragment : BaseBottomSheetFragment(),
if (pickitAlertDialog?.isShowing == true) {
pickitAlertDialog?.cancel()
}
if (pickitProgressDialog?.isShowing == true) {
pickitProgressDialog?.cancel()
}
if (BuildConfig.DEBUG) {
when {
wasDriveFile -> toast(getString(R.string.pickit_drive))
@ -341,14 +296,107 @@ abstract class RelationValueBaseFragment : BaseBottomSheetFragment(),
}
private fun clearPickit() {
val ctx = context
if (ctx != null) {
pickiT.deleteTemporaryFile(ctx)
}
pickiT.cancelTask()
pickitAlertDialog?.dismiss()
pickitProgressDialog?.dismiss()
}
//endregion
//region READ PERMISSION
private fun takeReadStoragePermission() {
if (requireActivity().shouldShowRequestPermissionRationaleCompat(READ_EXTERNAL_STORAGE)) {
root.showSnackbar(
R.string.permission_read_rationale,
Snackbar.LENGTH_INDEFINITE,
R.string.button_ok
) {
permissionReadStorage.launch(arrayOf(READ_EXTERNAL_STORAGE))
}
} else {
permissionReadStorage.launch(arrayOf(READ_EXTERNAL_STORAGE))
}
}
private val permissionReadStorage =
registerForActivityResult(ActivityResultContracts.RequestMultiplePermissions()) { grantResults ->
val readResult = grantResults[READ_EXTERNAL_STORAGE]
if (readResult == true) {
startFilePicker(MIME_FILE_ALL)
} else {
root.showSnackbar(R.string.permission_read_denied, Snackbar.LENGTH_SHORT)
}
}
//endregion
//region UPLOAD FILE LOGIC
private var mSnackbar: Snackbar? = null
protected fun openFilePicker() {
if (requireContext().isPermissionGranted(MIME_FILE_ALL)) {
startFilePicker(MIME_FILE_ALL)
} else {
takeReadStoragePermission()
}
}
override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) {
if (resultCode == Activity.RESULT_OK) {
when (requestCode) {
REQUEST_MEDIA_CODE -> {
data?.data?.let { uri ->
pickiT.getPath(uri, Build.VERSION.SDK_INT)
}
}
REQUEST_FILE_SAF_CODE -> {
data?.data?.let { uri ->
vm.onStartCopyFileToCacheDir(uri)
} ?: run {
toast("Error while getting file")
}
}
else -> toast("Unknown Request Code:$requestCode")
}
} else {
super.onActivityResult(requestCode, resultCode, data)
}
}
private fun onCopyFileCommand(command: CopyFileStatus) {
when (command) {
is CopyFileStatus.Error -> {
mSnackbar?.dismiss()
activity?.toast("Error while loading file:${command.msg}")
}
is CopyFileStatus.Completed -> {
mSnackbar?.dismiss()
onFilePathReady(command.result)
}
CopyFileStatus.Started -> {
mSnackbar = root.showSnackbar(
R.string.loading_file,
Snackbar.LENGTH_INDEFINITE,
R.string.cancel
) {
vm.onCancelCopyFileToCacheDir()
}
}
}
}
private fun clearOnCopyFile() {
vm.onCancelCopyFileToCacheDir()
mSnackbar?.dismiss()
mSnackbar = null
}
//endregion
override fun onDestroyView() {
clearPickit()
clearOnCopyFile()
super.onDestroyView()
}
@ -487,7 +535,7 @@ open class RelationValueDVFragment : RelationValueBaseFragment() {
RelationValueBaseViewModel.ObjectRelationValueCommand.ShowFileValueActionScreen -> {
//turn off for now https://app.clickup.com/t/h59z1j
//FileActionsFragment().show(childFragmentManager, null)
openGalleryWithPermissionCheck(type = MIME_FILE_ALL)
openFilePicker()
}
}
}
@ -501,6 +549,9 @@ open class RelationValueDVFragment : RelationValueBaseFragment() {
vm.onFileValueActionUploadFromGalleryClicked()
}
/**
* Called when a file was picked from file picker.
*/
override fun onFilePathReady(filePath: String?) {
if (filePath != null) {
vm.onAddFileToRecord(
@ -520,6 +571,14 @@ open class RelationValueDVFragment : RelationValueBaseFragment() {
vm.onFileValueActionUploadFromStorageClicked()
}
override fun PickiTonMultipleCompleteListener(
paths: ArrayList<String>?,
wasSuccessful: Boolean,
Reason: String?
) {
toast("Not implemented yet")
}
override fun injectDependencies() {
componentManager().objectSetObjectRelationValueComponent.get(ctx).inject(this)
}
@ -620,6 +679,14 @@ class RelationValueFragment : RelationValueBaseFragment() {
)
}
override fun PickiTonMultipleCompleteListener(
paths: ArrayList<String>?,
wasSuccessful: Boolean,
Reason: String?
) {
toast("Not implemented yet")
}
override fun observeCommands(command: RelationValueBaseViewModel.ObjectRelationValueCommand) {
when (command) {
RelationValueBaseViewModel.ObjectRelationValueCommand.ShowAddObjectScreen -> {
@ -651,7 +718,7 @@ class RelationValueFragment : RelationValueBaseFragment() {
RelationValueBaseViewModel.ObjectRelationValueCommand.ShowFileValueActionScreen -> {
//turn off for now https://app.clickup.com/t/h59z1j
//FileActionsFragment().show(childFragmentManager, null)
openGalleryWithPermissionCheck(type = MIME_FILE_ALL)
openFilePicker()
}
}
}
@ -670,6 +737,9 @@ class RelationValueFragment : RelationValueBaseFragment() {
vm.onFileValueActionUploadFromStorageClicked()
}
/**
* Called when a file was picked from file picker.
*/
override fun onFilePathReady(filePath: String?) {
if (filePath != null) {
vm.onAddFileToObject(

View file

@ -4,6 +4,7 @@
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:id="@+id/root"
android:orientation="vertical"
tools:context=".ui.relations.RelationValueBaseFragment">

View file

@ -106,13 +106,14 @@ Do the computation of an expensive paragraph of text on a background thread:
<string name="button_link">Link</string>
<string name="permission_read_rationale">Read permission is needed to load file to media block.</string>
<string name="permission_read_denied">Read permission was denied. Please consider granting it in order to load files to media blocks!</string>
<string name="permission_read_never_ask_again">Read permission was denied with never ask again.</string>
<string name="permission_read_denied">User denied permission. Please, approve this permission in \"Permissions\" in the app settings on your device.</string>
<string name="permission_read_never_ask_again">Read permission was denied with never ask again.\n You must approve this permission in \"Permissions\" in the app settings on your device.</string>
<string name="permission_write_rationale">Write permission is needed to load file to device.</string>
<string name="permission_write_denied">Write permission was denied. Please consider granting it in order to load files to device!</string>
<string name="permission_write_never_ask_again">Write permission was denied with never ask again.</string>
<string name="button_allow">Allow</string>
<string name="button_deny">Deny</string>
<string name="button_ok">Ok</string>
<string name="error">Error</string>
<string name="page_icon">Page icon</string>

View file

@ -3,12 +3,12 @@ apply from: './dependencies.gradle'
buildscript {
ext.kotlin_version = '1.5.21'
ext.gradle_tools = '3.1.3'
ext.build_tools = '29.0.3'
ext.build_tools = '31.0.0'
ext.nav_version = '2.3.0'
ext.dokka_version = '1.4.32'
ext.compile_sdk = 29
ext.target_sdk = 29
ext.compile_sdk = 31
ext.target_sdk = 31
ext.min_sdk = 24
ext.application_id = 'com.anytypeio.anytype'
@ -21,7 +21,7 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:4.2.2'
classpath 'com.android.tools.build:gradle:7.0.4'
classpath "org.jetbrains.kotlin:kotlin-gradle-plugin:$kotlin_version"
classpath "androidx.navigation:navigation-safe-args-gradle-plugin:$nav_version"
classpath 'com.google.gms:google-services:4.3.8'

View file

@ -36,12 +36,12 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = JavaVersion.VERSION_11
}
}

View file

@ -63,12 +63,12 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = JavaVersion.VERSION_11
}
}

View file

@ -175,6 +175,7 @@
<string name="loading">Loading…</string>
<string name="loading_file">Loading file, please wait</string>
<string name="error_while_loading">Error while loading</string>
<string name="error_while_loading_picture">Error while loading picture</string>
<string name="block_with_a_picture">Block with a picture</string>

View file

@ -0,0 +1,6 @@
package com.anytypeio.anytype.core_utils.const
object FileConstants {
const val REQUEST_FILE_SAF_CODE = 2211
const val REQUEST_MEDIA_CODE = 2212
}

View file

@ -1,14 +1,17 @@
package com.anytypeio.anytype.core_utils.ext
import android.Manifest
import android.app.Activity
import android.content.ClipboardManager
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.content.res.Resources
import android.graphics.Point
import android.graphics.Rect
import android.graphics.drawable.Drawable
import android.net.Uri
import android.os.Build
import android.os.Environment
import android.provider.MediaStore
import android.text.Editable
@ -22,11 +25,15 @@ import android.widget.EditText
import android.widget.TextView
import androidx.annotation.DimenRes
import androidx.annotation.StringRes
import androidx.core.app.ActivityCompat
import androidx.core.content.ContextCompat
import androidx.core.graphics.BlendModeColorFilterCompat
import androidx.core.graphics.BlendModeCompat
import androidx.core.view.updateLayoutParams
import androidx.fragment.app.Fragment
import androidx.recyclerview.widget.RecyclerView
import com.anytypeio.anytype.core_utils.const.FileConstants.REQUEST_FILE_SAF_CODE
import com.anytypeio.anytype.core_utils.const.FileConstants.REQUEST_MEDIA_CODE
import timber.log.Timber
import java.text.SimpleDateFormat
import java.util.*
@ -135,21 +142,6 @@ inline fun <reified T> Editable.removeSpans() {
getSpans(0, length, T::class.java).forEach { removeSpan(it) }
}
fun getVideoFileIntent(mediaType: String): Intent {
val intent =
if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
} else {
Intent(Intent.ACTION_PICK, MediaStore.Video.Media.INTERNAL_CONTENT_URI)
}
return intent.apply {
type = mediaType
action = Intent.ACTION_GET_CONTENT
putExtra("return-data", true)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
}
fun String.getFileName(mime: String?): String =
if (mime != null) {
"$this.${mime.substringAfter("/")}"
@ -253,4 +245,48 @@ fun View.focusAndShowKeyboard() {
}
fun String.normalizeUrl(): String =
if (!startsWith("http://") && !startsWith("https://")) "https://$this" else this
if (!startsWith("http://") && !startsWith("https://")) "https://$this" else this
fun Context.isPermissionGranted(mimeType: String): Boolean {
return if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R && mimeType == MIME_FILE_ALL) {
true
} else {
val readExternalStorage: Int = ContextCompat.checkSelfPermission(
this,
Manifest.permission.READ_EXTERNAL_STORAGE
)
readExternalStorage == PackageManager.PERMISSION_GRANTED
}
}
fun Activity.shouldShowRequestPermissionRationaleCompat(permission: String) =
ActivityCompat.shouldShowRequestPermissionRationale(this, permission)
fun Fragment.startFilePicker(mime: String) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.R) {
val intent = Intent(Intent.ACTION_GET_CONTENT).apply {
putExtra(Intent.EXTRA_ALLOW_MULTIPLE, false)
type = mime
}
val code = if (mime == MIME_FILE_ALL) {
REQUEST_FILE_SAF_CODE
} else {
REQUEST_MEDIA_CODE
}
startActivityForResult(intent, code)
} else {
val intent =
if (Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED) {
Intent(Intent.ACTION_PICK, MediaStore.Video.Media.EXTERNAL_CONTENT_URI)
} else {
Intent(Intent.ACTION_PICK, MediaStore.Video.Media.INTERNAL_CONTENT_URI)
}
intent.apply {
type = mime
action = Intent.ACTION_GET_CONTENT
putExtra("return-data", true)
addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
}
startActivityForResult(intent, REQUEST_MEDIA_CODE)
}
}

View file

@ -30,6 +30,33 @@ fun View.showSnackbar(text: String) {
Snackbar.make(this, text, Snackbar.LENGTH_LONG).show()
}
fun View.showSnackbar(msgId: Int, length: Int) = showSnackbar(context.getString(msgId), length)
fun View.showSnackbar(msg: String, length: Int) = showSnackbar(msg, length, null, {})
fun View.showSnackbar(
msgId: Int,
length: Int,
actionMessageId: Int,
action: (View) -> Unit
): Snackbar =
showSnackbar(context.getString(msgId), length, context.getString(actionMessageId), action)
fun View.showSnackbar(
msg: String,
length: Int,
actionMessage: CharSequence?,
action: (View) -> Unit
): Snackbar {
val snackbar = Snackbar.make(this, msg, length)
if (actionMessage != null) {
snackbar.setAction(actionMessage) {
action(this)
}
}
snackbar.show()
return snackbar
}
fun View.hideKeyboard() {
val inputMethodManager =
context.getSystemService(Context.INPUT_METHOD_SERVICE) as InputMethodManager
@ -50,7 +77,7 @@ fun Activity.hideSoftInput() {
fun Fragment.hideSoftInput() = requireActivity().hideSoftInput()
fun RecyclerView.containsItemDecoration(decoration: RecyclerView.ItemDecoration) : Boolean {
fun RecyclerView.containsItemDecoration(decoration: RecyclerView.ItemDecoration): Boolean {
if (itemDecorationCount > 0) {
for (i in 0..itemDecorationCount.dec()) {
val d = getItemDecorationAt(i)
@ -63,7 +90,7 @@ fun RecyclerView.containsItemDecoration(decoration: RecyclerView.ItemDecoration)
}
}
val Activity.statusBarHeight : Int
val Activity.statusBarHeight: Int
get() {
val rectangle = Rect()
window.decorView.getWindowVisibleDisplayFrame(rectangle)

View file

@ -4,24 +4,24 @@ ext {
kotlinx_serialization_json_version = '1.2.1'
// AndroidX
androidx_core_version = '1.6.0'
androidx_core_ktx_version = '1.6.0'
androidx_core_version = '1.7.0'
androidx_core_ktx_version = '1.7.0'
androidx_test_core_version = '1.4.0'
androidx_core_testing_version = '2.1.0'
androidx_security_crypto_version = '1.0.0'
// Other Android framework dependencies
appcompat_version = '1.3.0'
constraintLayout_version = '2.0.4'
constraintLayout_version = '2.1.2'
recyclerview_version = '1.2.1'
cardview_version = '1.0.0'
material_version = '1.3.0'
fragment_version = "1.3.6"
fragment_version = "1.4.0"
emoji_compat_version = '1.1.0'
view_pager_2_version = '1.0.0'
// Architecture Components
lifecycle_version = '2.3.1'
lifecycle_version = '2.4.0'
navigation_version = '2.3.5'
// Third party libraries
@ -37,8 +37,7 @@ ext {
gson_version = '2.8.6'
better_link_method_version = '2.2.0'
table_view_version = '0.8.9.4'
permission_disp_version = '4.8.0'
pickt_version = "0.1.14"
pickt_version = "2.0.2"
zxing_version = "4.1.0"
urlcleaner_version = "0.4.0"
katex_version = "1.0.2"
@ -124,8 +123,6 @@ ext {
timber: "com.jakewharton.timber:timber:$timber_version",
tableView: "com.evrencoskun.library:tableview:$table_view_version",
exoPlayer: "com.google.android.exoplayer:exoplayer:$exoplayer_version",
permissionDisp: "com.github.permissions-dispatcher:permissionsdispatcher:$permission_disp_version",
permissionDispCompiler: "com.github.permissions-dispatcher:permissionsdispatcher-processor:$permission_disp_version",
pickT: "com.github.HBiSoft:PickiT:$pickt_version",
zxing: "com.journeyapps:zxing-android-embedded:$zxing_version",
urlcleaner: "com.shekhargulati.urlcleaner:urlcleaner:$urlcleaner_version",

View file

@ -30,12 +30,12 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = JavaVersion.VERSION_11
}
}

View file

@ -3,4 +3,4 @@ distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
distributionUrl=https\://services.gradle.org/distributions/gradle-6.7.1-all.zip
distributionUrl=https\://services.gradle.org/distributions/gradle-7.0.2-all.zip

View file

@ -30,12 +30,12 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = JavaVersion.VERSION_11
}
}

View file

@ -59,12 +59,12 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = JavaVersion.VERSION_11
}
}

View file

@ -31,12 +31,12 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = JavaVersion.VERSION_11
}
}

View file

@ -20,7 +20,6 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {

View file

@ -0,0 +1,81 @@
package com.anytypeio.anytype.middleware.interactor
import kotlinx.coroutines.CoroutineScope
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.GlobalScope
import kotlinx.coroutines.launch
import service.InterfaceAddr
import service.InterfaceAddrIterator
import service.InterfaceAddrsGetter
import service.Service.setInterfaceAddrsGetter
import timber.log.Timber
import java.net.InterfaceAddress
import java.net.NetworkInterface
/**
* This class is used for sending local Ip addresses to middleware.
* See https://discuss.ipfs.io/t/basichosts-updatelocalipaddr-fails-on-android-11-net-interfaceaddrs-returns-error/13003
*/
class LocalNetworkAddressHandler(
private val scope: CoroutineScope = GlobalScope
) {
fun start() {
scope.launch(Dispatchers.IO) {
setInterfaceAddrsGetter(DefaultAddressProvider())
}
}
class DefaultAddressProvider : InterfaceAddrsGetter {
private var mLastUpdateTime: Long = 0
private var addresses = mutableListOf<InterfaceAddress>()
override fun interfaceAddrs(): InterfaceAddrIterator {
Timber.d("Getting addresses")
if (!isNeedToUpdateNetworkAddresses()) {
return DefaultAddressIterator(addresses.iterator())
}
return try {
addresses.clear()
val interfaces = NetworkInterface.getNetworkInterfaces().toList()
addresses.addAll(interfaces.flatMap { it.interfaceAddresses })
mLastUpdateTime = System.currentTimeMillis()
DefaultAddressIterator(addresses.iterator())
} catch (e: Exception) {
Timber.e(e, "Error getting local net address")
mLastUpdateTime = System.currentTimeMillis()
DefaultAddressIterator(addresses.iterator())
}
}
private fun isNeedToUpdateNetworkAddresses(): Boolean {
return System.currentTimeMillis() - mLastUpdateTime >= UPDATE_INTERVAL_MILLI_SECONDS
}
}
class DefaultAddressIterator(private val ia: Iterator<InterfaceAddress>) :
InterfaceAddrIterator {
override fun next(): LocalInterfaceAddr? {
if (ia.hasNext()) {
return LocalInterfaceAddr(ia.next())
}
return null
}
}
class LocalInterfaceAddr(private val interfaceAddress: InterfaceAddress?) : InterfaceAddr {
override fun ip(): ByteArray? {
return interfaceAddress?.address?.address
}
override fun prefix(): Long {
return interfaceAddress?.networkPrefixLength?.toLong() ?: 0L
}
}
companion object {
const val UPDATE_INTERVAL_MILLI_SECONDS = 180000
}
}

View file

@ -19,7 +19,6 @@ android {
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro'
}
}
}
dependencies {

View file

@ -1,85 +1,85 @@
package com.anytypeio.anytype
import androidx.arch.core.executor.testing.InstantTaskExecutorRule
import androidx.room.Room
import androidx.test.platform.app.InstrumentationRegistry
import com.anytypeio.anytype.persistence.db.AnytypeDatabase
import com.anytypeio.anytype.persistence.model.AccountTable
import kotlinx.coroutines.delay
import kotlinx.coroutines.runBlocking
import org.junit.After
import org.junit.Rule
import org.junit.Test
import org.junit.runner.RunWith
import org.robolectric.RobolectricTestRunner
import org.robolectric.annotation.Config
import kotlin.test.assertEquals
import kotlin.test.assertTrue
//import androidx.arch.core.executor.testing.InstantTaskExecutorRule
//import androidx.room.Room
//import androidx.test.platform.app.InstrumentationRegistry
//import com.anytypeio.anytype.persistence.db.AnytypeDatabase
//import com.anytypeio.anytype.persistence.model.AccountTable
//import kotlinx.coroutines.delay
//import kotlinx.coroutines.runBlocking
//import org.junit.After
//import org.junit.Rule
//import org.junit.Test
//import org.junit.runner.RunWith
//import org.robolectric.RobolectricTestRunner
//import org.robolectric.annotation.Config
//import kotlin.test.assertEquals
//import kotlin.test.assertTrue
@RunWith(RobolectricTestRunner::class)
@Config(manifest = Config.NONE)
class AccountDaoTest {
@get:Rule
val instantTaskExecutorRule = InstantTaskExecutorRule()
private val database = Room.inMemoryDatabaseBuilder(
InstrumentationRegistry.getInstrumentation().context,
AnytypeDatabase::class.java
).allowMainThreadQueries().build()
@After
fun after() {
database.close()
}
@Test
fun `should return empty list if there are no last account in db`() {
runBlocking {
val accounts = database.accountDao().lastAccount()
assertTrue { accounts.isEmpty() }
}
}
@Test
fun `should return last account`() = runBlocking {
val firstAccount = AccountTable(
id = MockDataFactory.randomString(),
name = MockDataFactory.randomString(),
timestamp = System.currentTimeMillis()
)
delay(1)
val secondAccount = AccountTable(
id = MockDataFactory.randomString(),
name = MockDataFactory.randomString(),
timestamp = System.currentTimeMillis()
)
database.accountDao().insert(firstAccount)
database.accountDao().insert(secondAccount)
val result = database.accountDao().lastAccount()
assertTrue { result.size == 1 }
assertTrue { result.first() == secondAccount }
}
@Test
fun `should return expected account when queried using account id`() = runBlocking {
val account = AccountTable(
id = MockDataFactory.randomString(),
name = MockDataFactory.randomString(),
timestamp = System.currentTimeMillis()
)
database.accountDao().insert(account)
val result = database.accountDao().getAccount(account.id)
assertEquals(account, result)
}
}
//@RunWith(RobolectricTestRunner::class)
//@Config(manifest = Config.NONE)
//class AccountDaoTest {
//
// @get:Rule
// val instantTaskExecutorRule = InstantTaskExecutorRule()
//
// private val database = Room.inMemoryDatabaseBuilder(
// InstrumentationRegistry.getInstrumentation().context,
// AnytypeDatabase::class.java
// ).allowMainThreadQueries().build()
//
// @After
// fun after() {
// database.close()
// }
//
// @Test
// fun `should return empty list if there are no last account in db`() {
// runBlocking {
// val accounts = database.accountDao().lastAccount()
// assertTrue { accounts.isEmpty() }
// }
// }
//
// @Test
// fun `should return last account`() = runBlocking {
//
// val firstAccount = AccountTable(
// id = MockDataFactory.randomString(),
// name = MockDataFactory.randomString(),
// timestamp = System.currentTimeMillis()
// )
//
// delay(1)
//
// val secondAccount = AccountTable(
// id = MockDataFactory.randomString(),
// name = MockDataFactory.randomString(),
// timestamp = System.currentTimeMillis()
// )
//
// database.accountDao().insert(firstAccount)
// database.accountDao().insert(secondAccount)
//
// val result = database.accountDao().lastAccount()
//
// assertTrue { result.size == 1 }
// assertTrue { result.first() == secondAccount }
// }
//
// @Test
// fun `should return expected account when queried using account id`() = runBlocking {
//
// val account = AccountTable(
// id = MockDataFactory.randomString(),
// name = MockDataFactory.randomString(),
// timestamp = System.currentTimeMillis()
// )
//
// database.accountDao().insert(account)
//
// val result = database.accountDao().getAccount(account.id)
//
// assertEquals(account, result)
// }
//}

View file

@ -9,7 +9,7 @@ class CreateAccountViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CreateAccountViewModel(
session = session
) as T

View file

@ -13,7 +13,7 @@ class SelectAccountViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return SelectAccountViewModel(
startLoadingAccounts = startLoadingAccounts,
observeAccounts = observeAccounts,

View file

@ -15,7 +15,7 @@ class SetupNewAccountViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return SetupNewAccountViewModel(
createAccount = createAccount,
session = session,

View file

@ -15,7 +15,7 @@ class SetupSelectedAccountViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return SetupSelectedAccountViewModel(
startAccount = startAccount,
pathProvider = pathProvider,

View file

@ -17,7 +17,7 @@ class KeychainLoginViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return KeychainLoginViewModel(
recoverWallet = recoverWallet,
convertWallet = convertWallet,

View file

@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModelProvider
class ChoosePinCodeViewModelFactory : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ChoosePinCodeViewModel() as T
}
}

View file

@ -5,7 +5,7 @@ import androidx.lifecycle.ViewModelProvider
class ConfirmPinCodeViewModelFactory : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ConfirmPinCodeViewModel() as T
}
}

View file

@ -6,7 +6,7 @@ import androidx.lifecycle.ViewModelProvider
class EnterPinCodeViewModelFactory : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return EnterPinCodeViewModel() as T
}
}

View file

@ -13,7 +13,7 @@ class StartLoginViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return StartLoginViewModel(
setupWallet = setupWallet,
pathProvider = pathProvider,

View file

@ -37,7 +37,7 @@ class HomeDashboardViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return HomeDashboardViewModel(
getProfile = getProfile,
openDashboard = openDashboard,

View file

@ -1,5 +1,6 @@
package com.anytypeio.anytype.presentation.editor
import android.net.Uri
import androidx.lifecycle.LiveData
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.viewModelScope
@ -105,7 +106,10 @@ import com.anytypeio.anytype.presentation.relations.DocumentRelationView
import com.anytypeio.anytype.presentation.relations.views
import com.anytypeio.anytype.presentation.search.ObjectSearchConstants
import com.anytypeio.anytype.presentation.search.ObjectSearchViewModel
import com.anytypeio.anytype.presentation.util.CopyFileStatus
import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.presentation.util.OnCopyFileToCacheAction
import kotlinx.coroutines.Job
import kotlinx.coroutines.delay
import kotlinx.coroutines.flow.*
@ -140,7 +144,8 @@ class EditorViewModel(
private val searchObjects: SearchObjects,
private val getDefaultEditorType: GetDefaultEditorType,
private val findObjectSetForType: FindObjectSetForType,
private val createObjectSet: CreateObjectSet
private val createObjectSet: CreateObjectSet,
private val copyFileToCache: CopyFileToCacheDirectory
) : ViewStateViewModel<ViewState>(),
SupportNavigation<EventWrapper<AppNavigation.Command>>,
SupportCommand<Command>,
@ -1655,14 +1660,7 @@ class EditorViewModel(
ActionItemType.Style -> {
viewModelScope.launch { proceedWithOpeningStyleToolbarFromActionMenu(id) }
}
ActionItemType.Download -> {
viewModelScope.launch {
onExitActionMode()
dispatch(Command.PopBackStack)
delay(300)
dispatch(Command.RequestDownloadPermission(id))
}
}
ActionItemType.Download -> { }
ActionItemType.SAM -> {
mode = EditorMode.SAM
viewModelScope.launch { orchestrator.stores.focus.update(Editor.Focus.empty()) }
@ -1919,12 +1917,12 @@ class EditorViewModel(
private fun onAddLocalVideoClicked(blockId: String) {
mediaBlockId = blockId
dispatch(Command.OpenGallery(mediaType = MIME_VIDEO_ALL))
dispatch(Command.OpenGallery(mimeType = MIME_VIDEO_ALL))
}
private fun onAddLocalPictureClicked(blockId: String) {
mediaBlockId = blockId
dispatch(Command.OpenGallery(mediaType = MIME_IMAGE_ALL))
dispatch(Command.OpenGallery(mimeType = MIME_IMAGE_ALL))
}
fun onTogglePlaceholderClicked(target: Id) {
@ -1951,7 +1949,7 @@ class EditorViewModel(
private fun onAddLocalFileClicked(blockId: String) {
mediaBlockId = blockId
dispatch(Command.OpenGallery(mediaType = MIME_FILE_ALL))
dispatch(Command.OpenGallery(mimeType = MIME_FILE_ALL))
}
fun onAddFileBlockClicked(type: Content.File.Type) {
@ -2802,16 +2800,6 @@ class EditorViewModel(
)
}
fun onDownloadClicked() {
Timber.d("onDownloadClicked, ")
val block = blocks.firstOrNull { it.content is Content.File }
if (block != null) {
dispatch(Command.RequestDownloadPermission(block.id))
} else {
Timber.e("onDownloadClicked, file not found in object")
}
}
fun onLayoutDialogDismissed() {
Timber.d("onLayoutDialogDismissed, ")
proceedWithOpeningObjectMenu()
@ -5389,4 +5377,40 @@ class EditorViewModel(
}
}
//endregion
//region COPY FILE TO CACHE
val copyFileStatus = MutableSharedFlow<CopyFileStatus>(replay = 0)
fun onStartCopyFileToCacheDir(uri: Uri) {
copyFileToCache.execute(
uri = uri,
scope = viewModelScope,
listener = copyFileListener
)
}
fun onCancelCopyFileToCacheDir() {
copyFileToCache.cancel()
}
private val copyFileListener = object : OnCopyFileToCacheAction {
override fun onCopyFileStart() {
viewModelScope.launch {
copyFileStatus.emit(CopyFileStatus.Started)
}
}
override fun onCopyFileResult(result: String?) {
viewModelScope.launch {
copyFileStatus.emit(CopyFileStatus.Completed(result))
}
}
override fun onCopyFileError(msg: String) {
viewModelScope.launch {
copyFileStatus.emit(CopyFileStatus.Error(msg))
}
}
}
//endregion
}

View file

@ -24,6 +24,7 @@ import com.anytypeio.anytype.presentation.common.StateReducer
import com.anytypeio.anytype.presentation.editor.editor.DetailModificationManager
import com.anytypeio.anytype.presentation.editor.editor.Orchestrator
import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer
import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
import com.anytypeio.anytype.presentation.util.Dispatcher
open class EditorViewModelFactory(
@ -51,11 +52,12 @@ open class EditorViewModelFactory(
private val objectTypesProvider: ObjectTypesProvider,
private val searchObjects: SearchObjects,
private val getDefaultEditorType: GetDefaultEditorType,
private val findObjectSetForType: FindObjectSetForType
private val findObjectSetForType: FindObjectSetForType,
private val copyFileToCacheDirectory: CopyFileToCacheDirectory
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return EditorViewModel(
openPage = openPage,
closePage = closeObject,
@ -81,7 +83,8 @@ open class EditorViewModelFactory(
searchObjects = searchObjects,
getDefaultEditorType = getDefaultEditorType,
findObjectSetForType = findObjectSetForType,
createObjectSet = createObjectSet
createObjectSet = createObjectSet,
copyFileToCache = copyFileToCacheDirectory
) as T
}
}

View file

@ -54,7 +54,7 @@ class LinkAddViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return LinkAddViewModel(unlink) as T
}
}

View file

@ -27,7 +27,7 @@ open class ArchiveViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ArchiveViewModel(
openPage = openPage,
closePage = closePage,

View file

@ -22,7 +22,7 @@ class CreateBookmarkViewModel() : ViewStateViewModel<ViewState>() {
class Factory() : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
override fun <T : ViewModel> create(modelClass: Class<T>): T =
CreateBookmarkViewModel() as T
}
}

View file

@ -203,7 +203,7 @@ class SelectCoverObjectViewModel(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return SelectCoverObjectViewModel(
setCoverColor = setCoverColor,
setCoverImage = setCoverImage,
@ -246,7 +246,7 @@ class SelectCoverObjectSetViewModel(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return SelectCoverObjectSetViewModel(
setCoverColor = setCoverColor,
setCoverImage = setCoverImage,

View file

@ -18,7 +18,7 @@ sealed class Command {
) : Command()
data class OpenGallery(
val mediaType: String
val mimeType: String
) : Command()
data class OpenBookmarkSetter(
@ -43,10 +43,6 @@ sealed class Command {
val excludedTypes: List<String> = emptyList()
) : Command()
data class RequestDownloadPermission(
val id: String
) : Command()
data class OpenFileByDefaultApp(
val id: String,
val mime: String,

View file

@ -97,7 +97,7 @@ class ObjectLayoutViewModel(
private val storage: Editor.Storage
): ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ObjectLayoutViewModel(
dispatcher = dispatcher,
setObjectLayout = setObjectLayout,

View file

@ -65,7 +65,7 @@ class DocumentAddBlockViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return DocumentAddBlockViewModel(getObjectTypes) as T
}
}

View file

@ -16,7 +16,7 @@ class DocumentIconActionMenuViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = DocumentIconActionMenuViewModel(
override fun <T : ViewModel> create(modelClass: Class<T>): T = DocumentIconActionMenuViewModel(
setEmojiIcon = setEmojiIcon,
setImageIcon = setImageIcon,
dispatcher = dispatcher,

View file

@ -20,7 +20,7 @@ class ObjectIconPickerViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ObjectIconPickerViewModel(
setEmojiIcon = setEmojiIcon,
setImageIcon = setImageIcon,
@ -42,7 +42,7 @@ class ObjectSetIconPickerViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ObjectSetIconPickerViewModel(
setEmojiIcon = setEmojiIcon,
setImageIcon = setImageIcon,

View file

@ -30,7 +30,7 @@ class KeychainPhraseViewModelFactory(
private val getMnemonic: GetMnemonic
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return KeychainPhraseViewModel(
getMnemonic = getMnemonic
) as T

View file

@ -15,7 +15,7 @@ class LinkToObjectViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return LinkToObjectViewModel(
urlBuilder = urlBuilder,
getObjectTypes = getObjectTypes,
@ -33,7 +33,7 @@ class LinkToObjectOrWebViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return LinkToObjectOrWebViewModel(
urlBuilder = urlBuilder,
getObjectTypes = getObjectTypes,

View file

@ -14,7 +14,7 @@ class MainViewModelFactory(
private val restoreWallpaper: RestoreWallpaper
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(
override fun <T : ViewModel> create(
modelClass: Class<T>
): T = MainViewModel(
launchAccount = launchAccount,

View file

@ -15,7 +15,7 @@ class MoveToViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return MoveToViewModel(
urlBuilder = urlBuilder,
getObjectTypes = getObjectTypes,

View file

@ -17,7 +17,7 @@ class PageNavigationViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return PageNavigationViewModel(
urlBuilder = urlBuilder,
getObjectInfoWithLinks = getObjectInfoWithLinks,

View file

@ -45,7 +45,7 @@ class CreateObjectViewModel(private val createPage: CreatePage) : ViewModel(){
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CreateObjectViewModel(createPage = createPage) as T
}
}

View file

@ -274,7 +274,7 @@ class ObjectMenuViewModel(
private val analytics: Analytics,
private val dispatcher: Dispatcher<Payload>
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ObjectMenuViewModel(
setObjectIsArchived = setObjectIsArchived,
addToFavorite = addToFavorite,
@ -312,7 +312,7 @@ class ObjectSetMenuViewModel(
private val analytics: Analytics,
private val state: StateFlow<ObjectSet>
) : ViewModelProvider.Factory {
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ObjectSetMenuViewModel(
setObjectIsArchived = setObjectIsArchived,
addToFavorite = addToFavorite,

View file

@ -9,7 +9,7 @@ class ObjectTypeChangeViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ObjectTypeChangeViewModel(
getCompatibleObjectTypes = getCompatibleObjectTypes
) as T

View file

@ -15,7 +15,7 @@ class ProfileViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ProfileViewModel(
logout = logout,
getCurrentAccount = getCurrentAccount,

View file

@ -404,7 +404,7 @@ class RelationOptionValueDVAddViewModel(
private val dispatcher: Dispatcher<Payload>,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return RelationOptionValueDVAddViewModel(
details = details,
values = values,
@ -570,7 +570,7 @@ class RelationOptionValueAddViewModel(
private val dispatcher: Dispatcher<Payload>,
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return RelationOptionValueAddViewModel(
details = details,
values = values,

View file

@ -26,7 +26,7 @@ class ObjectRelationListViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return RelationListViewModel(
stores = stores,
urlBuilder = urlBuilder,

View file

@ -113,7 +113,7 @@ class RelationAddToObjectViewModel(
private val analytics: Analytics
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return RelationAddToObjectViewModel(
addRelationToObject = addRelationToObject,
objectRelationList = objectRelationList,
@ -202,7 +202,7 @@ class RelationAddToDataViewViewModel(
private val analytics: Analytics
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return RelationAddToDataViewViewModel(
addRelationToDataView = addRelationToDataView,
objectRelationList = objectRelationList,

View file

@ -104,7 +104,7 @@ class RelationCreateFromScratchForObjectViewModel(
private val analytics: Analytics
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return RelationCreateFromScratchForObjectViewModel(
dispatcher = dispatcher,
addNewRelationToObject = addNewRelationToObject,
@ -157,7 +157,7 @@ class RelationCreateFromScratchForObjectBlockViewModel(
private val analytics: Analytics
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return RelationCreateFromScratchForObjectBlockViewModel(
dispatcher = dispatcher,
addNewRelationToObject = addNewRelationToObject,
@ -247,7 +247,7 @@ class RelationCreateFromScratchForDataViewViewModel(
private val analytics: Analytics
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return RelationCreateFromScratchForDataViewViewModel(
dispatcher = dispatcher,
addNewRelationToDataView = addNewRelationToDataView,

View file

@ -163,7 +163,7 @@ class RelationFileValueAddViewModel(
private val urlBuilder: UrlBuilder
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return RelationFileValueAddViewModel(
relations = relations,
values = values,

View file

@ -194,7 +194,7 @@ class RelationObjectValueAddViewModel(
private val objectTypesProvider: ObjectTypesProvider
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return RelationObjectValueAddViewModel(
relations = relations,
values = values,

View file

@ -259,7 +259,7 @@ class ViewerRelationsViewModel(
private val deleteRelationFromDataView: DeleteRelationFromDataView
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ViewerRelationsViewModel(
objectSetState = state,
session = session,

View file

@ -15,7 +15,7 @@ class ObjectSearchViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ObjectSearchViewModel(
urlBuilder = urlBuilder,
getObjectTypes = getObjectTypes,

View file

@ -82,7 +82,7 @@ class CreateDataViewViewerViewModel(
private val analytics: Analytics
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CreateDataViewViewerViewModel(
addDataViewViewer = addDataViewViewer,
dispatcher = dispatcher,

View file

@ -118,7 +118,7 @@ class CreateObjectSetViewModel(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CreateObjectSetViewModel(
getObjectTypes = getObjectTypes,
createObjectSet = createObjectSet,

View file

@ -44,7 +44,7 @@ class CreateObjectTypeViewModel : ViewModel() {
class Factory : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return CreateObjectTypeViewModel() as T
}
}

View file

@ -71,7 +71,7 @@ class DataViewViewerActionViewModel(
private val objectSetState: StateFlow<ObjectSet>
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return DataViewViewerActionViewModel(
duplicateDataViewViewer = duplicateDataViewViewer,
deleteDataViewViewer = deleteDataViewViewer,

View file

@ -233,7 +233,7 @@ class EditDataViewViewerViewModel(
private val objectSetSession: ObjectSetSession
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return EditDataViewViewerViewModel(
renameDataViewViewer = renameDataViewViewer,
deleteDataViewViewer = deleteDataViewViewer,

View file

@ -101,7 +101,7 @@ class ManageViewerViewModel(
private val setActiveViewer: SetActiveViewer
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ManageViewerViewModel(
state = state,
session = session,

View file

@ -51,7 +51,7 @@ class ObjectSetRecordViewModel(
private val objectSetRecordCache: ObjectSetRecordCache
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ObjectSetRecordViewModel(
objectSetState = objectSetState,
updateDataViewRecord = updateDataViewRecord,

View file

@ -32,7 +32,7 @@ class ObjectSetViewModelFactory(
private val analytics: Analytics
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ObjectSetViewModel(
reducer = reducer,
openObjectSet = openObjectSet,

View file

@ -135,7 +135,7 @@ class RelationDateValueViewModel(
private val values: ObjectValueProvider
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return RelationDateValueViewModel(relations, values) as T
}
}

View file

@ -72,7 +72,7 @@ class RelationTextValueViewModel(
private val values: ObjectValueProvider
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return RelationTextValueViewModel(relations, values) as T
}
}

View file

@ -1,5 +1,6 @@
package com.anytypeio.anytype.presentation.sets
import android.net.Uri
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import androidx.lifecycle.viewModelScope
@ -22,7 +23,10 @@ import com.anytypeio.anytype.presentation.objects.getProperName
import com.anytypeio.anytype.presentation.relations.providers.ObjectDetailProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectRelationProvider
import com.anytypeio.anytype.presentation.relations.providers.ObjectValueProvider
import com.anytypeio.anytype.presentation.util.CopyFileStatus
import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.presentation.util.OnCopyFileToCacheAction
import kotlinx.coroutines.Job
import kotlinx.coroutines.flow.*
import kotlinx.coroutines.launch
@ -33,7 +37,8 @@ abstract class RelationValueBaseViewModel(
private val values: ObjectValueProvider,
private val details: ObjectDetailProvider,
private val types: ObjectTypesProvider,
private val urlBuilder: UrlBuilder
private val urlBuilder: UrlBuilder,
private val copyFileToCache: CopyFileToCacheDirectory
) : BaseViewModel() {
val navigation = MutableSharedFlow<AppNavigation.Command>()
@ -304,6 +309,42 @@ abstract class RelationValueBaseViewModel(
}
}
//region COPY FILE TO CACHE
val copyFileStatus = MutableSharedFlow<CopyFileStatus>(replay = 0)
fun onStartCopyFileToCacheDir(uri: Uri) {
copyFileToCache.execute(
uri = uri,
scope = viewModelScope,
listener = copyFileListener
)
}
fun onCancelCopyFileToCacheDir() {
copyFileToCache.cancel()
}
private val copyFileListener = object : OnCopyFileToCacheAction {
override fun onCopyFileStart() {
viewModelScope.launch {
copyFileStatus.emit(CopyFileStatus.Started)
}
}
override fun onCopyFileResult(result: String?) {
viewModelScope.launch {
copyFileStatus.emit(CopyFileStatus.Completed(result))
}
}
override fun onCopyFileError(msg: String) {
viewModelScope.launch {
copyFileStatus.emit(CopyFileStatus.Error(msg))
}
}
}
//endregion
sealed class ObjectRelationValueCommand {
object ShowAddStatusOrTagScreen : ObjectRelationValueCommand()
object ShowAddObjectScreen : ObjectRelationValueCommand()
@ -384,15 +425,16 @@ class RelationValueDVViewModel(
private val removeTagFromDataViewRecord: RemoveTagFromDataViewRecord,
private val removeStatusFromDataViewRecord: RemoveStatusFromDataViewRecord,
private val updateDataViewRecord: UpdateDataViewRecord,
private val dispatcher: Dispatcher<Payload>,
private val urlBuilder: UrlBuilder,
private val addFileToRecord: AddFileToRecord
private val addFileToRecord: AddFileToRecord,
copyFileToCache: CopyFileToCacheDirectory
) : RelationValueBaseViewModel(
relations = relations,
values = values,
details = details,
types = types,
urlBuilder = urlBuilder
urlBuilder = urlBuilder,
copyFileToCache = copyFileToCache
) {
fun onRemoveTagFromDataViewRecordClicked(
@ -582,27 +624,27 @@ class RelationValueDVViewModel(
class Factory(
private val relations: ObjectRelationProvider,
private val values: ObjectValueProvider,
private val dispatcher: Dispatcher<Payload>,
private val details: ObjectDetailProvider,
private val types: ObjectTypesProvider,
private val updateDataViewRecord: UpdateDataViewRecord,
private val removeTagFromRecord: RemoveTagFromDataViewRecord,
private val removeStatusFromDataViewRecord: RemoveStatusFromDataViewRecord,
private val urlBuilder: UrlBuilder,
private val addFileToRecord: AddFileToRecord
private val addFileToRecord: AddFileToRecord,
private val copyFileToCache: CopyFileToCacheDirectory
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = RelationValueDVViewModel(
override fun <T : ViewModel> create(modelClass: Class<T>): T = RelationValueDVViewModel(
relations = relations,
values = values,
details = details,
types = types,
dispatcher = dispatcher,
removeTagFromDataViewRecord = removeTagFromRecord,
removeStatusFromDataViewRecord = removeStatusFromDataViewRecord,
urlBuilder = urlBuilder,
updateDataViewRecord = updateDataViewRecord,
addFileToRecord = addFileToRecord
addFileToRecord = addFileToRecord,
copyFileToCache = copyFileToCache
) as T
}
}
@ -615,13 +657,15 @@ class RelationValueViewModel(
private val updateDetail: UpdateDetail,
private val dispatcher: Dispatcher<Payload>,
private val urlBuilder: UrlBuilder,
private val addFileToObject: AddFileToObject
private val addFileToObject: AddFileToObject,
copyFileToCache: CopyFileToCacheDirectory
) : RelationValueBaseViewModel(
relations = relations,
values = values,
details = details,
types = types,
urlBuilder = urlBuilder
urlBuilder = urlBuilder,
copyFileToCache = copyFileToCache
) {
fun onObjectValueOrderChanged(
@ -791,10 +835,11 @@ class RelationValueViewModel(
private val types: ObjectTypesProvider,
private val updateDetail: UpdateDetail,
private val urlBuilder: UrlBuilder,
private val addFileToObject: AddFileToObject
private val addFileToObject: AddFileToObject,
private val copyFileToCache: CopyFileToCacheDirectory
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T = RelationValueViewModel(
override fun <T : ViewModel> create(modelClass: Class<T>): T = RelationValueViewModel(
relations = relations,
values = values,
details = details,
@ -802,7 +847,8 @@ class RelationValueViewModel(
dispatcher = dispatcher,
updateDetail = updateDetail,
urlBuilder = urlBuilder,
addFileToObject = addFileToObject
addFileToObject = addFileToObject,
copyFileToCache = copyFileToCache
) as T
}
}

View file

@ -20,7 +20,7 @@ class SelectFilterRelationViewModel(
private val session: ObjectSetSession
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return SelectFilterRelationViewModel(
objectSetState = state,
session = session

View file

@ -51,7 +51,7 @@ class SelectSortRelationViewModel(
private val addDataViewViewerSort: AddDataViewViewerSort
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return SelectSortRelationViewModel(
objectSetState = state,
session = session,

View file

@ -45,7 +45,7 @@ class ViewerCustomizeViewModel(private val state: StateFlow<ObjectSet>) : ViewMo
private val state: StateFlow<ObjectSet>
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ViewerCustomizeViewModel(state) as T
}
}

View file

@ -159,7 +159,7 @@ class ViewerSortByViewModel(private val state: StateFlow<ObjectSet>) : ViewModel
class Factory(private val state: StateFlow<ObjectSet>) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ViewerSortByViewModel(state = state) as T
}
}

View file

@ -706,7 +706,7 @@ open class FilterViewModel(
private val objectTypesProvider: ObjectTypesProvider
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return FilterViewModel(
objectSetState = objectSetState,
session = session,

View file

@ -40,7 +40,7 @@ class PickFilterConditionViewModel : BaseViewModel() {
class Factory() : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return PickFilterConditionViewModel() as T
}
}

View file

@ -200,7 +200,7 @@ class ViewerFilterViewModel(
private val urlBuilder: UrlBuilder
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ViewerFilterViewModel(
objectSetState = state,
session = session,

View file

@ -95,7 +95,7 @@ class ModifyViewerSortViewModel(
private val updateDataViewViewer: UpdateDataViewViewer
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ModifyViewerSortViewModel(
objectSetState = state,
dispatcher = dispatcher,

View file

@ -132,7 +132,7 @@ class ViewerSortViewModel(
private val updateDataViewViewer: UpdateDataViewViewer
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ViewerSortViewModel(
objectSetState = state,
session = session,

View file

@ -102,7 +102,7 @@ class ViewerCardSizeSelectViewModel(
private val updateDataViewViewer: UpdateDataViewViewer
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ViewerCardSizeSelectViewModel(
objectSetState = objectSetState,
session = session,

View file

@ -98,7 +98,7 @@ class ViewerImagePreviewSelectViewModel(
private val updateDataViewViewer: UpdateDataViewViewer
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return ViewerImagePreviewSelectViewModel(
objectSetState = objectSetState,
session = session,

View file

@ -128,7 +128,7 @@ class OtherSettingsViewModel(
private val analytics: Analytics
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(
override fun <T : ViewModel> create(
modelClass: Class<T>
): T = OtherSettingsViewModel(
getDefaultEditorType = getDefaultEditorType,

View file

@ -32,7 +32,7 @@ class SplashViewModelFactory(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T =
override fun <T : ViewModel> create(modelClass: Class<T>): T =
SplashViewModel(
checkAuthorizationStatus = checkAuthorizationStatus,
launchAccount = launchAccount,

View file

@ -0,0 +1,168 @@
package com.anytypeio.anytype.presentation.util
import android.content.Context
import android.net.Uri
import android.provider.OpenableColumns
import kotlinx.coroutines.*
import kotlinx.coroutines.flow.flow
import kotlinx.coroutines.flow.flowOn
import kotlinx.coroutines.flow.single
import timber.log.Timber
import java.io.File
import java.io.FileOutputStream
import java.lang.Exception
import java.lang.ref.WeakReference
import kotlin.system.measureTimeMillis
interface CopyFileToCacheDirectory {
fun execute(uri: Uri, scope: CoroutineScope, listener: OnCopyFileToCacheAction)
fun cancel()
}
const val SCHEME_CONTENT = "content"
const val CHAR_SLASH = '/'
const val TEMPORARY_DIRECTORY_NAME = "TemporaryFiles"
sealed class CopyFileStatus {
data class Error(val msg: String) : CopyFileStatus()
object Started: CopyFileStatus()
data class Completed(val result: String?): CopyFileStatus()
}
class DefaultCopyFileToCacheDirectory(context: Context) : CopyFileToCacheDirectory {
private var mContext: WeakReference<Context>? = null
private var job: Job? = null
init {
mContext = WeakReference(context)
}
override fun execute(uri: Uri, scope: CoroutineScope, listener: OnCopyFileToCacheAction) {
getNewPathInCacheDir(uri, scope, listener)
}
override fun cancel() {
job?.cancel()
mContext?.get()?.deleteTemporaryFolder()
}
private fun getNewPathInCacheDir(
uri: Uri,
scope: CoroutineScope,
listener: OnCopyFileToCacheAction
) {
var path: String? = null
job = scope.launch {
try {
val time = measureTimeMillis {
path = flow {
emit(copyFileToCacheDir(uri, job, listener))
}
.flowOn(Dispatchers.IO)
.single()
}
Timber.d("Total load file time: $time")
} catch (e: Exception) {
Timber.e(e, "Error while getNewPathInCacheDir")
listener.onCopyFileError(e.localizedMessage ?: "Unknown error")
} finally {
if (scope.isActive) {
listener.onCopyFileResult(path)
}
}
}
}
private fun copyFileToCacheDir(
uri: Uri,
job: Job?,
listener: OnCopyFileToCacheAction
): String? {
var newFile: File? = null
mContext?.get()?.let { context: Context ->
val cacheDir = context.getExternalFilesDirTemp()
if (cacheDir != null && !cacheDir.exists()) {
cacheDir.mkdirs()
}
try {
val inputStream = context.contentResolver.openInputStream(uri)
inputStream?.use { input ->
newFile = File(cacheDir?.path + "/" + getFileName(context, uri));
listener.onCopyFileStart()
Timber.d("Start copy file to cache : ${newFile?.path}")
FileOutputStream(newFile).use { output ->
job?.ensureActive()
val buffer = ByteArray(1024)
var read: Int = input.read(buffer)
while (read != -1) {
job?.ensureActive()
output.write(buffer, 0, read)
read = input.read(buffer)
}
}
return newFile?.path
}
} catch (e: Exception) {
val deleteResult = newFile?.deleteRecursively()
Timber.d("Get exception while copying file, deleteRecursively success: $deleteResult")
Timber.e(e, "Error while coping file")
}
}
return null
}
private fun getFileName(context: Context, uri: Uri): String? {
var result: String? = null
if (uri.scheme == SCHEME_CONTENT) {
context.contentResolver.query(
uri,
null,
null,
null,
null
)?.use { cursor ->
if (cursor.moveToFirst()) {
val index = cursor.getColumnIndex(OpenableColumns.DISPLAY_NAME)
if (index != -1) {
result = cursor.getString(index)
}
}
}
}
if (result == null) {
uri.path?.let { path ->
val cut = path.lastIndexOf(CHAR_SLASH)
if (cut != -1) {
result = path.substring(cut)
}
}
}
return result
}
}
/**
* Delete the /storage/emulated/0/Android/data/package/files/$TEMPORARY_DIRECTORY_NAME folder.
*/
private fun Context.deleteTemporaryFolder() {
getExternalFilesDirTemp()?.let { folder ->
if (folder.deleteRecursively()) {
Timber.d("${folder.absolutePath} delete successfully")
} else {
Timber.d("${folder.absolutePath} delete is unsuccessfully")
}
}
}
/**
* Return /storage/emulated/0/Android/data/package/files/$TEMPORARY_DIRECTORY_NAME directory
*/
private fun Context.getExternalFilesDirTemp(): File? = getExternalFilesDir(TEMPORARY_DIRECTORY_NAME)
interface OnCopyFileToCacheAction {
fun onCopyFileStart()
fun onCopyFileResult(result: String?)
fun onCopyFileError(msg: String)
}

View file

@ -66,7 +66,7 @@ class WallpaperSelectViewModel(
) : ViewModelProvider.Factory {
@Suppress("UNCHECKED_CAST")
override fun <T : ViewModel?> create(modelClass: Class<T>): T {
override fun <T : ViewModel> create(modelClass: Class<T>): T {
return WallpaperSelectViewModel(
setWallpaper = setWallpaper
) as T

View file

@ -349,42 +349,4 @@ class BlockReadModeTest : EditorViewModelTest() {
)
)
}
@Test
fun `should enter edit mode after action menu is closed by action item download`() {
val paragraphs = blocks
stubObserveEvents(flow)
stubOpenPage()
buildViewModel()
vm.onStart(root)
coroutineTestRule.advanceTime(100)
// TESTING
vm.onClickListener(
clicked = ListenerType.LongClick(
target = paragraphs[1].id,
dimensions = BlockDimensions(0, 0, 0, 0, 0, 0)
)
)
vm.onActionMenuItemClicked(id = paragraphs[1].id, action = ActionItemType.Download)
val testObserver = vm.state.test()
val initial = blockViewsEditMode
coroutineTestRule.advanceTime(EditorViewModel.TEXT_CHANGES_DEBOUNCE_DURATION)
runBlockingTest {
testObserver.assertValue(
ViewState.Success(
blocks = listOf(titleEditModeView) + initial
)
)
}
}
}

View file

@ -48,6 +48,7 @@ import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer
import com.anytypeio.anytype.presentation.editor.selection.SelectionStateHolder
import com.anytypeio.anytype.presentation.editor.toggle.ToggleStateHolder
import com.anytypeio.anytype.presentation.navigation.AppNavigation
import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
import com.anytypeio.anytype.presentation.util.CoroutinesTestRule
import com.anytypeio.anytype.presentation.util.Dispatcher
import com.anytypeio.anytype.presentation.util.TXT
@ -226,6 +227,9 @@ open class EditorViewModelTest {
@Mock
lateinit var createObjectSet: CreateObjectSet
@Mock
lateinit var copyFileToCacheDirectory: CopyFileToCacheDirectory
private lateinit var updateDetail: UpdateDetail
lateinit var vm: EditorViewModel
@ -3980,7 +3984,8 @@ open class EditorViewModelTest {
objectTypesProvider = objectTypesProvider,
searchObjects = searchObjects,
findObjectSetForType = findObjectSetForType,
createObjectSet = createObjectSet
createObjectSet = createObjectSet,
copyFileToCache = copyFileToCacheDirectory
)
}

View file

@ -36,6 +36,7 @@ import com.anytypeio.anytype.presentation.editor.editor.pattern.DefaultPatternMa
import com.anytypeio.anytype.presentation.editor.render.DefaultBlockViewRenderer
import com.anytypeio.anytype.presentation.editor.selection.SelectionStateHolder
import com.anytypeio.anytype.presentation.editor.toggle.ToggleStateHolder
import com.anytypeio.anytype.presentation.util.CopyFileToCacheDirectory
import com.anytypeio.anytype.presentation.util.Dispatcher
import kotlinx.coroutines.flow.Flow
import kotlinx.coroutines.flow.emptyFlow
@ -196,6 +197,9 @@ open class EditorPresentationTestSetup {
@Mock
lateinit var createObjectSet: CreateObjectSet
@Mock
lateinit var copyFileToCacheDirectory: CopyFileToCacheDirectory
private val builder: UrlBuilder get() = UrlBuilder(gateway)
private lateinit var updateDetail: UpdateDetail
@ -281,7 +285,8 @@ open class EditorPresentationTestSetup {
searchObjects = searchObjects,
getDefaultEditorType = getDefaultEditorType,
findObjectSetForType = findObjectSetForType,
createObjectSet = createObjectSet
createObjectSet = createObjectSet,
copyFileToCache = copyFileToCacheDirectory
)
}

View file

@ -24,12 +24,12 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = JavaVersion.VERSION_11
}
}

View file

@ -4,14 +4,14 @@ apply plugin: 'kotlin-android-extensions'
apply plugin: 'kotlin-kapt'
android {
compileSdkVersion 29
buildToolsVersion "29.0.3"
compileSdkVersion 31
buildToolsVersion "31.0.0"
defaultConfig {
applicationId "com.anytypeio.anytype.sample"
minSdkVersion 26
targetSdkVersion 29
targetSdkVersion 31
versionCode 1
versionName "1.0"
@ -39,12 +39,12 @@ android {
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
kotlinOptions {
jvmTarget = "1.8"
jvmTarget = JavaVersion.VERSION_11
}
}
@ -66,13 +66,10 @@ dependencies {
implementation applicationDependencies.timber
implementation applicationDependencies.fragment
implementation applicationDependencies.design
implementation applicationDependencies.permissionDisp
implementation applicationDependencies.pickT
//implementation 'com.github.gregcockroft:AndroidMath:ALPHA'
kapt applicationDependencies.permissionDispCompiler
testImplementation unitTestDependencies.junit
testImplementation unitTestDependencies.kotlinTest
testImplementation unitTestDependencies.robolectricLatest