diff --git a/apps/ios/Shared/Views/ChatList/ChatListView.swift b/apps/ios/Shared/Views/ChatList/ChatListView.swift index 886b7465f3..16f87f0c13 100644 --- a/apps/ios/Shared/Views/ChatList/ChatListView.swift +++ b/apps/ios/Shared/Views/ChatList/ChatListView.swift @@ -266,23 +266,19 @@ struct ChatListView: View { } struct SubsStatusIndicator: View { - @State private var subs: SMPServerSubs = SMPServerSubs.newSMPServerSubs - @State private var sess: ServerSessions = ServerSessions.newServerSessions + @State private var serversSummary: PresentedServersSummary? @State private var timer: Timer? = nil @State private var timerCounter = 0 @State private var showServersSummary = false @AppStorage(DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE) private var showSubscriptionPercentage = false - // Constants for the intervals - let initialInterval: TimeInterval = 1.0 - let regularInterval: TimeInterval = 3.0 - let initialPhaseDuration: TimeInterval = 10.0 // Duration for initial phase in seconds - var body: some View { Button { showServersSummary = true } label: { + let subs = serversSummary?.allUsersSMP.smpTotals.subs ?? SMPServerSubs.newSMPServerSubs + let sess = serversSummary?.allUsersSMP.smpTotals.sessions ?? ServerSessions.newServerSessions HStack(spacing: 4) { SubscriptionStatusIndicatorView(subs: subs, sess: sess) if showSubscriptionPercentage { @@ -291,34 +287,24 @@ struct SubsStatusIndicator: View { } } .onAppear { - startInitialTimer() + startTimer() } .onDisappear { stopTimer() } .sheet(isPresented: $showServersSummary) { - ServersSummaryView() + ServersSummaryView(serversSummary: $serversSummary) } } - private func startInitialTimer() { - timer = Timer.scheduledTimer(withTimeInterval: initialInterval, repeats: true) { _ in - getServersSummary() - timerCounter += 1 - // Switch to the regular timer after the initial phase - if timerCounter * Int(initialInterval) >= Int(initialPhaseDuration) { - switchToRegularTimer() + private func startTimer() { + timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in + if AppChatState.shared.value == .active { + getServersSummary() } } } - func switchToRegularTimer() { - timer?.invalidate() - timer = Timer.scheduledTimer(withTimeInterval: regularInterval, repeats: true) { _ in - getServersSummary() - } - } - func stopTimer() { timer?.invalidate() timer = nil @@ -326,8 +312,7 @@ struct SubsStatusIndicator: View { private func getServersSummary() { do { - let summ = try getAgentServersSummary() - (subs, sess) = (summ.allUsersSMP.smpTotals.subs, summ.allUsersSMP.smpTotals.sessions) + serversSummary = try getAgentServersSummary() } catch let error { logger.error("getAgentServersSummary error: \(responseError(error))") } diff --git a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift index 1fb889f863..3021a7ceb2 100644 --- a/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift +++ b/apps/ios/Shared/Views/ChatList/ServersSummaryView.swift @@ -11,12 +11,12 @@ import SimpleXChat struct ServersSummaryView: View { @EnvironmentObject var m: ChatModel - @State private var serversSummary: PresentedServersSummary? = nil + @EnvironmentObject var theme: AppTheme + @Binding var serversSummary: PresentedServersSummary? @State private var selectedUserCategory: PresentedUserCategory = .allUsers @State private var selectedServerType: PresentedServerType = .smp @State private var selectedSMPServer: String? = nil @State private var selectedXFTPServer: String? = nil - @State private var timer: Timer? = nil @State private var alert: SomeAlert? @AppStorage(DEFAULT_SHOW_SUBSCRIPTION_PERCENTAGE) private var showSubscriptionPercentage = false @@ -47,26 +47,10 @@ struct ServersSummaryView: View { if m.users.filter({ u in u.user.activeUser || !u.user.hidden }).count == 1 { selectedUserCategory = .currentUser } - getServersSummary() - startTimer() - } - .onDisappear { - stopTimer() } .alert(item: $alert) { $0.alert } } - private func startTimer() { - timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true) { _ in - getServersSummary() - } - } - - func stopTimer() { - timer?.invalidate() - timer = nil - } - private func shareButton() -> some View { Button { if let serversSummary = serversSummary { @@ -183,6 +167,8 @@ struct ServersSummaryView: View { } } else { Text("No info, try to reload") + .foregroundColor(theme.colors.secondary) + .background(theme.colors.background) } } @@ -369,7 +355,6 @@ struct ServersSummaryView: View { Task { do { try await resetAgentServersStats() - getServersSummary() } catch let error { alert = SomeAlert( alert: mkAlert( @@ -389,14 +374,6 @@ struct ServersSummaryView: View { Text("Reset all statistics") } } - - private func getServersSummary() { - do { - serversSummary = try getAgentServersSummary() - } catch let error { - logger.error("getAgentServersSummary error: \(responseError(error))") - } - } } struct SubscriptionStatusIndicatorView: View { @@ -734,5 +711,7 @@ struct DetailedXFTPStatsView: View { } #Preview { - ServersSummaryView() + ServersSummaryView( + serversSummary: Binding.constant(nil) + ) } diff --git a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt index 547db51bad..d739a033f9 100644 --- a/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt +++ b/apps/multiplatform/common/src/androidMain/kotlin/chat/simplex/common/platform/AppCommon.android.kt @@ -6,8 +6,6 @@ import android.net.LocalServerSocket import android.util.Log import androidx.activity.ComponentActivity import androidx.fragment.app.FragmentActivity -import chat.simplex.common.* -import chat.simplex.common.platform.* import java.io.* import java.lang.ref.WeakReference import java.util.* @@ -24,6 +22,8 @@ var isAppOnForeground: Boolean = false @Suppress("ConstantLocale") val defaultLocale: Locale = Locale.getDefault() +actual fun isAppVisibleAndFocused(): Boolean = isAppOnForeground + @SuppressLint("StaticFieldLeak") lateinit var androidAppContext: Context var mainActivity: WeakReference = WeakReference(null) diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt index 10cb17df1d..110e878c44 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/platform/AppCommon.kt @@ -3,7 +3,6 @@ package chat.simplex.common.platform import chat.simplex.common.BuildConfigCommon import chat.simplex.common.model.* import chat.simplex.common.ui.theme.DefaultTheme -import java.io.File import java.util.* enum class AppPlatform { @@ -20,6 +19,8 @@ expect val appPlatform: AppPlatform expect val deviceName: String +expect fun isAppVisibleAndFocused(): Boolean + val appVersionInfo: Pair = if (appPlatform == AppPlatform.ANDROID) BuildConfigCommon.ANDROID_VERSION_NAME to BuildConfigCommon.ANDROID_VERSION_CODE else diff --git a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt index 13e88c3b2f..45e851055e 100644 --- a/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt +++ b/apps/multiplatform/common/src/commonMain/kotlin/chat/simplex/common/views/chatlist/ChatListView.kt @@ -266,44 +266,29 @@ private fun ChatListToolbar(drawerState: DrawerState, userPickerState: MutableSt fun SubscriptionStatusIndicator(serversSummary: MutableState, click: (() -> Unit)) { var subs by remember { mutableStateOf(SMPServerSubs.newSMPServerSubs) } var sess by remember { mutableStateOf(ServerSessions.newServerSessions) } - var timer: Job? by remember { mutableStateOf(null) } - - val fetchInterval: Duration = 1.seconds - val scope = rememberCoroutineScope() - fun setServersSummary() { - withBGApi { - serversSummary.value = chatModel.controller.getAgentServersSummary(chatModel.remoteHostId()) + suspend fun setServersSummary() { + serversSummary.value = chatModel.controller.getAgentServersSummary(chatModel.remoteHostId()) - serversSummary.value?.let { - subs = it.allUsersSMP.smpTotals.subs - sess = it.allUsersSMP.smpTotals.sessions - } + serversSummary.value?.let { + subs = it.allUsersSMP.smpTotals.subs + sess = it.allUsersSMP.smpTotals.sessions } } LaunchedEffect(Unit) { setServersSummary() - timer = timer ?: scope.launch { - while (true) { - delay(fetchInterval.inWholeMilliseconds) - setServersSummary() + scope.launch { + while (isActive) { + delay(1.seconds) + if ((appPlatform.isDesktop || chatModel.chatId.value == null) && !ModalManager.start.hasModalsOpen() && !ModalManager.fullscreen.hasModalsOpen() && isAppVisibleAndFocused()) { + setServersSummary() + } } } } - fun stopTimer() { - timer?.cancel() - timer = null - } - - DisposableEffect(Unit) { - onDispose { - stopTimer() - } - } - SimpleButtonFrame(click = click) { SubscriptionStatusIndicatorView(subs = subs, sess = sess) } diff --git a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt index 905e25566b..9fc84dc86f 100644 --- a/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt +++ b/apps/multiplatform/common/src/desktopMain/kotlin/chat/simplex/common/platform/AppCommon.desktop.kt @@ -1,6 +1,7 @@ package chat.simplex.common.platform import chat.simplex.common.model.* +import chat.simplex.common.simplexWindowState import chat.simplex.common.views.call.RcvCallInvitation import chat.simplex.common.views.helpers.* import java.util.* @@ -10,6 +11,8 @@ actual val appPlatform = AppPlatform.DESKTOP actual val deviceName = generalGetString(MR.strings.desktop_device) +actual fun isAppVisibleAndFocused() = simplexWindowState.windowFocused.value + @Suppress("ConstantLocale") val defaultLocale: Locale = Locale.getDefault() diff --git a/src/Simplex/Chat.hs b/src/Simplex/Chat.hs index 1a4811c2a3..8f5295705b 100644 --- a/src/Simplex/Chat.hs +++ b/src/Simplex/Chat.hs @@ -2260,11 +2260,14 @@ processChatCommand' vr = \case DebugEvent event -> toView event >> ok_ GetAgentServersSummary userId -> withUserId userId $ \user -> do agentServersSummary <- lift $ withAgent' getAgentServersSummary - users <- withStore' getUsers - smpServers <- getUserProtocolServers user SPSMP - xftpServers <- getUserProtocolServers user SPXFTP + cfg <- asks config + (users, smpServers, xftpServers) <- + withStore' $ \db -> (,,) <$> getUsers db <*> getServers db cfg user SPSMP <*> getServers db cfg user SPXFTP let presentedServersSummary = toPresentedServersSummary agentServersSummary users user smpServers xftpServers pure $ CRAgentServersSummary user presentedServersSummary + where + getServers :: (ProtocolTypeI p, UserProtocol p) => DB.Connection -> ChatConfig -> User -> SProtocolType p -> IO (NonEmpty (ProtocolServer p)) + getServers db cfg user p = L.map (\ServerCfg {server} -> protoServer server) . useServers cfg p <$> getProtocolServers db user ResetAgentServersStats -> withAgent resetAgentServersStats >> ok_ GetAgentWorkers -> lift $ CRAgentWorkersSummary <$> withAgent' getAgentWorkersSummary GetAgentWorkersDetails -> lift $ CRAgentWorkersDetails <$> withAgent' getAgentWorkersDetails @@ -3223,7 +3226,8 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} S.toList $ S.fromList $ concatMap (\FD.FileChunk {replicas} -> map (\FD.FileChunkReplica {server} -> server) replicas) chunks getUnknownSrvs :: [XFTPServer] -> CM [XFTPServer] getUnknownSrvs srvs = do - knownSrvs <- getUserProtocolServers user SPXFTP + cfg <- asks config + knownSrvs <- L.map (\ServerCfg {server} -> protoServer server) . useServers cfg SPXFTP <$> withStore' (`getProtocolServers` user) pure $ filter (`notElem` knownSrvs) srvs ipProtectedForSrvs :: [XFTPServer] -> CM Bool ipProtectedForSrvs srvs = do @@ -3235,11 +3239,6 @@ receiveViaCompleteFD user fileId RcvFileDescr {fileDescrText, fileDescrComplete} forM_ aci_ $ \aci -> toView $ CRChatItemUpdated user aci throwChatError $ CEFileNotApproved fileId unknownSrvs -getUserProtocolServers :: (ProtocolTypeI p, UserProtocol p) => User -> SProtocolType p -> CM (NonEmpty (ProtocolServer p)) -getUserProtocolServers user p = do - cfg <- asks config - L.map (\ServerCfg {server} -> protoServer server) . useServers cfg p <$> withStore' (`getProtocolServers` user) - getNetworkConfig :: CM' NetworkConfig getNetworkConfig = withAgent' $ liftIO . getNetworkConfig' diff --git a/src/Simplex/Chat/Stats.hs b/src/Simplex/Chat/Stats.hs index 334fbb2c11..e1d0372080 100644 --- a/src/Simplex/Chat/Stats.hs +++ b/src/Simplex/Chat/Stats.hs @@ -150,7 +150,7 @@ toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrv AgentServersSummary {statsStartedAt, smpServersSessions, smpServersSubs, smpServersStats, xftpServersSessions, xftpServersStats, xftpRcvInProgress, xftpSndInProgress, xftpDelInProgress} = agentSummary countUserInAll auId = countUserInAllStats (AgentUserId auId) currentUser users accSMPTotals :: Map SMPServer SMPServerSummary -> SMPTotals - accSMPTotals = M.foldr addTotals initialTotals + accSMPTotals = M.foldr' addTotals initialTotals where initialTotals = SMPTotals {sessions = ServerSessions 0 0 0, subs = SMPServerSubs 0 0, stats = newAgentSMPServerStatsData} addTotals SMPServerSummary {sessions, subs, stats} SMPTotals {sessions = accSess, subs = accSubs, stats = accStats} = @@ -160,7 +160,7 @@ toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrv stats = maybe accStats (accStats `addSMPStatsData`) stats } accXFTPTotals :: Map XFTPServer XFTPServerSummary -> XFTPTotals - accXFTPTotals = M.foldr addTotals initialTotals + accXFTPTotals = M.foldr' addTotals initialTotals where initialTotals = XFTPTotals {sessions = ServerSessions 0 0 0, stats = newAgentXFTPServerStatsData} addTotals XFTPServerSummary {sessions, stats} XFTPTotals {sessions = accSess, stats = accStats} = @@ -169,7 +169,7 @@ toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrv stats = maybe accStats (accStats `addXFTPStatsData`) stats } smpSummsIntoCategories :: Map SMPServer SMPServerSummary -> ([SMPServerSummary], [SMPServerSummary], [SMPServerSummary]) - smpSummsIntoCategories = foldr partitionSummary ([], [], []) + smpSummsIntoCategories = M.foldr' partitionSummary ([], [], []) where partitionSummary srvSumm (curr, prev, prox) | isCurrentlyUsed srvSumm = (srvSumm : curr, prev, prox) @@ -183,7 +183,7 @@ toPresentedServersSummary agentSummary users currentUser userSMPSrvs userXFTPSrv Just AgentSMPServerStatsData {_sentDirect, _sentProxied, _sentDirectAttempts, _sentProxiedAttempts, _recvMsgs, _connCreated, _connSecured, _connSubscribed, _connSubAttempts} -> _sentDirect > 0 || _sentProxied > 0 || _sentDirectAttempts > 0 || _sentProxiedAttempts > 0 || _recvMsgs > 0 || _connCreated > 0 || _connSecured > 0 || _connSubscribed > 0 || _connSubAttempts > 0 xftpSummsIntoCategories :: Map XFTPServer XFTPServerSummary -> ([XFTPServerSummary], [XFTPServerSummary]) - xftpSummsIntoCategories = foldr partitionSummary ([], []) + xftpSummsIntoCategories = M.foldr' partitionSummary ([], []) where partitionSummary srvSumm (curr, prev) | isCurrentlyUsed srvSumm = (srvSumm : curr, prev)