diff --git a/go/constants/attributes.go b/go/constants/attributes.go index 8219d608..9af88ff3 100644 --- a/go/constants/attributes.go +++ b/go/constants/attributes.go @@ -12,3 +12,7 @@ const ProfileTypeV1Password = "v1-userPassword" // PeerOnline stores state on if the peer believes it is online const PeerOnline = "peer-online" + +const StateProfilePane = "state-profile-pane" +const StateSelectedConversation = "state-selected-conversation" +const StateSelectedProfileTime = "state-selected-profile-time" diff --git a/go/handlers/appHandler.go b/go/handlers/appHandler.go index abd24ed2..b6bb9869 100644 --- a/go/handlers/appHandler.go +++ b/go/handlers/appHandler.go @@ -25,7 +25,15 @@ func App(gcd *ui.GrandCentralDispatcher, subscribed chan bool, reloadingAccounts subscribed <- true the.CwtchApp.QueryACNVersion() - gcd.Loaded() + + initialProfileLoad := false + if !reloadingAccounts { + initialProfileLoad = true + the.CwtchApp.LoadProfiles(the.AppPassword) + } + + var latestProfileSelectedTs int64 = 0 + var latestProfileSelectedOnion = "" for { e := q.Next() @@ -61,6 +69,9 @@ func App(gcd *ui.GrandCentralDispatcher, subscribed chan bool, reloadingAccounts if e.Data[event.Error] == event.AppErrLoaded0 { if reloadingAccounts { reloadingAccounts = false + } else if initialProfileLoad { + initialProfileLoad = false + gcd.Loaded() } else { gcd.ErrorLoaded0() } @@ -68,10 +79,28 @@ func App(gcd *ui.GrandCentralDispatcher, subscribed chan bool, reloadingAccounts case event.ReloadDone: reloadingAccounts = false + + // profilePane: 3 as per main.qml + if gcd.GlobalSettings.StateRootPane == 3 && latestProfileSelectedOnion != "" { + gcd.Broadcast("ResetMessagePane") + gcd.Broadcast("ResetProfile") + gcd.SetSelectedProfile(latestProfileSelectedOnion) + gcd.LoadProfile(latestProfileSelectedOnion) + gcd.ChangeRootPane(3) + } + + gcd.Loaded() + if len(the.CwtchApp.ListPeers()) == 0 { + initialProfileLoad = true the.CwtchApp.LoadProfiles(the.AppPassword) } case event.NewPeer: + if initialProfileLoad { + initialProfileLoad = false + gcd.Loaded() + } + onion := e.Data[event.Identity] p := the.CwtchApp.GetPeer(onion) @@ -90,7 +119,7 @@ func App(gcd *ui.GrandCentralDispatcher, subscribed chan bool, reloadingAccounts the.CwtchApp.AddPeerPlugin(onion, plugins.NETWORKCHECK) } - log.Infof("NewPeer for %v\n", onion) + log.Debugf("NewPeer for %v\n", onion) ui.AddProfile(gcd, onion) incSubscribed := make(chan bool) @@ -110,6 +139,17 @@ func App(gcd *ui.GrandCentralDispatcher, subscribed chan bool, reloadingAccounts if exists && blockUnkownPeers == "true" { the.EventBus.Publish(event.NewEvent(event.BlockUnknownPeers, map[event.Field]string{})) } + + if reloadingAccounts { + lastSelectedTime, ok := p.GetAttribute(attr.GetSettingsScope(constants.StateSelectedProfileTime)) + if ok { + lastSelectedTs, _ := strconv.ParseInt(lastSelectedTime, 10, 64) + if lastSelectedTs > latestProfileSelectedTs { + latestProfileSelectedTs = lastSelectedTs + latestProfileSelectedOnion = onion + } + } + } } } diff --git a/go/ui/gcd.go b/go/ui/gcd.go index f0c96d5f..ab0a1bf0 100644 --- a/go/ui/gcd.go +++ b/go/ui/gcd.go @@ -12,6 +12,7 @@ import ( "github.com/therecipe/qt/qml" "strconv" "sync" + "time" "cwtch.im/ui/go/the" "encoding/base32" @@ -35,8 +36,7 @@ type GrandCentralDispatcher struct { profileLock sync.Mutex conversationLock sync.Mutex - m_selectedProfile string - m_selectedConversation string + m_selectedProfile string _ int `property:"torStatus"` _ string `property:"os"` @@ -49,10 +49,13 @@ type GrandCentralDispatcher struct { _ string `property:"buildDate"` _ string `property:"assetPath"` _ string `property:"selectedProfile,auto"` - _ string `property:"selectedConversation,auto"` + _ string `property:"selectedConversation,auto,changed"` _ bool `property:"experimentsEnabled,auto,changed"` _ map[string]bool `property:"experiments,auto,changed"` + // general ui + _ func(pant int) `signal:"ChangeRootPane"` + _ func(pane int) `signal:"ChangeProfilePane"` // profile management stuff _ func() `signal:"Loaded"` _ func(handle, displayname, image, tag string, online bool) `signal:"AddProfile"` @@ -99,9 +102,11 @@ type GrandCentralDispatcher struct { _ func(onion, nick string, authorization string, storage string) `signal:"SupplyPeerSettings"` _ func(server string, key_types []string, keys []string) `signal:"SupplyServerSettings"` - // signals emitted from the ui (written in go, below) + // signals emitted from the ui (and implemented in go, below) // ui - _ func() `signal:"onActivate,auto"` + _ func() `signal:"onActivate,auto"` + _ func(pane int) `signal:"setRootPaneState",auto` + _ func(pane int) `signal:"setProfilePaneState",auto` // profile managemenet _ func(onion, nick string) `signal:"updateNick,auto"` _ func(handle string) `signal:"loadProfile,auto"` @@ -152,6 +157,15 @@ func (this *GrandCentralDispatcher) init() { this.SetExperimentsEnabled(this.GlobalSettings.ExperimentsEnabled) this.SetExperiments(this.GlobalSettings.Experiments) this.AndroidCwtchActivity = android.NewCwtchActivity(nil) + + // Per main.qml + // managementPane:0 settingsPane:1 addEditProfilePane:2 profilePane:3 addEditServerPane:4 + // We can't support addEditProfile(2) or addEditServer(4) as we don't store the target id for those yet: TODO + // We don't switch here to profilePane(3) as we need to wait for appHandler to identify and set the selectedProfile + // managementPane(0) is a NOP as it's default pane + if this.GlobalSettings.StateRootPane == 1 { + this.ChangeRootPane(this.GlobalSettings.StateRootPane) + } } // GetUiManager gets (and creates if required) a ui Manager for the supplied profile id @@ -178,6 +192,11 @@ func (this *GrandCentralDispatcher) setSelectedProfile(onion string) { this.profileLock.Lock() defer this.profileLock.Unlock() + p := the.CwtchApp.GetPeer(onion) + if p != nil { + p.SetAttribute(attr.GetSettingsScope(constants.StateSelectedProfileTime), strconv.FormatInt(time.Now().Unix(), 10)) + } + this.m_selectedProfile = onion } @@ -185,6 +204,11 @@ func (this *GrandCentralDispatcher) selectedProfileChanged(onion string) { this.SelectedProfileChanged(onion) } +func (this *GrandCentralDispatcher) setRootPaneState(pane int) { + this.GlobalSettings.StateRootPane = pane + WriteGlobalSettings(this.GlobalSettings) +} + // DoIfProfile performs a gcd action for a profile IF it is the currently selected profile in the UI // otherwise it does nothing. it also locks profile switching for the duration of the action func (this *GrandCentralDispatcher) DoIfProfile(profile string, fn func()) { @@ -208,7 +232,14 @@ func (this *GrandCentralDispatcher) DoIfProfileElse(profile string, fn func(), e } } -func (this *GrandCentralDispatcher) selectedConversation() string { +func (this *GrandCentralDispatcher) setProfilePaneState(pane int) { + this.profileLock.Lock() + defer this.profileLock.Unlock() + + the.Peer.SetAttribute(attr.GetSettingsScope(constants.StateProfilePane), strconv.Itoa(pane)) +} + +/*func (this *GrandCentralDispatcher) selectedConversation() string { this.conversationLock.Lock() defer this.conversationLock.Unlock() @@ -219,11 +250,13 @@ func (this *GrandCentralDispatcher) setSelectedConversation(handle string) { this.conversationLock.Lock() defer this.conversationLock.Unlock() + the.Peer.SetAttribute(attr.GetSettingsScope(constants.StateSelectedConversation), handle) + this.m_selectedConversation = handle -} +}*/ func (this *GrandCentralDispatcher) selectedConversationChanged(handle string) { - this.SelectedConversationChanged(handle) + the.Peer.SetAttribute(attr.GetSettingsScope(constants.StateSelectedConversation), handle) } // DoIfConversation performs a gcd action for a conversation IF it is the currently selected conversation in the UI @@ -232,7 +265,7 @@ func (this *GrandCentralDispatcher) DoIfConversation(conversation string, fn fun this.conversationLock.Lock() defer this.conversationLock.Unlock() - if this.m_selectedConversation == conversation { + if this.SelectedConversation() == conversation { fn() } } @@ -242,7 +275,7 @@ func (this *GrandCentralDispatcher) DoIfConversationElse(conversation string, fn this.conversationLock.Lock() defer this.conversationLock.Unlock() - if this.m_selectedConversation == conversation { + if this.SelectedConversation() == conversation { fn() } else { elseFn() @@ -788,6 +821,33 @@ func (this *GrandCentralDispatcher) loadProfile(onion string) { })) } } + + selectedPane, pok := the.Peer.GetAttribute(attr.GetSettingsScope(constants.StateProfilePane)) + if pok { + selectedPaneId, err := strconv.Atoi(selectedPane) + if err == nil { + // emptyPane:0 addPeerGroupPane:4 main.qml + if selectedPaneId != 0 && selectedPaneId != 4 { + selectedContact, cok := the.Peer.GetAttribute(attr.GetSettingsScope(constants.StateSelectedConversation)) + if cok { + this.Broadcast("ResetMessagePane") + this.SetSelectedConversation(selectedContact) + + this.TimelineInterface.handle = selectedContact + this.loadMessagesPane(selectedContact) + + if isPeer(selectedContact) { + this.requestPeerSettings(selectedContact) + } else { + this.requestGroupSettings(selectedContact) + } + this.ChangeProfilePane(selectedPaneId) + } + } else { + this.ChangeProfilePane(selectedPaneId) + } + } + } } func (this *GrandCentralDispatcher) createProfile(nick string, defaultPass bool, password string) { diff --git a/go/ui/manager.go b/go/ui/manager.go index 0828df4e..38461e2e 100644 --- a/go/ui/manager.go +++ b/go/ui/manager.go @@ -190,7 +190,7 @@ func AddProfile(gcd *GrandCentralDispatcher, handle string) { online, _ := p.GetAttribute(attr.GetLocalScope(constants.PeerOnline)) - log.Infof("AddProfile %v %v %v %v %v\n", handle, nick, picPath, tag, online) + log.Debugf("AddProfile %v %v %v %v %v\n", handle, nick, picPath, tag, online) gcd.AddProfile(handle, nick, picPath, tag, online == event.True) } } diff --git a/go/ui/settings.go b/go/ui/settings.go index f90d41ca..4e8b3530 100644 --- a/go/ui/settings.go +++ b/go/ui/settings.go @@ -22,6 +22,7 @@ type GlobalSettings struct { PreviousPid int64 ExperimentsEnabled bool Experiments map[string]bool + StateRootPane int } var DefaultGlobalSettings = GlobalSettings{ @@ -31,6 +32,7 @@ var DefaultGlobalSettings = GlobalSettings{ PreviousPid: -1, ExperimentsEnabled: false, Experiments: make(map[string]bool), + StateRootPane: 0, } func InitGlobalSettingsFile(directory string, password string) error { diff --git a/main.go b/main.go index 3f6d22a4..0998020f 100644 --- a/main.go +++ b/main.go @@ -69,7 +69,7 @@ func main() { logfileDefault := "cwtch_log.txt" - flagDebug := flag.Bool("debug", false, "turn on extra logging. WARNING: THIS MAY EXPOSE PRIVATE INFORMATION IN CONSOLE OUTPUT!") + flagDebug := flag.Bool("debug", false, "turn on extra debug level logging. WARNING: THIS MAY EXPOSE PRIVATE INFORMATION IN CONSOLE OUTPUT!") flagLogFile := flag.Bool("logfile", false, "instead of console output, log to $HOME/.cwtch/"+logfileDefault) flagLocal := flag.Bool("local", false, "load user interface from the local folder \"qml\" instead of the built-in UI") flagService := flag.Bool("service", false, "run this process as an android service") @@ -89,17 +89,14 @@ func main() { log.SetLevel(log.LevelInfo) } - // TESTING - if buildVer == "" { - log.SetLevel(log.LevelDebug) - } log.ExcludeFromPattern("connection/connection") - //log.ExcludeFromPattern("outbound/3dhauthchannel") log.ExcludeFromPattern("event/eventmanager") log.ExcludeFromPattern("service.go") log.ExcludeFromPattern("tor/BaseOnionService.go") log.ExcludeFromPattern("applications/auth.go") - //log.ExcludeFromPattern("connections/engine.go") + log.ExcludeFromPattern("connections/engine.go") + log.ExcludeFromPattern("bridge/pipeBridge.go") + log.ExcludeFromPattern("app/appBridge.go") log.Infoln("ui main()") @@ -225,10 +222,8 @@ func mainUi(flagLocal bool, flagClientUI bool) { gcd.TimelineInterface = ui.NewMessageModel(nil) engine.RootContext().SetContextProperty("mm", gcd.TimelineInterface) - engine.RootContext().SetContextProperty("androidCwtchActivity", gcd.AndroidCwtchActivity) - engine.Load(qmlSource) go loadNetworkingAndFiles(gcd, false, flagClientUI) @@ -328,8 +323,4 @@ func loadNetworkingAndFiles(gcd *ui.GrandCentralDispatcher, service bool, client go servers.LaunchServiceManager(gcd, the.ACN, path.Join(the.CwtchDir, "servers")) <-subscribed } - - if !service && !clientUI { - the.CwtchApp.LoadProfiles(the.AppPassword) - } } diff --git a/qml/main.qml b/qml/main.qml index d43eb020..fa20cc77 100644 --- a/qml/main.qml +++ b/qml/main.qml @@ -108,7 +108,7 @@ ApplicationWindow { onRightMenu: { // If a group is selected.... - if (gcd.selectedConversation.length == 32) { + if (Utils.isGroup(gcd.selectedConversation)) { theStack.pane = theStack.groupProfilePane gcd.requestGroupSettings(gcd.selectedConversation) } else { @@ -255,6 +255,7 @@ ApplicationWindow { ServerInfoPane { anchors.fill: parent } onCurrentIndexChanged: { + gcd.setProfilePaneState(theStack.currentIndex) parentStack.updateToolbar() if (currentIndex == emptyPane) { toolbar.hideTitle() @@ -284,6 +285,19 @@ ApplicationWindow { } } + Connections { + target: gcd + + onChangeRootPane: function(pane) { + parentStack.currentIndex = pane + } + + onChangeProfilePane: function(pane) { + theStack.currentIndex = pane + } + } + + focus: true Keys.onPressed: { if (event.key == Qt.Key_Back) { @@ -292,7 +306,11 @@ ApplicationWindow { } } - onCurrentIndexChanged : { updateToolbar(); statusbar.resetHeight() } + onCurrentIndexChanged : { + gcd.setRootPaneState(parentStack.currentIndex) + updateToolbar(); + statusbar.resetHeight() + } function updateToolbar() { if (rootStack.splash == true) { diff --git a/qml/panes/AddPeerGroupPane.qml b/qml/panes/AddPeerGroupPane.qml index cc60c9e4..e568cc3f 100644 --- a/qml/panes/AddPeerGroupPane.qml +++ b/qml/panes/AddPeerGroupPane.qml @@ -224,5 +224,4 @@ Rectangle { } - } diff --git a/qml/widgets/ContactList.qml b/qml/widgets/ContactList.qml index 193c674b..6758d303 100644 --- a/qml/widgets/ContactList.qml +++ b/qml/widgets/ContactList.qml @@ -125,7 +125,7 @@ ColumnLayout { "_authorization": authorization, "_loading": loading, "_loading": loading, - "_lastMsgTs": lastMsgTs + "_lastMsgTs": lastMsgTs, } model.insert(index, newContact) @@ -173,6 +173,7 @@ ColumnLayout { rowColor: (_authorization == Const.auth_unknown) ? Theme.backgroundHilightElementColor : Theme.backgroundMainColor Layout.fillWidth: true visible: filterContact(displayName, handle) + isActive: gcd.selectedConversation == _handle } } diff --git a/qml/widgets/ContactRow.qml b/qml/widgets/ContactRow.qml index ddb93340..7078f5e9 100644 --- a/qml/widgets/ContactRow.qml +++ b/qml/widgets/ContactRow.qml @@ -98,7 +98,6 @@ Opaque.PortraitRow { onClicked: function() { gcd.broadcast("ResetMessagePane") - isActive = true theStack.pane = theStack.messagePane mm.setHandle(handle) gcd.loadMessagesPane(handle) @@ -149,10 +148,19 @@ Opaque.PortraitRow { } } - onIncContactUnreadCount: function(handle) { + onIncContactUnreadCount: function(_handle) { if (handle == _handle && gcd.selectedConversation != handle) { badge++ } } + + onSelectedConversationChanged: function() { + if (handle == gcd.selectedConversation) { + isActive = true + badge = 0 + } else { + isActive = false + } + } } }