From f349aa416729f57e16cc50011f35c57ebfd97112 Mon Sep 17 00:00:00 2001 From: wiiznokes <78230769+wiiznokes@users.noreply.github.com> Date: Sun, 19 May 2024 21:33:28 +0200 Subject: [PATCH] Feat app storage (#35) * add basic type * bottom sheet ui * some fixes * working solution * fix git push and pull without remove url * ui fixes * fix * add localization * rename to git_wrapper * french translation --- app/src/main/cpp/CMakeLists.txt | 4 +- .../main/cpp/{gitnote.cpp => git_wrapper.cpp} | 1 + .../github/wiiznokes/gitnote/MainActivity.kt | 37 ++-- .../wiiznokes/gitnote/data/AppPreferences.kt | 100 +++++++-- .../gitnote/data/platform/FileSystem.kt | 9 +- .../wiiznokes/gitnote/manager/GitManager.kt | 2 +- .../gitnote/manager/StorageManager.kt | 51 +++-- .../gitnote/ui/destination/InitDestination.kt | 24 ++- .../github/wiiznokes/gitnote/ui/model/Init.kt | 6 + .../ui/screen/init/FileExplorerScreen.kt | 25 ++- .../gitnote/ui/screen/init/InitScreen.kt | 40 +--- .../gitnote/ui/screen/init/MainInitScreen.kt | 173 +++++++++++++--- .../gitnote/ui/screen/init/RemoteScreen.kt | 196 +++++++++--------- .../gitnote/ui/screen/init/StorageScreen.kt | 47 ----- .../gitnote/ui/screen/settings/Settings.kt | 4 - .../gitnote/ui/viewmodel/EditViewModel.kt | 4 +- .../gitnote/ui/viewmodel/GridViewModel.kt | 2 +- .../gitnote/ui/viewmodel/InitViewModel.kt | 90 +++++--- app/src/main/res/values-fr/strings.xml | 28 +++ app/src/main/res/values/strings.xml | 28 +++ 20 files changed, 554 insertions(+), 317 deletions(-) rename app/src/main/cpp/{gitnote.cpp => git_wrapper.cpp} (99%) delete mode 100644 app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/StorageScreen.kt diff --git a/app/src/main/cpp/CMakeLists.txt b/app/src/main/cpp/CMakeLists.txt index c20412d..6c73e19 100644 --- a/app/src/main/cpp/CMakeLists.txt +++ b/app/src/main/cpp/CMakeLists.txt @@ -1,7 +1,7 @@ cmake_minimum_required(VERSION 3.22.1) -project("gitnote") +project("git_wrapper") set(JNI_LIBS ${CMAKE_SOURCE_DIR}/../jniLibs/${ANDROID_ABI}) @@ -18,7 +18,7 @@ add_library(${CMAKE_PROJECT_NAME} SHARED merge.cpp remote.cpp CallbackHandler.cpp - gitnote.cpp) + git_wrapper.cpp) target_link_libraries(${CMAKE_PROJECT_NAME} ${JNI_LIBS}/libgit2.so diff --git a/app/src/main/cpp/gitnote.cpp b/app/src/main/cpp/git_wrapper.cpp similarity index 99% rename from app/src/main/cpp/gitnote.cpp rename to app/src/main/cpp/git_wrapper.cpp index 0526b6f..ffba760 100644 --- a/app/src/main/cpp/gitnote.cpp +++ b/app/src/main/cpp/git_wrapper.cpp @@ -80,6 +80,7 @@ Java_io_github_wiiznokes_gitnote_manager_GitManagerKt_lastCommitLib(JNIEnv *env, err = git_reference_name_to_id(&sha_last_commit, repo, "HEAD"); + // XXX: can fail with error 4 if there are no commit yet in the repo CHECK_LG2_RETURN(err, "git_reference_name_to_id", NULL); // Convert git_oid to string diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/MainActivity.kt b/app/src/main/java/io/github/wiiznokes/gitnote/MainActivity.kt index 3ec896c..250375c 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/MainActivity.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/MainActivity.kt @@ -15,7 +15,6 @@ import dev.olshevski.navigation.reimagined.popAll import dev.olshevski.navigation.reimagined.popUpTo import dev.olshevski.navigation.reimagined.rememberNavController import io.github.wiiznokes.gitnote.MyApp.Companion.appModule -import io.github.wiiznokes.gitnote.helper.StoragePermissionHelper import io.github.wiiznokes.gitnote.ui.destination.AppDestination import io.github.wiiznokes.gitnote.ui.destination.Destination import io.github.wiiznokes.gitnote.ui.destination.InitDestination @@ -56,23 +55,19 @@ class MainActivity : ComponentActivity() { val startDestination: Destination = remember { - if (!StoragePermissionHelper.isPermissionGranted()) { - Destination.Init(InitDestination.LocalStoragePermission) - } else { - if (runBlocking { vm.tryInit() }) { - if (isEditUnsaved()) { - Log.d(TAG, "launch as EDIT_IS_UNSAVED") - Destination.App( - AppDestination.EditSaved - ) - } else { - Destination.App( - AppDestination.Grid - //AppDestination.Settings(SettingsDestination.Main) - ) - } - } else Destination.Init(InitDestination.Main) - } + if (runBlocking { vm.tryInit() }) { + if (isEditUnsaved()) { + Log.d(TAG, "launch as EDIT_IS_UNSAVED") + Destination.App( + AppDestination.EditSaved + ) + } else { + Destination.App( + AppDestination.Grid + //AppDestination.Settings(SettingsDestination.Main) + ) + } + } else Destination.Init(InitDestination.Main) } @@ -104,11 +99,7 @@ class MainActivity : ComponentActivity() { appDestination = destination.appDestination, onStorageFailure = { navController.popAll() - if (!StoragePermissionHelper.isPermissionGranted()) { - navController.navigate(Destination.Init(InitDestination.LocalStoragePermission)) - } else { - navController.navigate(Destination.Init(InitDestination.Main)) - } + navController.navigate(Destination.Init(InitDestination.Main)) } ) } diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/data/AppPreferences.kt b/app/src/main/java/io/github/wiiznokes/gitnote/data/AppPreferences.kt index 2a1407a..6d1e8c3 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/data/AppPreferences.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/data/AppPreferences.kt @@ -1,6 +1,8 @@ package io.github.wiiznokes.gitnote.data import android.content.Context +import android.os.Parcelable +import io.github.wiiznokes.gitnote.MyApp import io.github.wiiznokes.gitnote.manager.PreferencesManager import io.github.wiiznokes.gitnote.ui.model.GitCreed import io.github.wiiznokes.gitnote.ui.model.NoteMinWidth @@ -8,26 +10,68 @@ import io.github.wiiznokes.gitnote.ui.model.Provider import io.github.wiiznokes.gitnote.ui.model.SortOrder import io.github.wiiznokes.gitnote.ui.model.SortType import io.github.wiiznokes.gitnote.ui.theme.Theme +import kotlinx.coroutines.runBlocking +import kotlinx.parcelize.Parcelize +import kotlin.io.path.pathString class AppPreferences( context: Context ) : PreferencesManager(context, "settings") { + + companion object { + val appStorageRepoPath = + MyApp.appModule.context.filesDir.toPath().resolve("repo").pathString + const val DEFAULT_USERNAME = "gitnote" + } + val dynamicColor = booleanPreference("dynamicColor", true) val theme = enumPreference("theme", Theme.SYSTEM) - - val isRepoInitialize = booleanPreference("isRepoInitialize", false) val databaseCommit = stringPreference("") - val repoPath = stringPreference("repoPath") + private val repoPath = stringPreference("repoPath") + + suspend fun repoPath(): String { + return when (repoState.get()) { + RepoState.NoRepo -> throw Exception("calling repoPath function with no repo initialized") + RepoState.AppStorage -> appStorageRepoPath + RepoState.DeviceStorage -> repoPath.get() + } + } + + fun repoPathBlocking(): String = runBlocking { repoPath() } + + + fun repoPathSafely(): String { + return try { + repoPathBlocking() + } catch (e: Exception) { + "" + } + } + val remoteUrl = stringPreference("remoteUrl", "") val userName = stringPreference("userName", "") val password = stringPreference( "password", "" ) - val provider = enumPreference("provider", Provider.GitHub) + suspend fun gitCreed(): GitCreed? { + val userName = this.userName.get() + val password = this.password.get() + + return if (userName.isEmpty() or password.isEmpty()) { + null + } else { + GitCreed( + userName = userName, + password = password + ) + } + } + + val provider = enumPreference("provider", Provider.GitHub) val sortType = enumPreference("sortType", SortType.Modification) @@ -50,30 +94,46 @@ class AppPreferences( ) ) - suspend fun initRepo(repoPath: String) { - isRepoInitialize.update(true) - this.repoPath.update(repoPath) + + val repoState = enumPreference("repoState", RepoState.NoRepo) + + suspend fun initRepo(repoState: NewRepoState) { + when (repoState) { + NewRepoState.AppStorage -> { + this.repoState.update(RepoState.AppStorage) + } + + is NewRepoState.DeviceStorage -> { + this.repoState.update(RepoState.DeviceStorage) + this.repoPath.update(repoState.path) + } + } lastOpenedFolder.update("") } suspend fun closeRepo() { - isRepoInitialize.update(false) + repoState.update(RepoState.NoRepo) databaseCommit.update("") } - suspend fun gitCreed(): GitCreed? { - val userName = this.userName.get() - val password = this.password.get() +} - return if (userName.isEmpty() or password.isEmpty()) { - null - } else { - GitCreed( - userName = userName, - password = password - ) - } - } +enum class RepoState { + NoRepo, + AppStorage, + DeviceStorage +} + +@Parcelize +sealed class NewRepoState : Parcelable { + data object AppStorage : NewRepoState() + class DeviceStorage(val path: String) : NewRepoState() + fun repoPath(): String { + return when (this) { + AppStorage -> AppPreferences.appStorageRepoPath + is DeviceStorage -> this.path + } + } } diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/data/platform/FileSystem.kt b/app/src/main/java/io/github/wiiznokes/gitnote/data/platform/FileSystem.kt index 6ba0721..eb545bd 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/data/platform/FileSystem.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/data/platform/FileSystem.kt @@ -1,6 +1,8 @@ package io.github.wiiznokes.gitnote.data.platform import android.os.Environment +import io.github.wiiznokes.gitnote.MyApp +import io.github.wiiznokes.gitnote.R import io.github.wiiznokes.gitnote.data.removeFirstAndLastSlash import io.github.wiiznokes.gitnote.ui.model.FileExtension import io.github.wiiznokes.gitnote.util.toResult @@ -11,6 +13,7 @@ import java.nio.file.attribute.FileTime import kotlin.Result.Companion.failure import kotlin.Result.Companion.success import kotlin.io.path.ExperimentalPathApi +import kotlin.io.path.createDirectories import kotlin.io.path.createDirectory import kotlin.io.path.createFile import kotlin.io.path.deleteExisting @@ -140,11 +143,11 @@ sealed class NodeFs( try { if (!pathFs.isDirectory()) { - return failure(Exception("The path is not a directory")) + return failure(Exception(MyApp.appModule.context.getString(R.string.error_path_not_directory))) } if (pathFs.listDirectoryEntries().isNotEmpty()) { - return failure(Exception("The path is not empty")) + return failure(Exception(MyApp.appModule.context.getString(R.string.error_path_not_empty))) } } catch (e: Exception) { @@ -185,7 +188,7 @@ sealed class NodeFs( override fun create(): Result { return toResult { - pathFs.createDirectory() + pathFs.createDirectories() } } diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/manager/GitManager.kt b/app/src/main/java/io/github/wiiznokes/gitnote/manager/GitManager.kt index 334aa49..642226d 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/manager/GitManager.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/manager/GitManager.kt @@ -47,7 +47,7 @@ class GitManager { init { Log.d(TAG, "init") - System.loadLibrary("gitnote") + System.loadLibrary("git_wrapper") } } diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/manager/StorageManager.kt b/app/src/main/java/io/github/wiiznokes/gitnote/manager/StorageManager.kt index 99d4aa5..6489a98 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/manager/StorageManager.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/manager/StorageManager.kt @@ -8,6 +8,7 @@ import io.github.wiiznokes.gitnote.data.platform.NodeFs import io.github.wiiznokes.gitnote.data.room.Note import io.github.wiiznokes.gitnote.data.room.NoteFolder import io.github.wiiznokes.gitnote.data.room.RepoDatabase +import io.github.wiiznokes.gitnote.ui.model.GitCreed import kotlinx.coroutines.sync.Mutex import kotlinx.coroutines.sync.withLock import kotlin.Result.Companion.failure @@ -38,19 +39,24 @@ class StorageManager { Log.d(TAG, "updateDatabaseAndRepo") val creed = prefs.gitCreed() + val remoteUrl = prefs.remoteUrl.get() - gitManager.pull(creed).onFailure { - uiHelper.makeToast(it.message) + if (remoteUrl.isNotEmpty()) { + gitManager.pull(creed).onFailure { + uiHelper.makeToast(it.message) + } } // todo: maybe async this call - gitManager.commitAll(prefs.userName.get()).onFailure { + gitManager.commitAll(GitCreed.usernameOrDefault(creed)).onFailure { uiHelper.makeToast(it.message) } - // todo: maybe async this call - gitManager.push(creed).onFailure { - uiHelper.makeToast(it.message) + if (remoteUrl.isNotEmpty()) { + // todo: maybe async this call + gitManager.push(creed).onFailure { + uiHelper.makeToast(it.message) + } } updateDatabaseWithoutLocker() @@ -79,7 +85,7 @@ class StorageManager { return success(Unit) } - val repoPath = prefs.repoPath.get() + val repoPath = prefs.repoPath() Log.d(TAG, "repoPath = $repoPath") dao.clearAndInit(repoPath) @@ -106,7 +112,7 @@ class StorageManager { dao.removeNote(previous) dao.insertNote(new) - val rootPath = prefs.repoPath.get() + val rootPath = prefs.repoPath() val previousFile = NodeFs.File.fromPath(rootPath, previous.relativePath) previousFile.delete().onFailure { @@ -144,7 +150,7 @@ class StorageManager { update { dao.insertNote(note) - val rootPath = prefs.repoPath.get() + val rootPath = prefs.repoPath() val file = NodeFs.File.fromPath(rootPath, note.relativePath) file.create().onFailure { @@ -169,7 +175,7 @@ class StorageManager { update { dao.removeNote(note) - val rootPath = prefs.repoPath.get() + val rootPath = prefs.repoPath() val file = NodeFs.File.fromPath(rootPath, note.relativePath) file.delete().onFailure { val message = uiHelper.getString(R.string.error_delete_file, file.path, it.message) @@ -193,7 +199,7 @@ class StorageManager { } } - val rootPath = prefs.repoPath.get() + val rootPath = prefs.repoPath() noteRelativePaths.forEach { noteRelativePath -> Log.d(TAG, "deleting $noteRelativePath") @@ -216,7 +222,7 @@ class StorageManager { update { dao.insertNoteFolder(noteFolder) - val rootPath = prefs.repoPath.get() + val rootPath = prefs.repoPath() val folder = NodeFs.Folder.fromPath(rootPath, noteFolder.relativePath) folder.create().onFailure { @@ -240,8 +246,10 @@ class StorageManager { f: suspend () -> Result ): Result { + val creed = prefs.gitCreed() + val remoteUrl = prefs.remoteUrl.get() - gitManager.commitAll(prefs.userName.get()).onFailure { + gitManager.commitAll(GitCreed.usernameOrDefault(creed)).onFailure { return failure(it) } updateDatabaseWithoutLocker().onFailure { @@ -257,20 +265,27 @@ class StorageManager { } ) - gitManager.pull(prefs.gitCreed()).onFailure { - it.printStackTrace() + if (remoteUrl.isNotEmpty()) { + gitManager.pull(creed).onFailure { + it.printStackTrace() + } } updateDatabaseWithoutLocker().onFailure { return failure(it) } - gitManager.commitAll(prefs.userName.get()).onFailure { + gitManager.commitAll(GitCreed.usernameOrDefault(creed)).onFailure { return failure(it) } - gitManager.push(prefs.gitCreed()).onFailure { - it.printStackTrace() + + + if (remoteUrl.isNotEmpty()) { + gitManager.push(creed).onFailure { + it.printStackTrace() + } } + prefs.databaseCommit.update(gitManager.lastCommit()) return success(payload) diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/destination/InitDestination.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/destination/InitDestination.kt index 751794c..aa0be2f 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/destination/InitDestination.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/destination/InitDestination.kt @@ -1,15 +1,14 @@ package io.github.wiiznokes.gitnote.ui.destination import android.os.Parcelable +import io.github.wiiznokes.gitnote.MyApp +import io.github.wiiznokes.gitnote.R +import io.github.wiiznokes.gitnote.data.NewRepoState import kotlinx.parcelize.Parcelize sealed interface InitDestination : Parcelable { - - @Parcelize - data object LocalStoragePermission : InitDestination - @Parcelize data object Main : InitDestination @@ -21,12 +20,23 @@ sealed interface InitDestination : Parcelable { ) : InitDestination @Parcelize - data class Remote(val repoPath: String) : InitDestination + data class Remote(val repoState: NewRepoState) : InitDestination } enum class NewRepoSource { Create, Open, - Clone, -} \ No newline at end of file + Clone; + + + fun getExplorerTitle(): String { + val context = MyApp.appModule.context + + return when (this) { + Create -> context.getString(R.string.create_repo_explorer) + Open -> context.getString(R.string.open_repo_explorer) + Clone -> context.getString(R.string.clone_repo_explorer) + } + } +} diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Init.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Init.kt index 1fa7d2d..9162aa1 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Init.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/model/Init.kt @@ -1,6 +1,7 @@ package io.github.wiiznokes.gitnote.ui.model import android.os.Parcelable +import io.github.wiiznokes.gitnote.data.AppPreferences import kotlinx.parcelize.Parcelize @@ -32,6 +33,11 @@ data class GitCreed( val password: String, ) : Parcelable { + companion object { + fun usernameOrDefault(creed: GitCreed?): String = + creed?.userName ?: AppPreferences.DEFAULT_USERNAME + } + override fun toString(): String = "GitCreed(userName=${userName}, password=${"*".repeat(password.length)})" diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/FileExplorerScreen.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/FileExplorerScreen.kt index bbad697..6d23731 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/FileExplorerScreen.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/FileExplorerScreen.kt @@ -13,13 +13,16 @@ import androidx.compose.material.icons.rounded.CreateNewFolder import androidx.compose.material.icons.rounded.Folder import androidx.compose.material3.Button import androidx.compose.material3.IconButton +import androidx.compose.material3.LocalContentColor import androidx.compose.material3.MaterialTheme import androidx.compose.material3.Text +import androidx.compose.material3.contentColorFor import androidx.compose.runtime.Composable import androidx.compose.runtime.mutableStateOf import androidx.compose.runtime.saveable.rememberSaveable import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier +import androidx.compose.ui.graphics.Color import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.style.TextOverflow import androidx.compose.ui.unit.dp @@ -40,7 +43,8 @@ fun FileExplorerScreen( path: String?, onDirectoryClick: (String) -> Unit, onFinish: (String) -> Unit, - onBackClick: () -> Unit + onBackClick: () -> Unit, + title: String ) { @@ -92,12 +96,7 @@ fun FileExplorerScreen( onFinish(vm.currentDir.path) } ) { - val text = when (newRepoSource) { - NewRepoSource.Create -> "Create repository in this folder" - NewRepoSource.Open -> "Open this repository" - NewRepoSource.Clone -> "Clone repository in this folder" - } - Text(text = text) + Text(text = title) } } ) { @@ -113,7 +112,8 @@ fun FileExplorerScreen( path = parent.path, fullName = ".." ), - onDirectoryClick = onDirectoryClick + onDirectoryClick = onDirectoryClick, + color = MaterialTheme.colorScheme.tertiary ) } } @@ -140,7 +140,8 @@ private data class FolderItem( @Composable private fun FolderRow( item: FolderItem, - onDirectoryClick: (String) -> Unit + onDirectoryClick: (String) -> Unit, + color: Color = LocalContentColor.current ) { Row( modifier = Modifier @@ -154,12 +155,14 @@ private fun FolderRow( SimpleIcon( modifier = Modifier .size(50.dp), - imageVector = Icons.Rounded.Folder + imageVector = Icons.Rounded.Folder, + tint = color ) Text( text = item.fullName, maxLines = 1, - overflow = TextOverflow.Ellipsis + overflow = TextOverflow.Ellipsis, + color = color ) } } \ No newline at end of file diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/InitScreen.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/InitScreen.kt index 304aa63..b72ccb0 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/InitScreen.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/InitScreen.kt @@ -12,13 +12,13 @@ import dev.olshevski.navigation.reimagined.navigate import dev.olshevski.navigation.reimagined.pop import dev.olshevski.navigation.reimagined.popUpTo import dev.olshevski.navigation.reimagined.rememberNavController +import io.github.wiiznokes.gitnote.data.NewRepoState import io.github.wiiznokes.gitnote.ui.destination.InitDestination import io.github.wiiznokes.gitnote.ui.destination.NewRepoSource import io.github.wiiznokes.gitnote.ui.util.crossFade import io.github.wiiznokes.gitnote.ui.util.slide import io.github.wiiznokes.gitnote.ui.viewmodel.InitViewModel import io.github.wiiznokes.gitnote.ui.viewmodel.viewModelFactory -import kotlinx.coroutines.runBlocking private const val TAG = "InitScreen" @@ -42,20 +42,10 @@ fun InitScreen( transitionSpec = InitNavTransitionSpec ) { initDestination -> when (initDestination) { - is InitDestination.LocalStoragePermission -> LocalStoragePermissionScreen( - onSuccess = { - navController.popUpTo(inclusive = true) { - it == InitDestination.LocalStoragePermission - } - - if (runBlocking { vm.tryInit() }) onInitSuccess() - else navController.navigate(InitDestination.Main) - } - ) - InitDestination.Main -> MainScreen( - navController = navController + navController = navController, + onInitSuccess = onInitSuccess, ) @@ -79,20 +69,15 @@ fun InitScreen( ) }, onFinish = { path -> + val repoState = NewRepoState.DeviceStorage(path) when (initDestination.newRepoSource) { - NewRepoSource.Create -> { - vm.createRepo(path, onSuccess = onInitSuccess) - } - - NewRepoSource.Open -> { - vm.openRepo(path, onSuccess = onInitSuccess) - } - + NewRepoSource.Create -> vm.createRepo(repoState, onInitSuccess) + NewRepoSource.Open -> vm.openRepo(repoState, onInitSuccess) NewRepoSource.Clone -> { - vm.checkPathForClone(path).onSuccess { + vm.checkPathForClone(repoState.repoPath()).onSuccess { navController.navigate( - InitDestination.Remote(repoPath = path) + InitDestination.Remote(repoState) ) } } @@ -102,12 +87,13 @@ fun InitScreen( navController.popUpTo(inclusive = false) { it !is InitDestination.FileExplorer } - } + }, + title = initDestination.title ) } is InitDestination.Remote -> RemoteScreen( - repoPath = initDestination.repoPath, + repoState = initDestination.repoState, onInitSuccess = onInitSuccess, onBackClick = { navController.pop() @@ -134,17 +120,14 @@ private object InitNavTransitionSpec : NavTransitionSpec { crossFade() } - InitDestination.LocalStoragePermission -> crossFade() InitDestination.Main -> slide(backWard = true) is InitDestination.Remote -> slide() } } - InitDestination.LocalStoragePermission -> crossFade() InitDestination.Main -> { when (to) { is InitDestination.FileExplorer -> slide() - InitDestination.LocalStoragePermission -> crossFade() InitDestination.Main -> crossFade() is InitDestination.Remote -> slide() } @@ -153,7 +136,6 @@ private object InitNavTransitionSpec : NavTransitionSpec { is InitDestination.Remote -> { when (to) { is InitDestination.FileExplorer -> slide(backWard = true) - InitDestination.LocalStoragePermission -> crossFade() InitDestination.Main -> slide(backWard = true) is InitDestination.Remote -> crossFade() } diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/MainInitScreen.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/MainInitScreen.kt index fd6bc5f..6ca9930 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/MainInitScreen.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/MainInitScreen.kt @@ -1,84 +1,203 @@ package io.github.wiiznokes.gitnote.ui.screen.init +import androidx.activity.compose.rememberLauncherForActivityResult import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.foundation.layout.Spacer +import androidx.compose.foundation.layout.fillMaxWidth +import androidx.compose.foundation.layout.height import androidx.compose.material3.Button +import androidx.compose.material3.ExperimentalMaterial3Api +import androidx.compose.material3.MaterialTheme +import androidx.compose.material3.ModalBottomSheet import androidx.compose.material3.Text +import androidx.compose.material3.rememberModalBottomSheetState import androidx.compose.runtime.Composable +import androidx.compose.runtime.MutableState +import androidx.compose.runtime.mutableStateOf +import androidx.compose.runtime.remember +import androidx.compose.runtime.rememberCoroutineScope import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.res.stringResource import androidx.compose.ui.unit.dp import androidx.lifecycle.viewmodel.compose.viewModel import dev.olshevski.navigation.reimagined.NavController import dev.olshevski.navigation.reimagined.navigate +import io.github.wiiznokes.gitnote.MyApp +import io.github.wiiznokes.gitnote.R +import io.github.wiiznokes.gitnote.data.NewRepoState +import io.github.wiiznokes.gitnote.helper.StoragePermissionHelper import io.github.wiiznokes.gitnote.ui.component.AppPage import io.github.wiiznokes.gitnote.ui.destination.InitDestination import io.github.wiiznokes.gitnote.ui.destination.NewRepoSource import io.github.wiiznokes.gitnote.ui.viewmodel.InitViewModel import io.github.wiiznokes.gitnote.ui.viewmodel.viewModelFactory +import kotlinx.coroutines.launch + +private sealed class StorageChooser { + data object UnExpanded : StorageChooser() + data class Expanded(val source: NewRepoSource) : StorageChooser() +} + +@OptIn(ExperimentalMaterial3Api::class) @Composable fun MainScreen( navController: NavController, + onInitSuccess: () -> Unit ) { val vm = viewModel( factory = viewModelFactory { InitViewModel() } ) + val showStorageChooser: MutableState = + remember { mutableStateOf(StorageChooser.UnExpanded) } + AppPage( - title = "Choose method", + title = stringResource(R.string.app_page_choose_method), verticalArrangement = Arrangement.spacedBy(80.dp, Alignment.CenterVertically), horizontalAlignment = Alignment.CenterHorizontally ) { - val repoPath = vm.prefs.repoPath.getAsState() Button( onClick = { - navController.navigate( - InitDestination.FileExplorer( - title = "Choose the repo folder", - path = repoPath.value, - newRepoSource = NewRepoSource.Create, - ) - ) + showStorageChooser.value = StorageChooser.Expanded(NewRepoSource.Create) } ) { Text( - text = "Create local repository" + text = stringResource(R.string.create_repo) ) } Button( onClick = { - navController.navigate( - InitDestination.FileExplorer( - title = "Pick the repo folder", - path = repoPath.value, - newRepoSource = NewRepoSource.Open, - ) - ) + showStorageChooser.value = StorageChooser.Expanded(NewRepoSource.Open) } ) { Text( - text = "Open local repository" + text = stringResource(R.string.open_repo) ) } Button( onClick = { - navController.navigate( - InitDestination.FileExplorer( - title = "Choose the folder where the repo will be cloned", - path = repoPath.value, - newRepoSource = NewRepoSource.Clone, - ) - ) + showStorageChooser.value = StorageChooser.Expanded(NewRepoSource.Clone) } ) { Text( - text = "Clone remote repository" + text = stringResource(R.string.clone_repo) ) } } -} \ No newline at end of file + + + val sheetState = rememberModalBottomSheetState() + val scope = rememberCoroutineScope() + + val storageChooser = showStorageChooser.value + + if (storageChooser is StorageChooser.Expanded) { + ModalBottomSheet( + onDismissRequest = { + showStorageChooser.value = StorageChooser.UnExpanded + }, + sheetState = sheetState + ) { + + fun closeSheet() { + scope.launch { sheetState.hide() }.invokeOnCompletion { + if (!sheetState.isVisible) { + showStorageChooser.value = StorageChooser.UnExpanded + } + } + } + + Button( + modifier = Modifier + .fillMaxWidth(0.9F) + .align(Alignment.CenterHorizontally), + onClick = { + closeSheet() + + val repoState = NewRepoState.AppStorage + when (storageChooser.source) { + NewRepoSource.Create -> vm.createRepo(repoState, onInitSuccess) + NewRepoSource.Open -> vm.openRepo(repoState, onInitSuccess) + NewRepoSource.Clone -> navController.navigate( + InitDestination.Remote( + repoState + ) + ) + } + } + ) { + Text(text = stringResource(R.string.use_app_storage)) + } + + + Spacer(modifier = Modifier.height(60.dp)) + + Column( + modifier = Modifier + .fillMaxWidth(0.9F) + .align(Alignment.CenterHorizontally), + ) { + + val storagePermissionHelper = remember { + StoragePermissionHelper() + } + val (contract, permissionName) = storagePermissionHelper.permissionContract() + + val permissionLauncher = rememberLauncherForActivityResult(contract = contract) { + if (it) { + navController.navigate( + InitDestination.FileExplorer( + title = storageChooser.source.getExplorerTitle(), + path = vm.prefs.repoPathSafely(), + newRepoSource = storageChooser.source, + ) + ) + } else { + vm.uiHelper.makeToast(MyApp.appModule.context.getString(R.string.error_need_storage_permission)) + } + } + Button( + modifier = Modifier + .fillMaxWidth(), + onClick = { + closeSheet() + + if (!StoragePermissionHelper.isPermissionGranted()) { + permissionLauncher.launch(permissionName) + } else { + navController.navigate( + InitDestination.FileExplorer( + title = storageChooser.source.getExplorerTitle(), + path = vm.prefs.repoPathSafely(), + newRepoSource = storageChooser.source, + ) + ) + } + + } + ) { + Text(text = stringResource(R.string.use_device_storage)) + } + + Spacer(modifier = Modifier.height(8.dp)) + + Text( + text = stringResource(R.string.use_device_storage_info), + style = MaterialTheme.typography.bodySmall, + ) + } + + Spacer(modifier = Modifier.height(20.dp)) + } + + } +} diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/RemoteScreen.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/RemoteScreen.kt index 1936160..6a96ead 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/RemoteScreen.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/RemoteScreen.kt @@ -40,6 +40,7 @@ import androidx.compose.ui.Alignment import androidx.compose.ui.Modifier import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.platform.LocalUriHandler +import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.font.FontStyle import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.input.TextFieldValue @@ -48,6 +49,8 @@ import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import androidx.lifecycle.viewModelScope import androidx.lifecycle.viewmodel.compose.viewModel +import io.github.wiiznokes.gitnote.R +import io.github.wiiznokes.gitnote.data.NewRepoState import io.github.wiiznokes.gitnote.ui.component.AppPage import io.github.wiiznokes.gitnote.ui.model.GitCreed import io.github.wiiznokes.gitnote.ui.model.Provider @@ -92,103 +95,109 @@ private fun OpenLinks( provider: MutableState ) { - CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { + Column(modifier = Modifier.padding(15.dp)) { + Text( + modifier = Modifier.align(Alignment.CenterHorizontally), + text = stringResource(R.string.quick_links), + style = MaterialTheme.typography.bodyMedium + ) - Row( - modifier = Modifier - .fillMaxWidth() - .padding(15.dp), - horizontalArrangement = Arrangement.SpaceBetween, - verticalAlignment = Alignment.CenterVertically - ) { - - var providerExpanded by remember { - mutableStateOf(false) - } + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Rtl) { - CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) { + Row( + modifier = Modifier + .fillMaxWidth(), + horizontalArrangement = Arrangement.SpaceBetween, + verticalAlignment = Alignment.CenterVertically + ) { + var providerExpanded by remember { + mutableStateOf(false) + } - Box { + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) { - Button( - modifier = Modifier.padding(start = 7.dp), - onClick = { providerExpanded = !providerExpanded }, - colors = ButtonDefaults.buttonColors( - containerColor = MaterialTheme.colorScheme.secondary, - contentColor = MaterialTheme.colorScheme.onSecondary - ) - ) { - Text( - text = provider.value.name - ) - } + Box { + Button( + modifier = Modifier.padding(start = 7.dp), + onClick = { providerExpanded = !providerExpanded }, + colors = ButtonDefaults.buttonColors( + containerColor = MaterialTheme.colorScheme.secondary, + contentColor = MaterialTheme.colorScheme.onSecondary + ) + ) { + Text( + text = provider.value.name + ) + } - DropdownMenu( - expanded = providerExpanded, - onDismissRequest = { providerExpanded = false } - ) { - Provider.entries.forEach { - DropdownMenuItem( - text = { - Text(text = it.name) - }, - onClick = { - provider.value = it - vm.viewModelScope.launch { - vm.prefs.provider.update(it) + DropdownMenu( + expanded = providerExpanded, + onDismissRequest = { providerExpanded = false } + ) { + + Provider.entries.forEach { + DropdownMenuItem( + text = { + Text(text = it.name) + }, + onClick = { + provider.value = it + vm.viewModelScope.launch { + vm.prefs.provider.update(it) + } } - } - ) + ) + } } } } - } - CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) { - Row( - modifier = Modifier - .horizontalScroll(rememberScrollState()), - verticalAlignment = Alignment.CenterVertically, - horizontalArrangement = Arrangement.spacedBy(5.dp) - ) { + CompositionLocalProvider(LocalLayoutDirection provides LayoutDirection.Ltr) { + Row( + modifier = Modifier + .horizontalScroll(rememberScrollState()), + verticalAlignment = Alignment.CenterVertically, + horizontalArrangement = Arrangement.spacedBy(5.dp) + ) { - provider.value.mainPage?.let { - OpenLinkButton( - text = "Main page", - url = it - ) - } + provider.value.mainPage?.let { + OpenLinkButton( + text = stringResource(R.string.quick_links_home_page), + url = it + ) + } - provider.value.createRepo?.let { - OpenLinkButton( - text = "Create repo", - url = it - ) - } + provider.value.createRepo?.let { + OpenLinkButton( + text = stringResource(R.string.quick_links_create_repo), + url = it + ) + } - provider.value.createToken?.let { - OpenLinkButton( - text = "Create Token", - url = it - ) - } + provider.value.createToken?.let { + OpenLinkButton( + text = stringResource(R.string.quick_links_create_token), + url = it + ) + } - provider.value.checkOutRepo?.let { - OpenLinkButton( - text = "See repos", - url = it - ) + provider.value.checkOutRepo?.let { + OpenLinkButton( + text = stringResource(R.string.quick_links_see_repos), + url = it + ) + } } - } - } + } + } } } } @@ -223,7 +232,7 @@ private fun ElevatedCard( @Composable fun RemoteScreen( - repoPath: String, + repoState: NewRepoState, onInitSuccess: () -> Unit, onBackClick: () -> Unit ) { @@ -231,7 +240,7 @@ fun RemoteScreen( val vm: InitViewModel = viewModel() AppPage( - title = "Clone repository", + title = stringResource(R.string.app_page_clone_repository), horizontalAlignment = Alignment.CenterHorizontally, onBackClick = onBackClick, ) { @@ -251,12 +260,12 @@ fun RemoteScreen( .padding(vertical = 50.dp) ) { Text( - text = "Path where the repo will be cloned", + text = stringResource(R.string.where_repo_is_cloned), style = MaterialTheme.typography.bodyLarge ) Text( - text = repoPath, + text = repoState.repoPath(), maxLines = 1, overflow = TextOverflow.Ellipsis, style = MaterialTheme.typography.bodyMedium.copy( @@ -271,7 +280,7 @@ fun RemoteScreen( } InitGroupExplication( - explication = "1. Enter the Git clone URL (Https)" + explication = stringResource(R.string.clone_step_url) ) { OutlinedTextField( value = repoUrl, @@ -279,13 +288,13 @@ fun RemoteScreen( repoUrl = it }, label = { - Text(text = "clone URL") - }, - placeholder = { - Text(text = "https://github.com/wiiznokes/gitnote.git") + Text(text = stringResource(R.string.clone_step_url_label)) }, singleLine = true, - isError = repoUrl.text.contains(" ") + isError = repoUrl.text.contains(" ") || repoUrl.text.isEmpty(), + keyboardOptions = KeyboardOptions( + keyboardType = KeyboardType.Uri + ) ) } @@ -303,7 +312,7 @@ fun RemoteScreen( horizontalArrangement = Arrangement.SpaceBetween, verticalAlignment = Alignment.CenterVertically, ) { - Text(text = "With credentials") + Text(text = stringResource(R.string.with_credentials)) IconButton( onClick = { withCreed = !withCreed } ) { @@ -337,7 +346,7 @@ fun RemoteScreen( AnimatedVisibility(visible = withCreed) { Column { InitGroupExplication( - explication = "2. Username" + explication = stringResource(R.string.clone_step_username) ) { OutlinedTextField( value = userName, @@ -345,18 +354,16 @@ fun RemoteScreen( userName = it }, label = { - Text(text = "Username") - }, - placeholder = { - Text(text = "MyUserName") + Text(text = stringResource(R.string.clone_step_username_label)) }, singleLine = true, + isError = userName.text.isEmpty() ) } InitGroupExplication( - explication = "3. Password" + explication = stringResource(R.string.clone_step_password) ) { OutlinedTextField( @@ -365,10 +372,11 @@ fun RemoteScreen( password = it }, label = { - Text("Password") + Text(stringResource(R.string.clone_step_password_label)) }, keyboardOptions = KeyboardOptions(keyboardType = KeyboardType.Password), singleLine = true, + isError = userName.text.isEmpty() ) } } @@ -390,7 +398,7 @@ fun RemoteScreen( .padding(), onClick = { vm.cloneRepo( - repoPath = repoPath, + repoState = repoState, repoUrl = repoUrl.text, gitCreed = if (withCreed) GitCreed( userName = userName.text, @@ -401,7 +409,7 @@ fun RemoteScreen( }, enabled = cloneState.isClickable() ) { - Text(text = "Clone repo") + Text(text = stringResource(R.string.clone_repo_button)) Icon(imageVector = Icons.Default.Download, contentDescription = null) } } diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/StorageScreen.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/StorageScreen.kt deleted file mode 100644 index e7736cd..0000000 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/init/StorageScreen.kt +++ /dev/null @@ -1,47 +0,0 @@ -package io.github.wiiznokes.gitnote.ui.screen.init - -import androidx.activity.compose.rememberLauncherForActivityResult -import androidx.compose.foundation.layout.Box -import androidx.compose.foundation.layout.fillMaxSize -import androidx.compose.foundation.layout.padding -import androidx.compose.material3.Scaffold -import androidx.compose.runtime.Composable -import androidx.compose.runtime.remember -import androidx.compose.ui.Modifier -import io.github.wiiznokes.gitnote.helper.StoragePermissionHelper -import io.github.wiiznokes.gitnote.ui.component.CenteredButton - - -@Composable -fun LocalStoragePermissionScreen( - onSuccess: () -> Unit -) { - - val storagePermissionHelper = remember { - StoragePermissionHelper() - } - - Scaffold { paddingValues -> - Box( - modifier = Modifier - .padding(paddingValues) - .fillMaxSize() - - ) { - - val (contract, permissionName) = storagePermissionHelper.permissionContract() - - val permissionLauncher = rememberLauncherForActivityResult(contract = contract) { - if (it) { - onSuccess() - } - } - CenteredButton( - onClick = { - permissionLauncher.launch(permissionName) - }, - text = "request storage permission" - ) - } - } -} diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/settings/Settings.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/settings/Settings.kt index 5b37555..0cd1a8c 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/settings/Settings.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/screen/settings/Settings.kt @@ -7,8 +7,6 @@ import androidx.compose.foundation.layout.ColumnScope import androidx.compose.foundation.layout.Row import androidx.compose.foundation.layout.fillMaxWidth import androidx.compose.foundation.layout.padding -import androidx.compose.material.icons.Icons -import androidx.compose.material.icons.filled.Edit import androidx.compose.material3.Button import androidx.compose.material3.HorizontalDivider import androidx.compose.material3.MaterialTheme @@ -25,14 +23,12 @@ import androidx.compose.ui.platform.LocalLayoutDirection import androidx.compose.ui.res.stringResource import androidx.compose.ui.text.input.KeyboardType import androidx.compose.ui.text.style.TextOverflow -import androidx.compose.ui.tooling.preview.Preview import androidx.compose.ui.unit.LayoutDirection import androidx.compose.ui.unit.dp import io.github.wiiznokes.gitnote.R import io.github.wiiznokes.gitnote.ui.component.BaseDialog import io.github.wiiznokes.gitnote.ui.component.GetStringDialog import io.github.wiiznokes.gitnote.ui.component.SimpleIcon -import io.github.wiiznokes.gitnote.ui.theme.GitNoteTheme private val padding = 12.dp diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/EditViewModel.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/EditViewModel.kt index ed6c329..2235278 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/EditViewModel.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/EditViewModel.kt @@ -154,7 +154,7 @@ class EditViewModel() : ViewModel() { val relativePath = "$parentPath/$name.${fileExtension.text}" - prefs.repoPath.getBlocking().let { rootPath -> + prefs.repoPathBlocking().let { rootPath -> val previousFile = NodeFs.File.fromPath(rootPath, previousNote.relativePath) if (!previousFile.exist()) { @@ -216,7 +216,7 @@ class EditViewModel() : ViewModel() { val relativePath = "$parentPath/$name.${fileExtension.text}" - prefs.repoPath.getBlocking().let { rootPath -> + prefs.repoPathBlocking().let { rootPath -> if (NodeFs.File.fromPath(rootPath, relativePath).exist()) { uiHelper.makeToast("New note already exist") return failure(EditException(EditExceptionType.NoteAlreadyExist)) diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/GridViewModel.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/GridViewModel.kt index 553f900..5c59b6f 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/GridViewModel.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/GridViewModel.kt @@ -129,7 +129,7 @@ class GridViewModel : ViewModel() { val relativePath = "$relativeParentPath/$name" - prefs.repoPath.getBlocking().let { rootPath -> + prefs.repoPathBlocking().let { rootPath -> if (NodeFs.Folder.fromPath(rootPath, relativePath).exist()) { uiHelper.makeToast(uiHelper.getString(R.string.error_folder_already_exist)) return false diff --git a/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/InitViewModel.kt b/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/InitViewModel.kt index ebee3af..db0a6ce 100644 --- a/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/InitViewModel.kt +++ b/app/src/main/java/io/github/wiiznokes/gitnote/ui/viewmodel/InitViewModel.kt @@ -3,8 +3,12 @@ package io.github.wiiznokes.gitnote.ui.viewmodel import androidx.lifecycle.ViewModel import io.github.wiiznokes.gitnote.MyApp +import io.github.wiiznokes.gitnote.R import io.github.wiiznokes.gitnote.data.AppPreferences +import io.github.wiiznokes.gitnote.data.NewRepoState +import io.github.wiiznokes.gitnote.data.RepoState import io.github.wiiznokes.gitnote.data.platform.NodeFs +import io.github.wiiznokes.gitnote.helper.StoragePermissionHelper import io.github.wiiznokes.gitnote.helper.UiHelper import io.github.wiiznokes.gitnote.manager.GitException import io.github.wiiznokes.gitnote.manager.GitExceptionType @@ -22,7 +26,7 @@ class InitViewModel : ViewModel() { val prefs: AppPreferences = MyApp.appModule.appPreferences private val gitManager = MyApp.appModule.gitManager - private val uiHelper: UiHelper = MyApp.appModule.uiHelper + val uiHelper: UiHelper = MyApp.appModule.uiHelper private val storageManager = MyApp.appModule.storageManager @@ -30,22 +34,32 @@ class InitViewModel : ViewModel() { private const val TAG = "InitViewModel" } + private fun prepareLocalStorageRepoPath() { + val folder = NodeFs.Folder.fromPath(AppPreferences.appStorageRepoPath) + folder.delete() + folder.create() + } - fun createRepo(repoPath: String, onSuccess: () -> Unit) { + fun createRepo(repoState: NewRepoState, onSuccess: () -> Unit) { CoroutineScope(Dispatchers.IO).launch { - NodeFs.Folder.fromPath(repoPath).isEmptyDirectory().onFailure { + + if (repoState is NewRepoState.AppStorage) { + prepareLocalStorageRepoPath() + } + + NodeFs.Folder.fromPath(repoState.repoPath()).isEmptyDirectory().onFailure { uiHelper.makeToast(it.message) return@launch } - gitManager.createRepo(repoPath).onFailure { + gitManager.createRepo(repoState.repoPath()).onFailure { uiHelper.makeToast(it.message) return@launch } - prefs.initRepo(repoPath) + prefs.initRepo(repoState) CoroutineScope(Dispatchers.IO).launch { storageManager.updateDatabase() @@ -56,26 +70,20 @@ class InitViewModel : ViewModel() { } - fun checkPathForClone(repoPath: String): Result { - val result = NodeFs.Folder.fromPath(repoPath).isEmptyDirectory() - result.onFailure { - uiHelper.makeToast(it.message) - } - return result - } - - private suspend fun openRepoSuspend(repoPath: String): Result { + private suspend fun openRepoSuspend(repoState: NewRepoState): Result { - if (!NodeFs.Folder.fromPath(repoPath).exist()) { - uiHelper.makeToast("Path is not a directory") - return failure(GitException(GitExceptionType.WrongPath)) + if (!NodeFs.Folder.fromPath(repoState.repoPath()).exist()) { + val msg = uiHelper.getString(R.string.error_path_not_directory) + uiHelper.makeToast(msg) + return failure(Exception(msg)) } - gitManager.openRepo(repoPath).onFailure { + gitManager.openRepo(repoState.repoPath()).onFailure { uiHelper.makeToast(it.message) return failure(it) } + prefs.initRepo(repoState) // yes, there can be pending file not committed // but they will be committed in the updateDatabaseAndRepo function @@ -87,11 +95,10 @@ class InitViewModel : ViewModel() { return success(Unit) } - fun openRepo(repoPath: String, onSuccess: () -> Unit) { + fun openRepo(repoState: NewRepoState, onSuccess: () -> Unit) { CoroutineScope(Dispatchers.IO).launch { - openRepoSuspend(repoPath).onSuccess { - prefs.initRepo(repoPath) + openRepoSuspend(repoState).onSuccess { onSuccess() } } @@ -99,24 +106,35 @@ class InitViewModel : ViewModel() { } + fun checkPathForClone(repoPath: String): Result { + val result = NodeFs.Folder.fromPath(repoPath).isEmptyDirectory() + result.onFailure { + uiHelper.makeToast(it.message) + } + return result + } + private val _cloneState: MutableStateFlow = MutableStateFlow(CloneState.Idle) val cloneState: StateFlow get() = _cloneState.asStateFlow() fun cloneRepo( - repoPath: String, + repoState: NewRepoState, repoUrl: String, gitCreed: GitCreed? = null, onSuccess: () -> Unit ) { CoroutineScope(Dispatchers.IO).launch { + if (repoState is NewRepoState.AppStorage) { + prepareLocalStorageRepoPath() + } _cloneState.emit(CloneState.Cloning(0)) gitManager.cloneRepo( - repoPath = repoPath, + repoPath = repoState.repoPath(), repoUrl = repoUrl, creed = gitCreed, progressCallback = { @@ -131,7 +149,7 @@ class InitViewModel : ViewModel() { _cloneState.emit(CloneState.Cloned) - prefs.initRepo(repoPath) + prefs.initRepo(repoState) prefs.remoteUrl.update(repoUrl) gitCreed?.let { @@ -149,16 +167,32 @@ class InitViewModel : ViewModel() { suspend fun tryInit(): Boolean { - if (!prefs.isRepoInitialize.get()) return false - val repoPath = prefs.repoPath.get() + val repoState = when (prefs.repoState.get()) { + RepoState.NoRepo -> return false + RepoState.AppStorage -> { + NewRepoState.AppStorage + } + + RepoState.DeviceStorage -> { + if (!StoragePermissionHelper.isPermissionGranted()) { + return false + } + val repoPath = try { + prefs.repoPath() + } catch (e: Exception) { + return false + } + NewRepoState.DeviceStorage(repoPath) + } + } + - openRepoSuspend(repoPath).onFailure { + openRepoSuspend(repoState).onFailure { CoroutineScope(Dispatchers.IO).launch { storageManager.closeRepo() } return false } - return true } diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 24d341c..93cdb3d 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -78,5 +78,33 @@ Impossible de créer le dépôt: %1$s Grille À propos + Choisir la méthode + Créer un dépôt local + Ouvrir un dépôt local + Cloner un dépôt distant + Utiliser la mémoire de l\'app + L\'application a besoin de cette permission pour accéder au stockage + Utiliser la mémoire de l\'appareil + Requiert une autorisation d\'accès au stockage complet de l\'appareil. Vous aurez la possibilité d\'accéder au dépôt sur votre ordinateur par la suite. + Créer un dépôt dans ce dossier + Ouvrir ce dépôt + Cloner le dépôt dans ce dossier + Liens rapides + Page d\'accueil + Créer dépôt + Créer token + Voir les référentiel + Cloner le référentiel + Chemin où le référentiel sera cloné + 1. Entrez l\'url du référentiel (https) + url + Avec identifiants + 2. Nom d\'utilisateur + Nom d\'utilisateur + 3. Mot de passe (dev. token) + Mot de passe + Cloner le référentiel + Le chemin n\'est pas un répertoire + Le dossier n\'est pas vide \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index f59c64c..285488a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -65,5 +65,33 @@ Folder already exist Grid About + Choose method + Create local repository + Open local repository + Clone remote repository + Use app storage + The application needs this permission to access the storage + Use device storage + Requires permission to access the full device storage. You will be able to access the repository on your computer afterwards. + Create repository in this folder + Open this repository + Clone repository in this folder + Quick links + Home page + Create repo + Create Token + See repos + Clone repository + Path where the repo will be cloned + 1. Enter the repository url (https) + url + With credentials + 2. Username + Username + 3. Password (dev. token) + Password + clone repository + The path is not a directory + The folder is not empty \ No newline at end of file