-
-
Notifications
You must be signed in to change notification settings - Fork 28
Improve toolbar code quality #219
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
6a0a0d7
48f46a5
3bfcf27
a124a13
e897157
0e1b967
72ef977
0c21f98
c388c05
89e998d
834e4f4
3d15d79
096848f
4c1d81a
b24670b
df893fb
9d152ac
67aec53
c1ffe2d
d219b89
5321daa
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -1,33 +1,33 @@ | ||
| package com.ethran.notable.editor | ||
|
|
||
| import androidx.compose.foundation.layout.BoxWithConstraints | ||
| import androidx.compose.foundation.layout.Column | ||
| import androidx.compose.foundation.layout.Row | ||
| import androidx.compose.foundation.layout.Spacer | ||
| import androidx.compose.foundation.layout.fillMaxHeight | ||
| import androidx.compose.foundation.layout.fillMaxWidth | ||
| import androidx.compose.runtime.Composable | ||
| import androidx.compose.runtime.DisposableEffect | ||
| import androidx.compose.runtime.LaunchedEffect | ||
| import androidx.compose.runtime.collectAsState | ||
| import androidx.compose.runtime.getValue | ||
| import androidx.compose.runtime.mutableStateOf | ||
| import androidx.compose.runtime.remember | ||
| import androidx.compose.runtime.rememberCoroutineScope | ||
| import androidx.compose.runtime.setValue | ||
| import androidx.compose.ui.Modifier | ||
| import androidx.compose.ui.platform.LocalContext | ||
| import androidx.hilt.lifecycle.viewmodel.compose.hiltViewModel | ||
| import androidx.navigation.NavController | ||
| import com.ethran.notable.data.AppRepository | ||
| import com.ethran.notable.data.datastore.AppSettings | ||
| import com.ethran.notable.data.datastore.EditorSettingCacheManager | ||
| import com.ethran.notable.data.datastore.GlobalAppSettings | ||
| import com.ethran.notable.editor.canvas.CanvasEventBus | ||
| import com.ethran.notable.editor.state.EditorState | ||
| import com.ethran.notable.editor.state.History | ||
| import com.ethran.notable.editor.ui.EditorSurface | ||
| import com.ethran.notable.editor.ui.HorizontalScrollIndicator | ||
| import com.ethran.notable.editor.ui.ScrollIndicator | ||
| import com.ethran.notable.editor.ui.SelectedBitmap | ||
| import com.ethran.notable.editor.ui.toolbar.Toolbar | ||
| import com.ethran.notable.editor.ui.toolbar.PositionedToolbar | ||
| import com.ethran.notable.gestures.EditorGestureReceiver | ||
| import com.ethran.notable.io.ExportEngine | ||
| import com.ethran.notable.io.exportToLinkedFile | ||
|
|
@@ -37,7 +37,9 @@ import com.ethran.notable.ui.SnackConf | |
| import com.ethran.notable.ui.SnackState | ||
| import com.ethran.notable.ui.convertDpToPixel | ||
| import com.ethran.notable.ui.theme.InkaTheme | ||
| import com.ethran.notable.ui.views.BugReportDestination | ||
| import com.ethran.notable.ui.views.LibraryDestination | ||
| import com.ethran.notable.ui.views.PagesDestination | ||
| import io.shipbook.shipbooksdk.ShipBook | ||
| import kotlinx.coroutines.Dispatchers | ||
| import kotlinx.coroutines.launch | ||
|
|
@@ -71,14 +73,16 @@ fun EditorView( | |
| appRepository: AppRepository, | ||
| bookId: String?, | ||
| pageId: String, | ||
| onPageChange: (String) -> Unit | ||
| onPageChange: (String) -> Unit, | ||
| viewModel: EditorViewModel = hiltViewModel() | ||
| ) { | ||
| val context = LocalContext.current | ||
| val snackManager = LocalSnackContext.current | ||
| val scope = rememberCoroutineScope() | ||
|
|
||
| var pageExists by remember(pageId) { mutableStateOf<Boolean?>(null) } | ||
| LaunchedEffect(pageId) { | ||
| viewModel.loadBookData(bookId, pageId) | ||
| val exists = withContext(Dispatchers.IO) { | ||
| appRepository.pageRepository.getById(pageId) != null | ||
| } | ||
|
|
@@ -92,8 +96,7 @@ fun EditorView( | |
| log.i("Could not find page, Cleaning book") | ||
| SnackState.globalSnackFlow.tryEmit( | ||
| SnackConf( | ||
| text = "Could not find page, cleaning book", | ||
| duration = 4000 | ||
| text = "Could not find page, cleaning book", duration = 4000 | ||
| ) | ||
| ) | ||
| scope.launch(Dispatchers.IO) { | ||
|
|
@@ -104,7 +107,6 @@ fun EditorView( | |
| } | ||
| } | ||
|
|
||
|
|
||
| if (pageExists == null) return | ||
|
|
||
| BoxWithConstraints { | ||
|
|
@@ -124,17 +126,16 @@ fun EditorView( | |
| ) | ||
| } | ||
|
|
||
| val editorState = | ||
| remember { | ||
| EditorState( | ||
| appRepository = appRepository, | ||
| bookId = bookId, | ||
| pageId = pageId, | ||
| pageView = page, | ||
| persistedEditorSettings = editorSettingCacheManager.getEditorSettings(), | ||
| onPageChange = onPageChange | ||
| ) | ||
| } | ||
| val editorState = remember { | ||
| EditorState( | ||
| appRepository = appRepository, | ||
| bookId = bookId, | ||
| pageId = pageId, | ||
| pageView = page, | ||
| persistedEditorSettings = editorSettingCacheManager.getEditorSettings(), | ||
| onPageChange = onPageChange | ||
| ) | ||
| } | ||
|
|
||
| val history = remember { | ||
| History(page) | ||
|
|
@@ -143,13 +144,121 @@ fun EditorView( | |
| EditorControlTower(scope, page, history, editorState).apply { registerObservers() } | ||
| } | ||
|
|
||
| // Collect UI Events from ViewModel | ||
| LaunchedEffect(Unit) { | ||
| viewModel.uiEvents.collect { event -> | ||
| when (event) { | ||
| is EditorUiEvent.Undo -> editorControlTower.undo() | ||
| is EditorUiEvent.Redo -> editorControlTower.redo() | ||
| is EditorUiEvent.Paste -> editorControlTower.pasteFromClipboard() | ||
| is EditorUiEvent.ResetView -> editorControlTower.resetZoomAndScroll() | ||
| is EditorUiEvent.ClearAllStrokes -> { | ||
| CanvasEventBus.clearPageSignal.emit(Unit) | ||
| snackManager.displaySnack(SnackConf(text = "Cleared all strokes")) | ||
| } | ||
|
|
||
| is EditorUiEvent.NavigateToLibrary -> { | ||
| navController.navigate(LibraryDestination.createRoute(event.folderId)) | ||
| } | ||
|
|
||
| is EditorUiEvent.NavigateToPages -> { | ||
| navController.navigate(PagesDestination.createRoute(event.bookId)) | ||
| } | ||
|
|
||
| EditorUiEvent.NavigateToBugReport -> { | ||
| navController.navigate(BugReportDestination.route) | ||
| } | ||
|
|
||
| is EditorUiEvent.ShowSnackbar -> { | ||
| snackManager.displaySnack(SnackConf(text = event.message)) | ||
| } | ||
|
|
||
| is EditorUiEvent.CopyImageToCanvas -> { | ||
| CanvasEventBus.addImageByUri.value = event.uri | ||
| } | ||
|
|
||
| EditorUiEvent.RefreshCanvas -> { | ||
| CanvasEventBus.reloadFromDb.emit(Unit) | ||
| } | ||
|
|
||
| is EditorUiEvent.ModeChanged -> { | ||
| editorState.mode = event.mode | ||
| } | ||
|
|
||
| is EditorUiEvent.PenChanged -> { | ||
| editorState.pen = event.pen | ||
| } | ||
|
|
||
| is EditorUiEvent.PenSettingChanged -> { | ||
| val newSettings = editorState.penSettings.toMutableMap() | ||
| newSettings[event.pen.penName] = event.setting | ||
| editorState.penSettings = newSettings | ||
| } | ||
|
|
||
| is EditorUiEvent.EraserChanged -> { | ||
| editorState.eraser = event.eraser | ||
| } | ||
|
|
||
| is EditorUiEvent.ToolbarVisibilityChanged -> { | ||
| editorState.isToolbarOpen = event.visible | ||
| } | ||
| } | ||
| } | ||
| } | ||
|
|
||
| // Handle Canvas signals in UI | ||
| LaunchedEffect(Unit) { | ||
| CanvasEventBus.closeMenusSignal.collect { | ||
| log.d("Closing all menus") | ||
| viewModel.onToolbarAction(ToolbarAction.CloseAllMenus) | ||
| } | ||
| } | ||
|
|
||
| // Handle focus changes from Canvas | ||
| LaunchedEffect(Unit) { | ||
| CanvasEventBus.onFocusChange.collect { hasFocus -> | ||
| log.d("Canvas has focus: $hasFocus") | ||
| viewModel.onFocusChanged(hasFocus) | ||
| } | ||
| } | ||
|
|
||
| // Sync legacy state to ViewModel for Toolbar rendering | ||
| val zoomLevel by page.zoomLevel.collectAsState() | ||
Ethran marked this conversation as resolved.
Show resolved
Hide resolved
|
||
| val selectionActive = editorState.selectionState.isNonEmpty() | ||
| LaunchedEffect( | ||
| zoomLevel, | ||
| page.scroll, | ||
| editorState.clipboard, | ||
| editorState.isToolbarOpen, | ||
| editorState.mode, | ||
| editorState.pen, | ||
| editorState.eraser, | ||
| editorState.penSettings, | ||
| selectionActive | ||
| ) { | ||
| viewModel.setHasClipboard(editorState.clipboard != null) | ||
| viewModel.setShowResetView(zoomLevel != 1.0f) // page.scroll != Offset.Zero | ||
|
||
| viewModel.setSelectionActive(selectionActive) | ||
| viewModel.updateToolbarSettings( | ||
| ToolbarUiState( | ||
| isToolbarOpen = editorState.isToolbarOpen, | ||
| mode = editorState.mode, | ||
| pen = editorState.pen, | ||
| eraser = editorState.eraser, | ||
| penSettings = editorState.penSettings | ||
| ) | ||
| ) | ||
| } | ||
|
|
||
| DisposableEffect(Unit) { | ||
| onDispose { | ||
| // finish selection operation | ||
| editorState.selectionState.applySelectionDisplace(page) | ||
| if (bookId != null) | ||
| exportToLinkedFile(exportEngine, bookId, appRepository.bookRepository) | ||
| if (bookId != null) exportToLinkedFile( | ||
| exportEngine, | ||
| bookId, | ||
| appRepository.bookRepository | ||
| ) | ||
| page.disposeOldPage() | ||
| } | ||
| } | ||
|
|
@@ -160,7 +269,6 @@ fun EditorView( | |
| editorState.pen, | ||
| editorState.penSettings, | ||
| editorState.mode, | ||
| editorState.isToolbarOpen, | ||
| editorState.eraser | ||
| ) { | ||
| log.i("EditorView: saving") | ||
|
|
@@ -180,14 +288,10 @@ fun EditorView( | |
| InkaTheme { | ||
| EditorGestureReceiver(controlTower = editorControlTower) | ||
| EditorSurface( | ||
| appRepository = appRepository, | ||
| state = editorState, | ||
| page = page, | ||
| history = history | ||
| appRepository = appRepository, state = editorState, page = page, history = history | ||
| ) | ||
| SelectedBitmap( | ||
| context = context, | ||
| controlTower = editorControlTower | ||
| context = context, controlTower = editorControlTower | ||
| ) | ||
| Row( | ||
| modifier = Modifier | ||
|
|
@@ -197,40 +301,9 @@ fun EditorView( | |
| Spacer(modifier = Modifier.weight(1f)) | ||
| ScrollIndicator(state = editorState) | ||
| } | ||
| PositionedToolbar(exportEngine,navController, appRepository, editorState, editorControlTower) | ||
| PositionedToolbar( | ||
| viewModel = viewModel, onDrawingStateCheck = { viewModel.updateDrawingState() }) | ||
| HorizontalScrollIndicator(state = editorState) | ||
| } | ||
| } | ||
| } | ||
|
|
||
|
|
||
| @Composable | ||
| fun PositionedToolbar( | ||
| exportEngine: ExportEngine, | ||
| navController: NavController, | ||
| appRepository: AppRepository, | ||
| editorState: EditorState, | ||
| editorControlTower: EditorControlTower | ||
| ) { | ||
| val position = GlobalAppSettings.current.toolbarPosition | ||
|
|
||
| when (position) { | ||
| AppSettings.Position.Top -> { | ||
| Toolbar( | ||
| exportEngine, | ||
| navController, appRepository, editorState, editorControlTower | ||
| ) | ||
| } | ||
|
|
||
| AppSettings.Position.Bottom -> { | ||
| Column( | ||
| Modifier | ||
| .fillMaxWidth() | ||
| .fillMaxHeight() | ||
| ) { | ||
| Spacer(modifier = Modifier.weight(1f)) | ||
| Toolbar(exportEngine, navController, appRepository, editorState, editorControlTower) | ||
| } | ||
| } | ||
| } | ||
| } | ||
Uh oh!
There was an error while loading. Please reload this page.