Skip to content

Commit ed79d8f

Browse files
committed
allow remembering previously opened tabs (#204)
1 parent 8d47ab6 commit ed79d8f

File tree

15 files changed

+280
-75
lines changed

15 files changed

+280
-75
lines changed

app/src/main/java/com/raival/compose/file/explorer/screen/main/MainActivity.kt

Lines changed: 19 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -37,8 +37,10 @@ import androidx.compose.ui.unit.Velocity
3737
import androidx.lifecycle.Lifecycle
3838
import androidx.lifecycle.compose.LifecycleEventEffect
3939
import com.raival.compose.file.explorer.App.Companion.globalClass
40+
import com.raival.compose.file.explorer.R
4041
import com.raival.compose.file.explorer.base.BaseActivity
4142
import com.raival.compose.file.explorer.common.isNot
43+
import com.raival.compose.file.explorer.common.showMsg
4244
import com.raival.compose.file.explorer.common.toJson
4345
import com.raival.compose.file.explorer.common.ui.SafeSurface
4446
import com.raival.compose.file.explorer.screen.main.tab.apps.AppsTab
@@ -55,6 +57,7 @@ import com.raival.compose.file.explorer.screen.main.ui.StartupTabsSettingsScreen
5557
import com.raival.compose.file.explorer.screen.main.ui.TabLayout
5658
import com.raival.compose.file.explorer.screen.main.ui.Toolbar
5759
import com.raival.compose.file.explorer.theme.FileExplorerTheme
60+
import kotlinx.coroutines.delay
5861
import kotlinx.coroutines.launch
5962
import java.io.File
6063
import kotlin.math.abs
@@ -76,11 +79,21 @@ class MainActivity : BaseActivity() {
7679
val coroutineScope = rememberCoroutineScope()
7780
val mainActivityManager = globalClass.mainActivityManager
7881
val mainActivityState by mainActivityManager.state.collectAsState()
82+
var backPressedOnce by remember { mutableStateOf(false) }
7983

8084
BackHandler {
8185
coroutineScope.launch {
8286
if (mainActivityManager.canExit()) {
83-
finish()
87+
if (!globalClass.preferencesManager.confirmBeforeAppClose || backPressedOnce) {
88+
finish()
89+
} else {
90+
backPressedOnce = true
91+
showMsg(R.string.press_back_again)
92+
launch {
93+
delay(2000)
94+
backPressedOnce = false
95+
}
96+
}
8497
}
8598
}
8699
}
@@ -93,6 +106,11 @@ class MainActivity : BaseActivity() {
93106
mainActivityManager.onStop()
94107
}
95108

109+
LifecycleEventEffect(Lifecycle.Event.ON_PAUSE) {
110+
if (globalClass.preferencesManager.rememberLastSession)
111+
mainActivityManager.saveSession()
112+
}
113+
96114
LaunchedEffect(Unit) {
97115
mainActivityManager.checkForUpdate()
98116
if (hasIntent()) {

app/src/main/java/com/raival/compose/file/explorer/screen/main/MainActivityManager.kt

Lines changed: 122 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,9 @@ import com.raival.compose.file.explorer.common.isNot
1212
import com.raival.compose.file.explorer.common.padEnd
1313
import com.raival.compose.file.explorer.common.printFullStackTrace
1414
import com.raival.compose.file.explorer.common.showMsg
15+
import com.raival.compose.file.explorer.common.toJson
1516
import com.raival.compose.file.explorer.screen.main.model.GithubRelease
17+
import com.raival.compose.file.explorer.screen.main.startup.StartupTab
1618
import com.raival.compose.file.explorer.screen.main.startup.StartupTabType
1719
import com.raival.compose.file.explorer.screen.main.startup.StartupTabs
1820
import com.raival.compose.file.explorer.screen.main.tab.Tab
@@ -69,13 +71,15 @@ class MainActivityManager {
6971
if (tabIndex isNot _state.value.selectedTabIndex) return
7072

7173
val tabToKeep = _state.value.tabs[tabIndex]
74+
val tabsToRemove = _state.value.tabs.filter { it != tabToKeep }
75+
76+
// Call onTabRemoved on tabs being removed BEFORE state update
77+
tabsToRemove.forEach { it.onTabRemoved() }
78+
79+
// Update the state
7280
_state.update {
7381
it.copy(
74-
tabs = _state.value.tabs.filter { item ->
75-
val toKeep = item == tabToKeep
76-
if (!toKeep) item.onTabRemoved()
77-
toKeep
78-
},
82+
tabs = listOf(tabToKeep),
7983
selectedTabIndex = 0
8084
)
8185
}
@@ -85,26 +89,23 @@ class MainActivityManager {
8589
// There must be at least one tab
8690
if (_state.value.tabs.size <= 1) return
8791

88-
// Call the proper callbacks on the tab to be removed
89-
_state.value.tabs[index].apply {
90-
if (_state.value.selectedTabIndex == index) onTabStopped()
91-
onTabRemoved()
92+
val tabToRemove = _state.value.tabs[index]
93+
val currentSelectedIndex = _state.value.selectedTabIndex
94+
95+
// Call callbacks on the tab to be removed BEFORE state update
96+
if (currentSelectedIndex == index) {
97+
tabToRemove.onTabStopped()
9298
}
99+
tabToRemove.onTabRemoved()
93100

94-
// handle selected tab index
95-
val newSelectedTabIndex = if (index < _state.value.selectedTabIndex) {
96-
_state.value.selectedTabIndex - 1
97-
} else if (index > _state.value.selectedTabIndex) {
98-
_state.value.selectedTabIndex
101+
// Calculate new selected tab index
102+
val newSelectedTabIndex = if (index < currentSelectedIndex) {
103+
currentSelectedIndex - 1
104+
} else if (index > currentSelectedIndex) {
105+
currentSelectedIndex
99106
} else {
100107
// Removing the selected tab itself - choose the previous tab if available, otherwise the next one
101108
max(0, index - 1)
102-
}.also {
103-
// Call the proper callbacks on the new selected tab (if it's not the already selected one)
104-
if (index isNot it) {
105-
val newSelectedTab = _state.value.tabs[it]
106-
if (newSelectedTab.isCreated) newSelectedTab.onTabResumed() else newSelectedTab.onTabStarted()
107-
}
108109
}
109110

110111
// Update the state
@@ -116,14 +117,23 @@ class MainActivityManager {
116117
selectedTabIndex = newSelectedTabIndex
117118
)
118119
}
120+
121+
// Call callbacks on the new selected tab AFTER state update (if it's different from before)
122+
if (index == currentSelectedIndex) {
123+
val newSelectedTab = _state.value.tabs[newSelectedTabIndex]
124+
if (newSelectedTab.isCreated) {
125+
newSelectedTab.onTabResumed()
126+
} else {
127+
newSelectedTab.onTabStarted()
128+
}
129+
}
119130
}
120131

121132
fun addTabAndSelect(tab: Tab, index: Int = -1) {
122-
// Stop the active tab
123-
getActiveTab()?.onTabStopped()
133+
val currentActiveTab = getActiveTab()
124134

125-
// Start the new tab
126-
if (tab.isCreated) tab.onTabResumed() else tab.onTabStarted()
135+
// Stop the active tab BEFORE state update
136+
currentActiveTab?.onTabStopped()
127137

128138
// Validate the index
129139
val validatedIndex = if (index isNot -1) {
@@ -139,6 +149,13 @@ class MainActivityManager {
139149
selectedTabIndex = validatedIndex
140150
)
141151
}
152+
153+
// Start the new tab AFTER state update
154+
if (tab.isCreated) {
155+
tab.onTabResumed()
156+
} else {
157+
tab.onTabStarted()
158+
}
142159
}
143160

144161
fun selectTabAt(index: Int, skipTabRefresh: Boolean = false) {
@@ -149,42 +166,57 @@ class MainActivityManager {
149166
_state.value.tabs.lastIndex
150167
}
151168

169+
val currentSelectedIndex = _state.value.selectedTabIndex
170+
val currentActiveTab = getActiveTab()
171+
152172
// If the tab is already selected, resume that tab (kind of refreshing the tab)
153-
if (validatedIndex == _state.value.selectedTabIndex) {
154-
if (!skipTabRefresh) getActiveTab()?.onTabResumed()
173+
if (validatedIndex == currentSelectedIndex) {
174+
if (!skipTabRefresh) {
175+
currentActiveTab?.onTabResumed()
176+
}
155177
} else {
156-
// Stop the active tab
157-
getActiveTab()?.onTabStopped()
158-
159-
// Start the new tab
160-
val newSelectedTab = _state.value.tabs[validatedIndex]
161-
if (newSelectedTab.isCreated) newSelectedTab.onTabResumed() else newSelectedTab.onTabStarted()
178+
// Stop the active tab BEFORE state update
179+
currentActiveTab?.onTabStopped()
162180

163181
// Update the state
164182
_state.update {
165183
it.copy(selectedTabIndex = validatedIndex)
166184
}
185+
186+
// Start the new tab AFTER state update
187+
val newSelectedTab = _state.value.tabs[validatedIndex]
188+
if (newSelectedTab.isCreated) {
189+
newSelectedTab.onTabResumed()
190+
} else {
191+
newSelectedTab.onTabStarted()
192+
}
167193
}
168194
}
169195

170196
fun replaceCurrentTabWith(tab: Tab) {
171-
// Stop the active tab
172-
getActiveTab()?.apply {
197+
val currentActiveTab = getActiveTab()
198+
199+
// Stop and remove the active tab BEFORE state update
200+
currentActiveTab?.apply {
173201
onTabStopped()
174202
onTabRemoved()
175203
}
176204

177-
// Start the new tab
178-
if (tab.isCreated) tab.onTabResumed() else tab.onTabStarted()
179-
180-
// update the state
205+
// Update the state
181206
_state.update {
182207
it.copy(
183208
tabs = _state.value.tabs.mapIndexed { index, oldTab ->
184209
if (index == _state.value.selectedTabIndex) tab else oldTab
185210
}
186211
)
187212
}
213+
214+
// Start the new tab AFTER state update
215+
if (tab.isCreated) {
216+
tab.onTabResumed()
217+
} else {
218+
tab.onTabStarted()
219+
}
188220
}
189221

190222
fun jumpToFile(file: LocalFileHolder, context: Context) {
@@ -253,7 +285,7 @@ class MainActivityManager {
253285
}
254286

255287
// Remove the active tab
256-
if (tabs.size > 1 && selectedTabIndex isNot 0) {
288+
if (tabs.size > 1 && selectedTabIndex isNot 0 && globalClass.preferencesManager.closeTabOnBackPress) {
257289
removeTabAt(selectedTabIndex)
258290
return false
259291
}
@@ -309,33 +341,79 @@ class MainActivityManager {
309341
}
310342
}
311343

344+
fun saveSession() {
345+
val startupTabs = arrayListOf<StartupTab>()
346+
_state.value.tabs.forEach { tab ->
347+
when (tab) {
348+
is FilesTab -> {
349+
startupTabs.add(
350+
StartupTab(
351+
type = StartupTabType.FILES,
352+
extra = tab.activeFolder.uniquePath
353+
)
354+
)
355+
}
356+
357+
is AppsTab -> {
358+
startupTabs.add(
359+
StartupTab(
360+
type = StartupTabType.APPS
361+
)
362+
)
363+
}
364+
365+
else -> {
366+
startupTabs.add(
367+
StartupTab(
368+
type = StartupTabType.HOME
369+
)
370+
)
371+
}
372+
}
373+
}
374+
globalClass.preferencesManager.lastSessionTabs = StartupTabs(startupTabs).toJson()
375+
}
376+
312377
fun loadStartupTabs() {
313378
managerScope.launch {
314379
val startupTabs: StartupTabs =
315-
fromJson(globalClass.preferencesManager.startupTabs)
380+
if (globalClass.preferencesManager.rememberLastSession)
381+
fromJson(globalClass.preferencesManager.lastSessionTabs)
382+
?: StartupTabs.default()
383+
else fromJson(globalClass.preferencesManager.startupTabs)
316384
?: StartupTabs.default()
317385

318386
val tabs = arrayListOf<Tab>()
319387
val index = 0
320388

321-
startupTabs.tabs.forEachIndexed { index, tab ->
389+
startupTabs.tabs.forEachIndexed { _, tab ->
322390
val newTab = when (tab.type) {
323391
StartupTabType.FILES -> FilesTab(LocalFileHolder(File(tab.extra)))
324392
StartupTabType.APPS -> AppsTab()
325393
else -> HomeTab()
326-
}.apply {
327-
if (isCreated) onTabResumed() else onTabStarted()
328394
}
329-
330395
tabs.add(newTab)
331396
}
332397

398+
// Update the state first
333399
_state.update {
334400
it.copy(
335401
tabs = tabs,
336402
selectedTabIndex = index
337403
)
338404
}
405+
406+
// Call callbacks on tabs AFTER state update
407+
tabs.forEachIndexed { tabIndex, tab ->
408+
if (tabIndex == index) {
409+
// This is the selected tab
410+
if (tab.isCreated) {
411+
tab.onTabResumed()
412+
} else {
413+
tab.onTabStarted()
414+
}
415+
}
416+
}
339417
}
340418
}
341419

app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/Tab.kt

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -24,11 +24,13 @@ abstract class Tab {
2424
open fun onBackPressed(): Boolean = false
2525

2626
fun requestHomeToolbarUpdate() {
27-
CoroutineScope(Dispatchers.IO).launch {
28-
globalClass.mainActivityManager.updateHomeToolbar(
29-
title = getTitle(),
30-
subtitle = getSubtitle()
31-
)
27+
if (globalClass.mainActivityManager.getActiveTab() == this) {
28+
CoroutineScope(Dispatchers.IO).launch {
29+
globalClass.mainActivityManager.updateHomeToolbar(
30+
title = getTitle(),
31+
subtitle = getSubtitle()
32+
)
33+
}
3234
}
3335
}
3436
}

app/src/main/java/com/raival/compose/file/explorer/screen/main/tab/files/FilesTab.kt

Lines changed: 4 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -99,7 +99,7 @@ class FilesTab(
9999
val bottomOptionsBarState = _bottomOptionsBarState.asStateFlow()
100100

101101
var handleBackGesture by mutableStateOf(true)
102-
var tabViewLabel by mutableStateOf(homeDir.displayName)
102+
var tabViewLabel by mutableStateOf(createTabLabel())
103103

104104
var isLoading by mutableStateOf(false)
105105

@@ -293,7 +293,7 @@ class FilesTab(
293293

294294
// Update header label
295295
withContext(Dispatchers.Main) {
296-
updateTabViewLabel()
296+
tabViewLabel = createTabLabel()
297297
}
298298

299299
// Clear selection if not needed
@@ -583,12 +583,12 @@ class FilesTab(
583583
contentListStates[activeFolder.uniquePath] = it
584584
}
585585

586-
private fun updateTabViewLabel() {
586+
private fun createTabLabel(): String {
587587
val fullName =
588588
activeFolder.displayName.orIf(globalClass.getString(R.string.internal_storage)) {
589589
activeFolder.uniquePath == Environment.getExternalStorageDirectory().absolutePath
590590
}
591-
tabViewLabel = if (fullName.length > 18) fullName.substring(0, 15) + "..." else fullName
591+
return if (fullName.length > 18) fullName.substring(0, 15) + "..." else fullName
592592
}
593593

594594
private suspend fun updatePathList() {

0 commit comments

Comments
 (0)