Skip to content

Commit 2c94070

Browse files
Mobile settings - Adding account manager in mobile (#575)
* mobile settings - adding account settings * code clean
1 parent 569c220 commit 2c94070

14 files changed

Lines changed: 389 additions & 224 deletions

File tree

application/composeApp/build.gradle.kts

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -176,8 +176,8 @@ android {
176176
applicationId = "io.writeopia"
177177
minSdk = libs.versions.minSdk.get().toInt()
178178
targetSdk = libs.versions.targetSdk.get().toInt()
179-
versionCode = 57
180-
versionName = "0.44.0"
179+
versionCode = 60
180+
versionName = "0.47.0"
181181

182182
testInstrumentationRunner = "androidx.test.runner.AndroidJUnitRunner"
183183
}
Lines changed: 33 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,33 @@
1+
package io.writeopia.auth.core.manager
2+
3+
import io.writeopia.sdk.models.utils.ResultData
4+
import io.writeopia.sdk.models.workspace.Workspace
5+
import kotlinx.coroutines.CoroutineScope
6+
import kotlinx.coroutines.flow.Flow
7+
import kotlinx.coroutines.flow.StateFlow
8+
9+
interface WorkspaceHandler {
10+
val availableWorkspaces: StateFlow<ResultData<List<Workspace>>>
11+
12+
val selectedWorkspace: Flow<Workspace?>
13+
14+
val usersOfSelectedWorkspace: Flow<ResultData<List<String>>>
15+
16+
val lastWorkspaceSync: StateFlow<ResultData<String>>
17+
18+
val workspaceLocalPath: StateFlow<String>
19+
20+
fun initScope(coroutineScope: CoroutineScope)
21+
22+
fun loadAvailableWorkspaces()
23+
24+
fun selectWorkspaceToManage(workspaceId: String)
25+
26+
fun syncWorkspace()
27+
28+
fun addUserToWorkspace(userEmail: String)
29+
30+
fun changeWorkspaceLocalPath(path: String)
31+
32+
fun initWorkspacePath()
33+
}

application/core/documents/config/ktlint/baseline.xml

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,9 @@
33
<file name="src/androidMain/kotlin/io/writeopia/core/folders/di/InDocumentSearchInjection.android.kt">
44
<error line="7" column="74" source="standard:function-expression-body" />
55
</file>
6+
<file name="src/commonMain/kotlin/io/writeopia/core/folders/di/WorkspaceHandlerImpl.kt">
7+
<error line="43" column="17" source="standard:backing-property-naming" />
8+
</file>
69
<file name="src/commonMain/kotlin/io/writeopia/core/folders/repository/InDocumentSearchSqlRepository.kt">
710
<error line="10" column="80" source="standard:function-expression-body" />
811
</file>
Lines changed: 145 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,145 @@
1+
@file:OptIn(ExperimentalTime::class)
2+
3+
package io.writeopia.core.folders.di
4+
5+
import io.writeopia.auth.core.data.WorkspaceApi
6+
import io.writeopia.auth.core.manager.AuthRepository
7+
import io.writeopia.auth.core.manager.WorkspaceHandler
8+
import io.writeopia.core.folders.sync.WorkspaceSync
9+
import io.writeopia.models.interfaces.configuration.WorkspaceConfigRepository
10+
import io.writeopia.sdk.models.utils.ResultData
11+
import io.writeopia.sdk.models.workspace.Workspace
12+
import kotlinx.coroutines.CoroutineScope
13+
import kotlinx.coroutines.Dispatchers
14+
import kotlinx.coroutines.ExperimentalCoroutinesApi
15+
import kotlinx.coroutines.flow.Flow
16+
import kotlinx.coroutines.flow.MutableStateFlow
17+
import kotlinx.coroutines.flow.StateFlow
18+
import kotlinx.coroutines.flow.asStateFlow
19+
import kotlinx.coroutines.flow.combine
20+
import kotlinx.coroutines.flow.flatMapLatest
21+
import kotlinx.coroutines.flow.flow
22+
import kotlinx.coroutines.launch
23+
import kotlin.time.Clock
24+
import kotlin.time.ExperimentalTime
25+
26+
class WorkspaceHandlerImpl(
27+
private val authRepository: AuthRepository,
28+
private val workspaceApi: WorkspaceApi,
29+
private val workspaceSync: WorkspaceSync,
30+
private val workspaceConfigRepository: WorkspaceConfigRepository
31+
) : WorkspaceHandler {
32+
33+
private val _availableWorkspaces: MutableStateFlow<ResultData<List<Workspace>>> =
34+
MutableStateFlow(ResultData.Idle())
35+
override val availableWorkspaces: StateFlow<ResultData<List<Workspace>>> = _availableWorkspaces
36+
37+
private lateinit var coroutineScope: CoroutineScope
38+
39+
override fun initScope(coroutineScope: CoroutineScope) {
40+
this.coroutineScope = coroutineScope
41+
}
42+
43+
private val _selectedWorkspaceId = MutableStateFlow<String?>(null)
44+
override val selectedWorkspace: Flow<Workspace?> by lazy {
45+
combine(_availableWorkspaces, _selectedWorkspaceId) { workspacesResult, selectedId ->
46+
if (workspacesResult is ResultData.Complete) {
47+
workspacesResult.data.firstOrNull { it.id == selectedId }
48+
} else {
49+
null
50+
}
51+
}
52+
}
53+
54+
@OptIn(ExperimentalCoroutinesApi::class)
55+
override val usersOfSelectedWorkspace: Flow<ResultData<List<String>>> =
56+
selectedWorkspace.flatMapLatest { workspace ->
57+
val workspaceId = workspace?.id
58+
59+
flow {
60+
val token = authRepository.getAuthToken()
61+
62+
if (token != null && workspaceId != null) {
63+
workspaceApi.getUsersOfWorkspace(workspaceId, token, forceRefresh = false)
64+
.collect { emit(it) }
65+
} else {
66+
emit(ResultData.Error())
67+
}
68+
}
69+
}
70+
71+
private val _lastWorkspaceSync = MutableStateFlow<ResultData<String>>(ResultData.Idle())
72+
override val lastWorkspaceSync: StateFlow<ResultData<String>> = _lastWorkspaceSync.asStateFlow()
73+
74+
private val _workspaceLocalPath = MutableStateFlow("")
75+
override val workspaceLocalPath: StateFlow<String> = _workspaceLocalPath.asStateFlow()
76+
77+
override fun loadAvailableWorkspaces() {
78+
coroutineScope.launch {
79+
val result = authRepository.getAuthToken()?.let { token ->
80+
workspaceApi.getAvailableWorkspaces(token)
81+
}
82+
83+
if (result != null) {
84+
_availableWorkspaces.value = result
85+
}
86+
}
87+
}
88+
89+
override fun selectWorkspaceToManage(workspaceId: String) {
90+
_selectedWorkspaceId.value = workspaceId
91+
}
92+
93+
override fun syncWorkspace() {
94+
coroutineScope.launch {
95+
_lastWorkspaceSync.value = ResultData.Loading()
96+
97+
val workspace = authRepository.getWorkspace() ?: Workspace.disconnectedWorkspace()
98+
val workspaceId = workspace.id
99+
val result = workspaceSync.syncWorkspace(workspaceId, force = true)
100+
101+
_lastWorkspaceSync.value = if (result is ResultData.Complete) {
102+
val lastSync = Clock.System
103+
.now()
104+
.toString()
105+
106+
ResultData.Complete("Last sync: $lastSync")
107+
} else {
108+
println("result error: $result")
109+
ResultData.Error(RuntimeException("Error in sync"))
110+
}
111+
}
112+
}
113+
114+
override fun addUserToWorkspace(userEmail: String) {
115+
coroutineScope.launch {
116+
val workspaceId = _selectedWorkspaceId.value
117+
118+
if (workspaceId != null) {
119+
authRepository.getAuthToken()?.let { token ->
120+
val result = workspaceApi.addUserToWorkspace(workspaceId, userEmail, token)
121+
122+
if (result is ResultData.Complete) {
123+
workspaceApi.refreshUsersInWorkspace(workspaceId, token)
124+
}
125+
}
126+
}
127+
}
128+
}
129+
130+
override fun changeWorkspaceLocalPath(path: String) {
131+
coroutineScope.launch(Dispatchers.Default) {
132+
val userId = authRepository.getUser().id
133+
134+
workspaceConfigRepository.saveWorkspacePath(path, userId)
135+
_workspaceLocalPath.value = workspaceConfigRepository.loadWorkspacePath(userId) ?: ""
136+
}
137+
}
138+
139+
override fun initWorkspacePath() {
140+
coroutineScope.launch {
141+
_workspaceLocalPath.value =
142+
workspaceConfigRepository.loadWorkspacePath(authRepository.getUser().id) ?: ""
143+
}
144+
}
145+
}

application/core/documents/src/commonMain/kotlin/io/writeopia/core/folders/di/WorkspaceInjection.kt

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,9 @@
11
package io.writeopia.core.folders.di
22

3+
import io.writeopia.auth.core.data.WorkspaceApi
34
import io.writeopia.auth.core.di.AuthCoreInjectionNeo
5+
import io.writeopia.auth.core.manager.WorkspaceHandler
6+
import io.writeopia.core.configuration.di.AppConfigurationInjector
47
import io.writeopia.core.folders.api.DocumentsApi
58
import io.writeopia.core.folders.sync.DocumentConflictHandler
69
import io.writeopia.core.folders.sync.ImageSync
@@ -15,8 +18,21 @@ class WorkspaceInjection private constructor(
1518
private val connectionInjector: WriteopiaConnectionInjector =
1619
WriteopiaConnectionInjector.singleton(),
1720
private val repositoryInjection: RepositoryInjector = RepositoryInjector.singleton(),
21+
private val appConfigurationInjector: AppConfigurationInjector =
22+
AppConfigurationInjector.singleton(),
1823
) {
1924

25+
private fun provideWorkspaceApi() =
26+
WorkspaceApi(appConnectionInjection.provideHttpClient(), connectionInjector.baseUrl())
27+
28+
fun provideWorkspaceHandler(): WorkspaceHandler =
29+
WorkspaceHandlerImpl(
30+
authRepository = authCoreInjection.provideAuthRepository(),
31+
workspaceApi = provideWorkspaceApi(),
32+
workspaceSync = provideWorkspaceSync(),
33+
workspaceConfigRepository = appConfigurationInjector.provideNotesConfigurationRepository()
34+
)
35+
2036
fun provideWorkspaceSync(): WorkspaceSync {
2137
val documentRepo = repositoryInjection.provideDocumentRepository()
2238
return WorkspaceSync(

application/features/account/src/commonMain/kotlin/io/writeopia/account/di/AccountMenuKmpInjector.kt

Lines changed: 7 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,17 +9,17 @@ import io.writeopia.core.folders.di.WorkspaceInjection
99

1010
class AccountMenuKmpInjector private constructor(
1111
private val workspaceInjection: WorkspaceInjection = WorkspaceInjection.singleton(),
12+
private val authCoreInjection: AuthCoreInjectionNeo = AuthCoreInjectionNeo.singleton(),
1213
) {
1314

14-
private fun provideAccountMenuKmpViewModel(): AccountMenuKmpViewModel =
15-
AccountMenuKmpViewModel(
16-
authRepository = AuthCoreInjectionNeo.singleton().provideAuthRepository(),
17-
workspaceSync = workspaceInjection.provideWorkspaceSync()
18-
)
19-
2015
@Composable
2116
fun provideAccountMenuViewModel(): AccountMenuViewModel =
22-
viewModel { provideAccountMenuKmpViewModel() }
17+
viewModel {
18+
AccountMenuKmpViewModel(
19+
authRepository = authCoreInjection.provideAuthRepository(),
20+
workspaceHandler = workspaceInjection.provideWorkspaceHandler()
21+
)
22+
}
2323

2424
companion object {
2525
private var instance: AccountMenuKmpInjector? = null
Lines changed: 19 additions & 51 deletions
Original file line numberDiff line numberDiff line change
@@ -1,22 +1,15 @@
11
package io.writeopia.account.ui
22

33
import androidx.compose.foundation.layout.Column
4-
import androidx.compose.foundation.layout.Spacer
54
import androidx.compose.foundation.layout.fillMaxSize
6-
import androidx.compose.foundation.layout.height
75
import androidx.compose.foundation.layout.padding
8-
import androidx.compose.material3.MaterialTheme
9-
import androidx.compose.material3.Text
6+
import androidx.compose.foundation.rememberScrollState
7+
import androidx.compose.foundation.verticalScroll
108
import androidx.compose.runtime.Composable
11-
import androidx.compose.runtime.collectAsState
129
import androidx.compose.ui.Modifier
13-
import androidx.compose.ui.text.font.FontWeight
1410
import androidx.compose.ui.unit.dp
1511
import io.writeopia.account.viewmodel.AccountMenuViewModel
16-
import io.writeopia.sdk.models.utils.toBoolean
17-
import io.writeopia.commonui.buttons.AccentButton
1812
import io.writeopia.model.ColorThemeOption
19-
import io.writeopia.resources.WrStrings
2013
import io.writeopia.sdk.models.utils.ResultData
2114
import kotlinx.coroutines.flow.MutableStateFlow
2215
import kotlinx.coroutines.flow.StateFlow
@@ -30,11 +23,11 @@ fun AccountMenuScreen(
3023
selectColorTheme: (ColorThemeOption) -> Unit,
3124
modifier: Modifier = Modifier,
3225
) {
33-
Column(modifier = modifier.fillMaxSize().padding(16.dp)) {
34-
Connect(accountMenuViewModel, isLoggedInState, onLogout, goToRegister)
35-
36-
Spacer(modifier = Modifier.height(16.dp))
37-
26+
Column(
27+
modifier = modifier.fillMaxSize()
28+
.padding(16.dp)
29+
.verticalScroll(state = rememberScrollState())
30+
) {
3831
SettingsScreen(
3932
showPath = false,
4033
showOllamaConfig = false,
@@ -53,43 +46,18 @@ fun AccountMenuScreen(
5346
downloadModel = {},
5447
deleteModel = {},
5548
syncWorkspace = accountMenuViewModel::syncWorkspace,
49+
workspacesState = accountMenuViewModel.availableWorkspaces,
50+
selectedWorkspaceState = accountMenuViewModel.selectedWorkspace,
51+
selectWorkspace = accountMenuViewModel::selectWorkspace,
52+
addUserToTeam = accountMenuViewModel::addUserToWorkspace,
53+
usersInSelectedWorkspace = accountMenuViewModel.usersOfSelectedWorkspace,
54+
isLoggedInState = isLoggedInState,
55+
goToRegister = goToRegister,
56+
logout = {
57+
accountMenuViewModel.logout {
58+
onLogout()
59+
}
60+
},
5661
)
5762
}
5863
}
59-
60-
@Composable
61-
private fun Connect(
62-
accountMenuViewModel: AccountMenuViewModel,
63-
isLoggedInState: StateFlow<ResultData<Boolean>>,
64-
onLogout: () -> Unit,
65-
goToRegister: () -> Unit
66-
) {
67-
val isLoggedIn = isLoggedInState.collectAsState().value.toBoolean()
68-
69-
val titleStyle = MaterialTheme.typography.titleLarge
70-
val titleColor = MaterialTheme.colorScheme.onBackground
71-
72-
Text(WrStrings.account(), style = titleStyle, color = titleColor)
73-
74-
if (!isLoggedIn) {
75-
Spacer(modifier = Modifier.height(8.dp))
76-
77-
Text(
78-
modifier = Modifier,
79-
text = WrStrings.youAreOffline(),
80-
style = MaterialTheme.typography.bodySmall,
81-
color = MaterialTheme.colorScheme.onBackground,
82-
fontWeight = FontWeight.Bold,
83-
)
84-
}
85-
86-
Spacer(modifier = Modifier.height(4.dp))
87-
88-
AccentButton(text = if (isLoggedIn) WrStrings.logout() else WrStrings.singIn()) {
89-
if (isLoggedIn) {
90-
accountMenuViewModel.logout(onLogout)
91-
} else {
92-
goToRegister()
93-
}
94-
}
95-
}

0 commit comments

Comments
 (0)