diff --git a/README.md b/README.md index b958313..189b92c 100644 --- a/README.md +++ b/README.md @@ -63,11 +63,10 @@ Shiori is built using a variety of modern and robust technologies to ensure scal - **Protobuf (Proto)**: For efficient data serialization. - ## Development Status ⚠️ -Please note that Shiori is currently under active development. While we strive to provide a stable experience, you may encounter bugs or incomplete features. We encourage users to: +Please note that Shiori is currently under development. While we strive to provide a stable experience, you may encounter bugs or incomplete features. We encourage users to: - Report any issues you find on our [GitHub Issues page](https://github.com/DesarrolloAntonio/Shiori-Android-Client/issues) - Be aware that some features might be unstable or work in progress - Expect regular updates as we continue to improve the application -- Back up your data regularly during this development phase ## Download diff --git a/data/src/main/java/com/desarrollodroide/data/extensions/BookmarkExtensions.kt b/data/src/main/java/com/desarrollodroide/data/extensions/BookmarkExtensions.kt index 10afea1..1e7f4a1 100644 --- a/data/src/main/java/com/desarrollodroide/data/extensions/BookmarkExtensions.kt +++ b/data/src/main/java/com/desarrollodroide/data/extensions/BookmarkExtensions.kt @@ -4,7 +4,21 @@ import com.google.gson.GsonBuilder import com.desarrollodroide.data.helpers.TagTypeAdapter import com.desarrollodroide.model.Bookmark import com.desarrollodroide.model.Tag +import com.google.gson.ExclusionStrategy +import com.google.gson.FieldAttributes -fun Bookmark.toBodyJson() = GsonBuilder() +/** + * Converts Bookmark to JSON, omitting the imageURL field when empty + * to prevent empty image generation processing in the backend. + * + * @return String containing the JSON representation of the Bookmark + */ +fun Bookmark.toBodyJson() = GsonBuilder() .registerTypeAdapter(Tag::class.java, TagTypeAdapter()) - .create().toJson(this) \ No newline at end of file + .setExclusionStrategies(object : ExclusionStrategy { + override fun shouldSkipField(f: FieldAttributes) = + f.name == "imageURL" && this@toBodyJson.imageURL.isEmpty() + override fun shouldSkipClass(clazz: Class<*>?) = false + }) + .create() + .toJson(this) \ No newline at end of file diff --git a/gradle.properties b/gradle.properties index 68e1076..8f90c0f 100644 --- a/gradle.properties +++ b/gradle.properties @@ -25,8 +25,8 @@ android.nonTransitiveRClass=true compileSdkVersion=34 minSdkVersion=26 targetSdkVersion=34 -versionCode=47 -versionName=1.50.01 +versionCode=48 +versionName=1.50.10 android.defaults.buildfeatures.buildconfig=true android.nonFinalResIds=false diff --git a/presentation/src/main/java/com/desarrollodroide/pagekeeper/MainActivity.kt b/presentation/src/main/java/com/desarrollodroide/pagekeeper/MainActivity.kt index e6fda39..65b0cf7 100644 --- a/presentation/src/main/java/com/desarrollodroide/pagekeeper/MainActivity.kt +++ b/presentation/src/main/java/com/desarrollodroide/pagekeeper/MainActivity.kt @@ -19,6 +19,7 @@ import com.desarrollodroide.pagekeeper.navigation.Navigation import org.koin.android.ext.android.inject import com.desarrollodroide.pagekeeper.extensions.shareEpubFile import com.desarrollodroide.pagekeeper.extensions.shareText +import com.desarrollodroide.pagekeeper.ui.bookmarkeditor.BookmarkEditorActivity import java.util.Locale class MainActivity : ComponentActivity() { @@ -49,6 +50,9 @@ class MainActivity : ComponentActivity() { }, shareText = { shareText(it) + }, + onAddManuallyClick = { + startActivity(BookmarkEditorActivity.createManualIntent(this)) } ) } @@ -59,7 +63,7 @@ class MainActivity : ComponentActivity() { override fun onResume() { super.onResume() Log.v("MainActivity", "onResume") - // TODO: sync + // TODO: sync when endpoint is available } } diff --git a/presentation/src/main/java/com/desarrollodroide/pagekeeper/navigation/Navigation.kt b/presentation/src/main/java/com/desarrollodroide/pagekeeper/navigation/Navigation.kt index 76d7152..253a254 100644 --- a/presentation/src/main/java/com/desarrollodroide/pagekeeper/navigation/Navigation.kt +++ b/presentation/src/main/java/com/desarrollodroide/pagekeeper/navigation/Navigation.kt @@ -22,6 +22,7 @@ import java.io.File fun Navigation( onFinish: () -> Unit, openUrlInBrowser: (String) -> Unit, + onAddManuallyClick: () -> Unit, shareEpubFile: (File) -> Unit, shareText: (String) -> Unit ) { @@ -56,7 +57,8 @@ fun Navigation( onFinish = onFinish, openUrlInBrowser = openUrlInBrowser, shareEpubFile = shareEpubFile, - shareText = shareText + shareText = shareText, + onAddManuallyClick = onAddManuallyClick ) } } diff --git a/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/bookmarkeditor/BookmarkEditorActivity.kt b/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/bookmarkeditor/BookmarkEditorActivity.kt index 8fc0de4..1b6497a 100644 --- a/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/bookmarkeditor/BookmarkEditorActivity.kt +++ b/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/bookmarkeditor/BookmarkEditorActivity.kt @@ -1,5 +1,6 @@ package com.desarrollodroide.pagekeeper.ui.bookmarkeditor +import android.content.Context import android.content.Intent import android.os.Bundle import android.util.Log @@ -24,8 +25,25 @@ class BookmarkEditorActivity : ComponentActivity() { private val bookmarkViewModel: BookmarkViewModel by viewModel() + companion object { + const val EXTRA_MODE = "extra_mode" + + fun createManualIntent(context: Context): Intent { + return Intent(context, BookmarkEditorActivity::class.java).apply { + putExtra(EXTRA_MODE, BookmarkEditorType.ADD_MANUALLY.name) + } + } + } + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) + + val mode = intent.getStringExtra(EXTRA_MODE) + if (mode == BookmarkEditorType.ADD_MANUALLY.name) { + setupBookmarkEditor(BookmarkEditorType.ADD_MANUALLY, "", "") + return + } + var sharedUrl = "" var title = "" intent?.let { intent -> @@ -43,6 +61,7 @@ class BookmarkEditorActivity : ComponentActivity() { finish() } } + lifecycleScope.launch { bookmarkViewModel.toastMessage.collect { message -> message?.let { @@ -51,50 +70,18 @@ class BookmarkEditorActivity : ComponentActivity() { } } } + lifecycleScope.launch { if (sharedUrl.isNotEmpty()) { if (bookmarkViewModel.userHasSession()) { if (bookmarkViewModel.autoAddBookmark) { - // Auto-add bookmark without showing the editor screen bookmarkViewModel.autoAddBookmark(sharedUrl, title) Toast.makeText(this@BookmarkEditorActivity, "Bookmark saved", Toast.LENGTH_LONG).show() finish() } else { - // Show the bookmark editor screen - setContent { - ShioriTheme { - Surface( - modifier = Modifier - .fillMaxSize() - .background(MaterialTheme.colorScheme.inverseOnSurface) - ) { - val makeArchivePublic = bookmarkViewModel.makeArchivePublic - val createEbook = bookmarkViewModel.createEbook - val createArchive = bookmarkViewModel.createArchive - BookmarkEditorScreen( - title = "Add", - bookmarkEditorType = BookmarkEditorType.ADD, - bookmark = Bookmark( - url = sharedUrl, - title = title, - tags = emptyList(), - public = if (makeArchivePublic) 1 else 0, - createArchive = createArchive, - createEbook = createEbook - ), - onBack = { finish() }, - updateBookmark = { finish() }, - showToast = { message -> - Toast.makeText(this@BookmarkEditorActivity, message, Toast.LENGTH_LONG).show() - }, - startMainActivity = { startMainActivity() } - ) - } - } - } + setupBookmarkEditor(BookmarkEditorType.ADD, sharedUrl, title) } } else { - // User doesn't have a session, show login screen setContent { ShioriTheme { NotSessionScreen( @@ -106,13 +93,46 @@ class BookmarkEditorActivity : ComponentActivity() { } } } else { - // No shared URL, finish the activity Toast.makeText(this@BookmarkEditorActivity, "No shared URL found", Toast.LENGTH_LONG).show() finish() } } } + private fun setupBookmarkEditor(type: BookmarkEditorType, url: String, title: String) { + setContent { + ShioriTheme { + Surface( + modifier = Modifier + .fillMaxSize() + .background(MaterialTheme.colorScheme.inverseOnSurface) + ) { + val makeArchivePublic = bookmarkViewModel.makeArchivePublic + val createEbook = bookmarkViewModel.createEbook + val createArchive = bookmarkViewModel.createArchive + BookmarkEditorScreen( + title = if (type == BookmarkEditorType.ADD_MANUALLY) "Add Manually" else "Add", + bookmarkEditorType = type, + bookmark = Bookmark( + url = url, + title = title, + tags = emptyList(), + public = if (makeArchivePublic) 1 else 0, + createArchive = createArchive, + createEbook = createEbook + ), + onBack = { finish() }, + updateBookmark = { finish() }, + showToast = { message -> + Toast.makeText(this@BookmarkEditorActivity, message, Toast.LENGTH_LONG).show() + }, + startMainActivity = { startMainActivity() } + ) + } + } + } + } + private fun startMainActivity() { val intent = Intent(this, MainActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK diff --git a/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/bookmarkeditor/BookmarkEditorScreen.kt b/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/bookmarkeditor/BookmarkEditorScreen.kt index fcedd3d..7b0ef05 100644 --- a/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/bookmarkeditor/BookmarkEditorScreen.kt +++ b/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/bookmarkeditor/BookmarkEditorScreen.kt @@ -31,6 +31,7 @@ fun BookmarkEditorScreen( val newTag = remember { mutableStateOf("") } val availableTags = bookmarkViewModel.availableTags.collectAsState() val bookmarkUiState = bookmarkViewModel.bookmarkUiState.collectAsState().value + var currentUrl by remember { mutableStateOf(bookmark.url) } // No need to update values in settings var localMakeArchivePublic by remember { mutableStateOf(bookmarkViewModel.makeArchivePublic) } @@ -66,15 +67,16 @@ fun BookmarkEditorScreen( BookmarkEditorView( title = title, + url = currentUrl, bookmarkEditorType = bookmarkEditorType, newTag = newTag, assignedTags = assignedTags, availableTags = availableTags, saveBookmark = { when (bookmarkEditorType) { - BookmarkEditorType.ADD -> { + BookmarkEditorType.ADD, BookmarkEditorType.ADD_MANUALLY -> { bookmarkViewModel.saveBookmark( - url = bookmark.url, + url = currentUrl, title = bookmark.title, tags = assignedTags.value, createArchive = localCreateArchive, @@ -100,9 +102,9 @@ fun BookmarkEditorScreen( makeArchivePublic = localMakeArchivePublic, onMakeArchivePublicChanged = { localMakeArchivePublic = it }, createEbook = localCreateEbook, - url = bookmark.url, onCreateEbookChanged = { localCreateEbook = it }, - onCreateArchiveChanged = { localCreateArchive = it } + onCreateArchiveChanged = { localCreateArchive = it }, + onUrlChange = { currentUrl = it } ) } diff --git a/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/bookmarkeditor/BookmarkEditorView.kt b/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/bookmarkeditor/BookmarkEditorView.kt index fb224bd..4f8d645 100644 --- a/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/bookmarkeditor/BookmarkEditorView.kt +++ b/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/bookmarkeditor/BookmarkEditorView.kt @@ -47,7 +47,7 @@ import com.desarrollodroide.pagekeeper.ui.components.Categories import com.desarrollodroide.pagekeeper.ui.components.CategoriesType import com.desarrollodroide.model.Tag -enum class BookmarkEditorType { ADD, EDIT } +enum class BookmarkEditorType { ADD, ADD_MANUALLY, EDIT } @Composable fun BookmarkEditorView( title: String, @@ -64,6 +64,7 @@ fun BookmarkEditorView( onMakeArchivePublicChanged: (Boolean) -> Unit, createEbook: Boolean, onCreateEbookChanged: (Boolean) -> Unit, + onUrlChange: (String) -> Unit = {} ) { Column( modifier = Modifier @@ -92,16 +93,28 @@ fun BookmarkEditorView( Icon(Icons.Outlined.Save, contentDescription = "Save") } } - Text( - text = url, - color = MaterialTheme.colorScheme.onSurface, - modifier = Modifier - .fillMaxWidth() - .background(MaterialTheme.colorScheme.surfaceVariant) - .padding(8.dp), - maxLines = 3, - overflow = TextOverflow.Ellipsis // - ) + if (bookmarkEditorType == BookmarkEditorType.ADD_MANUALLY) { + OutlinedTextField( + value = url, + onValueChange = onUrlChange, + label = { Text("URL") }, + modifier = Modifier + .fillMaxWidth() + .padding(vertical = 8.dp), + singleLine = true + ) + } else { + Text( + text = url, + color = MaterialTheme.colorScheme.onSurface, + modifier = Modifier + .fillMaxWidth() + .background(MaterialTheme.colorScheme.surfaceVariant) + .padding(8.dp), + maxLines = 3, + overflow = TextOverflow.Ellipsis + ) + } if (bookmarkEditorType == BookmarkEditorType.ADD) { Row(verticalAlignment = CenterVertically) { diff --git a/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/feed/FeedViewModel.kt b/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/feed/FeedViewModel.kt index bfb8fed..d2b2d33 100644 --- a/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/feed/FeedViewModel.kt +++ b/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/feed/FeedViewModel.kt @@ -270,6 +270,7 @@ class FeedViewModel( fun refreshFeed() { viewModelScope.launch { val localBookmarkIds = bookmarkDatabase.getAllBookmarkIds() + // TODO sync disabled until endpoint finished syncBookmarks(localBookmarkIds, settingsPreferenceDataSource.getLastSyncTimestamp()) // TODO remove with sync is completed in backend retrieveAllRemoteBookmarks() diff --git a/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/home/HomeScreen.kt b/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/home/HomeScreen.kt index da58012..12f5168 100644 --- a/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/home/HomeScreen.kt +++ b/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/home/HomeScreen.kt @@ -28,6 +28,7 @@ import org.koin.androidx.compose.get import androidx.compose.runtime.* import androidx.compose.material3.Text import androidx.compose.material.icons.Icons +import androidx.compose.material.icons.filled.Add import androidx.compose.material.icons.filled.Search import androidx.compose.material.icons.filled.Settings import androidx.compose.material.icons.filled.Sync @@ -86,6 +87,7 @@ fun HomeScreen( goToLogin: () -> Unit, onFinish: () -> Unit, openUrlInBrowser: (String) -> Unit, + onAddManuallyClick: () -> Unit, shareEpubFile: (File) -> Unit, shareText: (String) -> Unit ) { @@ -146,7 +148,8 @@ fun HomeScreen( bottomSheetState.show() } }, - pendingJobs = pendingJobs + pendingJobs = pendingJobs, + onAddManuallyClick = onAddManuallyClick, ) } } @@ -262,6 +265,7 @@ fun HomeScreen( fun TopBar( toggleCategoryVisibility: () -> Unit, toggleSearchBarVisibility: () -> Unit, + onAddManuallyClick: () -> Unit, onSettingsClick: () -> Unit, onSyncButtonClick: () -> Unit, scrollBehavior: TopAppBarScrollBehavior, @@ -309,6 +313,13 @@ fun TopBar( ) }, actions = { + IconButton(onClick = onAddManuallyClick) { + Icon( + imageVector = Icons.Default.Add, + contentDescription = "Add Manually", + tint = MaterialTheme.colorScheme.secondary, + ) + } IconButton(onClick = { toggleSearchBarVisibility() }) { Icon( imageVector = Icons.Filled.Search, @@ -507,6 +518,7 @@ fun TopBarPreview() { toggleCategoryVisibility = { }, toggleSearchBarVisibility = { }, onSettingsClick = { }, + onAddManuallyClick = { }, scrollBehavior = TopAppBarDefaults.pinnedScrollBehavior(), hasBookmarks = true, selectedTagsCount = 2, diff --git a/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/settings/AccountSection.kt b/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/settings/AccountSection.kt index 4ba5b74..50218dd 100644 --- a/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/settings/AccountSection.kt +++ b/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/settings/AccountSection.kt @@ -20,6 +20,7 @@ import androidx.compose.ui.unit.dp @Composable fun AccountSection( + serverUrl: String, onLogout: () -> Unit, onNavigateToTermsOfUse: () -> Unit, onNavigateToPrivacyPolicy: () -> Unit, @@ -40,7 +41,8 @@ fun AccountSection( Spacer(modifier = Modifier.height(5.dp)) ClickableOption( Item( - "Logout", + title = "Logout", + subtitle = serverUrl, icon = Icons.AutoMirrored.Filled.Logout, onClick = onLogout ), diff --git a/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/settings/SettingsScreen.kt b/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/settings/SettingsScreen.kt index bded973..8e6c3c0 100644 --- a/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/settings/SettingsScreen.kt +++ b/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/settings/SettingsScreen.kt @@ -137,7 +137,8 @@ fun SettingsScreen( hideTag = tagToHide, cacheSize = settingsViewModel.cacheSize, onClearCache = settingsViewModel::clearImageCache, - serverVersion = settingsViewModel.serverVersion + serverVersion = settingsViewModel.getServerVersion(), + serverUrl = settingsViewModel.getServerUrl() ) } } @@ -172,6 +173,7 @@ fun SettingsContent( cacheSize: StateFlow, onClearCache: () -> Unit, serverVersion: String, + serverUrl: String, ) { val context = LocalContext.current if (settingsUiState.isLoading) { @@ -244,6 +246,7 @@ fun SettingsContent( HorizontalDivider() Spacer(modifier = Modifier.height(18.dp)) AccountSection( + serverUrl = serverUrl, onLogout = onLogout, onNavigateToTermsOfUse = onNavigateToTermsOfUse, onNavigateToPrivacyPolicy = onNavigateToPrivacyPolicy, @@ -333,7 +336,9 @@ fun SettingsScreenPreview() { createEbook = false, onCreateEbookChanged = {}, createArchive = false, + onCreateArchiveChanged = {}, autoAddBookmark = false, + onAutoAddBookmarkChanged = { }, compactView = false, onCompactViewChanged = {}, onLogout = {}, @@ -351,8 +356,7 @@ fun SettingsScreenPreview() { hideTag = null, cacheSize = MutableStateFlow("Calculating..."), onClearCache = {}, - onAutoAddBookmarkChanged = { }, - onCreateArchiveChanged = {}, - serverVersion = "1.0.0" + serverVersion = "1.0.0", + serverUrl = "192.168.1.66:8888" ) } \ No newline at end of file diff --git a/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/settings/SettingsViewModel.kt b/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/settings/SettingsViewModel.kt index 924aa01..47e2262 100644 --- a/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/settings/SettingsViewModel.kt +++ b/presentation/src/main/java/com/desarrollodroide/pagekeeper/ui/settings/SettingsViewModel.kt @@ -46,8 +46,9 @@ class SettingsViewModel( val useDynamicColors = MutableStateFlow(false) val themeMode = MutableStateFlow(ThemeMode.AUTO) - private var token = "" - var serverVersion = "" + private var _token = "" + private var _serverVersion = "" + private var _serverUrl: String = "" val compactView: StateFlow = settingsPreferenceDataSource.compactViewFlow .stateIn(viewModelScope, SharingStarted.Eagerly, false) @@ -134,8 +135,9 @@ class SettingsViewModel( viewModelScope.launch { useDynamicColors.value = settingsPreferenceDataSource.getUseDynamicColors() themeMode.value = settingsPreferenceDataSource.getThemeMode() - token = settingsPreferenceDataSource.getToken() - serverVersion = settingsPreferenceDataSource.getServerVersion() + _token = settingsPreferenceDataSource.getToken() + _serverVersion = settingsPreferenceDataSource.getServerVersion() + _serverUrl = settingsPreferenceDataSource.getUrl() } } @@ -143,7 +145,7 @@ class SettingsViewModel( viewModelScope.launch { getTagsUseCase.invoke( serverUrl = settingsPreferenceDataSource.getUrl(), - token = token, + token = _token, ) .distinctUntilChanged() .collect { result -> @@ -195,5 +197,10 @@ class SettingsViewModel( } } } + + fun getServerUrl(): String = _serverUrl + + fun getServerVersion(): String = _serverVersion + }