refactor(@desktop/chat-communities): initial model for the chat/community sections

Model used for list of chats in case of Chat section and used for
channels/categories/category channels is initially added. With an option for
setting active chat/channel/category (which are an Item type) and setting active
channel within a category (which is a SubItem type).
This commit is contained in:
Sale Djenic 2021-10-27 14:20:11 +02:00
parent b15c348931
commit d8dea2dc58
54 changed files with 1489 additions and 58 deletions

View File

@ -0,0 +1,96 @@
import NimQml
import item, sub_item, active_sub_item
QtObject:
type ActiveItem* = ref object of QObject
item: Item
activeSubItem: ActiveSubItem
activeSubItemVariant: QVariant
proc setup(self: ActiveItem) =
self.QObject.setup
self.activeSubItem = newActiveSubItem()
self.activeSubItemVariant = newQVariant(self.activeSubItem)
proc delete*(self: ActiveItem) =
self.activeSubItem.delete
self.activeSubItemVariant.delete
self.QObject.delete
proc newActiveItem*(): ActiveItem =
new(result, delete)
result.setup
#################################################
# Forward declaration section
proc activeSubItemChanged(self: ActiveItem) {.signal.}
#################################################
proc setActiveItemData*(self: ActiveItem, item: Item, subItem: SubItem) =
self.item = item
self.activeSubItem.setActiveSubItemData(subItem)
self.activeSubItemChanged()
proc getId(self: ActiveItem): string {.slot.} =
return self.item.id
QtProperty[string] id:
read = getId
proc getName(self: ActiveItem): string {.slot.} =
return self.item.name
QtProperty[string] name:
read = getName
proc getIcon(self: ActiveItem): string {.slot.} =
return self.item.icon
QtProperty[string] icon:
read = getIcon
proc getColor(self: ActiveItem): string {.slot.} =
return self.item.color
QtProperty[string] color:
read = getColor
proc getDescription(self: ActiveItem): string {.slot.} =
return self.item.description
QtProperty[string] description:
read = getDescription
proc getType(self: ActiveItem): int {.slot.} =
return self.item.`type`
QtProperty[int] type:
read = getType
proc getHasNotification(self: ActiveItem): bool {.slot.} =
return self.item.hasNotification
QtProperty[bool] hasNotification:
read = getHasNotification
proc getNotificationCount(self: ActiveItem): int {.slot.} =
return self.item.notificationsCount
QtProperty[int] notificationCount:
read = getNotificationCount
proc getMuted(self: ActiveItem): bool {.slot.} =
return self.item.muted
QtProperty[bool] muted:
read = getMuted
proc getActiveSubItem(self: ActiveItem): QVariant {.slot.} =
return self.activeSubItemVariant
QtProperty[QVariant] activeSubItem:
read = getActiveSubItem
notify = activeSubItemChanged

View File

@ -0,0 +1,69 @@
import NimQml
import sub_item
QtObject:
type ActiveSubItem* = ref object of QObject
item: SubItem
proc setup(self: ActiveSubItem) =
self.QObject.setup
proc delete*(self: ActiveSubItem) =
self.QObject.delete
proc newActiveSubItem*(): ActiveSubItem =
new(result, delete)
result.setup
proc setActiveSubItemData*(self: ActiveSubItem, item: SubItem) =
self.item = item
proc getId(self: ActiveSubItem): string {.slot.} =
return self.item.id
QtProperty[string] id:
read = getId
proc getName(self: ActiveSubItem): string {.slot.} =
return self.item.name
QtProperty[string] name:
read = getName
proc getIcon(self: ActiveSubItem): string {.slot.} =
return self.item.icon
QtProperty[string] icon:
read = getIcon
proc getColor(self: ActiveSubItem): string {.slot.} =
return self.item.color
QtProperty[string] color:
read = getColor
proc getDescription(self: ActiveSubItem): string {.slot.} =
return self.item.description
QtProperty[string] description:
read = getDescription
proc getHasNotification(self: ActiveSubItem): bool {.slot.} =
return self.item.hasNotification
QtProperty[bool] hasNotification:
read = getHasNotification
proc getNotificationCount(self: ActiveSubItem): int {.slot.} =
return self.item.notificationsCount
QtProperty[int] notificationCount:
read = getNotificationCount
proc getMuted(self: ActiveSubItem): bool {.slot.} =
return self.item.muted
QtProperty[bool] muted:
read = getMuted

View File

@ -0,0 +1,70 @@
type
BaseItem* {.pure inheritable.} = ref object of RootObj
id: string
name: string
icon: string
color: string
description: string
hasNotification: bool
notificationsCount: int
muted: bool
active: bool
proc setup*(self: BaseItem, id, name, icon, color, description: string, hasNotification: bool, notificationsCount: int,
muted, active: bool) =
self.id = id
self.name = name
self.icon = icon
self.color = color
self.description = description
self.hasNotification = hasNotification
self.notificationsCount = notificationsCount
self.muted = muted
self.active = active
proc initBaseItem*(id, name, icon, color, description: string, hasNotification: bool, notificationsCount: int,
muted, active: bool): BaseItem =
result = BaseItem()
result.setup(id, name, icon, color, description, hasNotification, notificationsCount, muted, active)
proc delete*(self: BaseItem) =
discard
method id*(self: BaseItem): string {.inline base.} =
self.id
method name*(self: BaseItem): string {.inline base.} =
self.name
method icon*(self: BaseItem): string {.inline base.} =
self.icon
method color*(self: BaseItem): string {.inline base.} =
self.color
method description*(self: BaseItem): string {.inline base.} =
self.description
method hasNotification*(self: BaseItem): bool {.inline base.} =
self.hasNotification
method `hasNotification=`*(self: var BaseItem, value: bool) {.inline base.} =
self.hasNotification = value
method notificationsCount*(self: BaseItem): int {.inline base.} =
self.notificationsCount
method `notificationsCount=`*(self: var BaseItem, value: int) {.inline base.} =
self.notificationsCount = value
method muted*(self: BaseItem): bool {.inline base.} =
self.muted
method `muted=`*(self: var BaseItem, value: bool) {.inline base.} =
self.muted = value
method active*(self: BaseItem): bool {.inline base.} =
self.active
method `active=`*(self: var BaseItem, value: bool) {.inline base.} =
self.active = value

View File

@ -3,6 +3,7 @@ import Tables
import controller_interface
import io_interface
import ../../../../app_service/service/chat/service as chat_service
import ../../../../app_service/service/community/service as community_service
export controller_interface
@ -12,16 +13,19 @@ type
delegate: io_interface.AccessInterface
id: string
isCommunityModule: bool
activeItemId: string
activeSubItemId: string
chatService: chat_service.ServiceInterface
communityService: community_service.ServiceInterface
proc newController*(delegate: io_interface.AccessInterface,
id: string, isCommunity: bool,
communityService: community_service.ServiceInterface):
Controller =
proc newController*(delegate: io_interface.AccessInterface, id: string, isCommunity: bool,
chatService: chat_service.ServiceInterface,
communityService: community_service.ServiceInterface): Controller =
result = Controller()
result.delegate = delegate
result.id = id
result.isCommunityModule = isCommunity
result.chatService = chatService
result.communityService = communityService
method delete*(self: Controller) =
@ -36,5 +40,26 @@ method getId*(self: Controller): string =
method isCommunity*(self: Controller): bool =
return self.isCommunityModule
method getCommunities*(self: Controller): seq[community_service.CommunityDto] =
return self.communityService.getCommunities()
method getCommunityIds*(self: Controller): seq[string] =
return self.communityService.getCommunityIds()
method getCategories*(self: Controller, communityId: string): seq[Category] =
return self.communityService.getCategories(communityId)
method getChats*(self: Controller, communityId: string, categoryId: string): seq[Chat] =
return self.communityService.getChats(communityId, categoryId)
method getChatDetails*(self: Controller, communityId, chatId: string): ChatDto =
let fullId = communityId & chatId
return self.chatService.getChatById(fullId)
method getChatDetailsForChatTypes*(self: Controller, types: seq[ChatType]): seq[ChatDto] =
return self.chatService.getChatsOfChatTypes(types)
method setActiveItemSubItem*(self: Controller, itemId: string, subItemId: string) =
self.activeItemId = itemId
self.activeSubItemId = subItemId
# We need to take other actions here like notify status go that unviewed mentions count is updated and so...
self.delegate.activeItemSubItemSet(self.activeItemId, self.activeSubItemId)

View File

@ -1,3 +1,4 @@
import ../../../../app_service/service/chat/service_interface as chat_service
import ../../../../app_service/service/community/service_interface as community_service
type
@ -16,7 +17,20 @@ method getId*(self: AccessInterface): string {.base.} =
method isCommunity*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")
method getCommunities*(self: AccessInterface):
seq[community_service.CommunityDto] {.base.} =
method getCommunityIds*(self: AccessInterface): seq[string] {.base.} =
raise newException(ValueError, "No implementation available")
method getCategories*(self: AccessInterface, communityId: string): seq[Category] {.base.} =
raise newException(ValueError, "No implementation available")
method getChats*(self: AccessInterface, communityId: string, categoryId: string): seq[Chat] {.base.} =
raise newException(ValueError, "No implementation available")
method getChatDetails*(self: AccessInterface, communityId, chatId: string): ChatDto {.base.} =
raise newException(ValueError, "No implementation available")
method getChatDetailsForChatTypes*(self: AccessInterface, types: seq[ChatType]): seq[ChatDto] {.base.} =
raise newException(ValueError, "No implementation available")
method setActiveItemSubItem*(self: AccessInterface, itemId: string, subItemId: string) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,33 @@
import controller_interface
import io_interface
import ../../../../../app_service/service/community/service as community_service
export controller_interface
type
Controller* = ref object of controller_interface.AccessInterface
delegate: io_interface.AccessInterface
id: string
isCommunityModule: bool
communityService: community_service.ServiceInterface
proc newController*(delegate: io_interface.AccessInterface, id: string, isCommunity: bool,
communityService: community_service.ServiceInterface): Controller =
result = Controller()
result.delegate = delegate
result.id = id
result.isCommunityModule = isCommunity
result.communityService = communityService
method delete*(self: Controller) =
discard
method init*(self: Controller) =
discard
method getId*(self: Controller): string =
return self.id
method isCommunity*(self: Controller): bool =
return self.isCommunityModule

View File

@ -0,0 +1,19 @@
import ../../../../../app_service/service/community/service_interface as community_service
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
## Abstract class for any input/interaction with this module.
method delete*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method init*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method getId*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available")
method isCommunity*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,12 @@
# Defines how parent module accesses this module
include ./private_interfaces/module_base_interface
include ./private_interfaces/module_access_interface
# Defines how this module view communicates with this module
include ./private_interfaces/module_view_delegate_interface
# Defines how this controller communicates with this module
include ./private_interfaces/module_controller_delegate_interface
# Defines how submodules of this module communicate with this module
# will be added if needed

View File

@ -0,0 +1,21 @@
import NimQml
QtObject:
type
Item* = ref object of QObject
proc setup(self: Item) =
self.QObject.setup
proc delete*(self: Item) =
self.QObject.delete
proc newItem*(): Item =
new(result, delete)
result.setup()
proc id*(self: Item): string {.slot.} =
self.id
QtProperty[string] id:
read = id

View File

@ -0,0 +1,17 @@
import NimQml
import item
QtObject:
type
Model* = ref object of QAbstractListModel
sections: seq[Item]
proc setup(self: Model) =
self.QAbstractListModel.setup
proc delete*(self: Model) =
self.QAbstractListModel.delete
proc newModel*(): Model =
new(result, delete)
result.setup

View File

@ -0,0 +1,46 @@
import NimQml
import io_interface
import ../io_interface as delegate_interface
import view, controller
import ../../../../core/global_singleton
import ../../../../../app_service/service/chat/service as chat_service
import ../../../../../app_service/service/community/service as community_service
export io_interface
type
Module* = ref object of io_interface.AccessInterface
delegate: delegate_interface.AccessInterface
view: View
viewVariant: QVariant
controller: controller.AccessInterface
moduleLoaded: bool
proc newModule*(delegate: delegate_interface.AccessInterface, id: string, isCommunity: bool,
chatService: chat_service.Service, communityService: community_service.Service):
Module =
result = Module()
result.delegate = delegate
result.view = view.newView(result)
result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, id, isCommunity, communityService)
result.moduleLoaded = false
method delete*(self: Module) =
self.view.delete
self.viewVariant.delete
self.controller.delete
method load*(self: Module) =
singletonInstance.engine.setRootContextProperty("inputAreaModule", self.viewVariant)
self.controller.init()
self.view.load()
method isLoaded*(self: Module): bool =
return self.moduleLoaded
method viewDidLoad*(self: Module) =
self.moduleLoaded = true
self.delegate.inputAreaDidLoad()

View File

@ -0,0 +1,8 @@
method delete*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method load*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method isLoaded*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,5 @@
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
# Since nim doesn't support using concepts in second level nested types we
# define delegate interfaces within access interface.

View File

@ -0,0 +1,2 @@
method viewDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,22 @@
import NimQml
import model
import io_interface
QtObject:
type
View* = ref object of QObject
delegate: io_interface.AccessInterface
model: Model
proc delete*(self: View) =
self.model.delete
self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete)
result.QObject.setup
result.delegate = delegate
result.model = newModel()
proc load*(self: View) =
self.delegate.viewDidLoad()

View File

@ -9,4 +9,6 @@ include ./private_interfaces/module_view_delegate_interface
include ./private_interfaces/module_controller_delegate_interface
# Defines how submodules of this module communicate with this module
# will be added if needed
include ./private_interfaces/module_input_area_delegate_interface
include ./private_interfaces/module_messages_delegate_interface
include ./private_interfaces/module_users_delegate_interface

View File

@ -1,21 +1,55 @@
import NimQml
import strformat
import base_item, sub_model, sub_item
QtObject:
type
Item* = ref object of QObject
proc setup(self: Item) =
self.QObject.setup
type
Item* = ref object of BaseItem
`type`: int
subItems: SubModel
proc delete*(self: Item) =
self.QObject.delete
proc initItem*(id, name, icon, color, description: string, `type`: int, hasNotification: bool, notificationsCount: int,
muted, active: bool): Item =
result = Item()
result.setup(id, name, icon, color, description, hasNotification, notificationsCount, muted, active)
result.`type` = `type`
result.subItems = newSubModel()
proc newItem*(): Item =
new(result, delete)
result.setup()
proc delete*(self: Item) =
self.subItems.delete
self.BaseItem.delete
proc id*(self: Item): string {.slot.} =
self.id
proc subItems*(self: Item): SubModel {.inline.} =
self.subItems
QtProperty[string] id:
read = id
proc type*(self: Item): int {.inline.} =
self.`type`
proc `$`*(self: Item): string =
result = fmt"""ChatSectionItem(
id: {self.id},
name: {self.name},
icon: {self.icon},
color: {self.color},
description: {self.description},
type: {self.`type`},
hasNotification: {self.hasNotification},
notificationsCount: {self.notificationsCount},
muted: {self.muted},
active: {self.active},
subItems:[
{$self.subItems}
]"""
proc appendSubItems*(self: Item, items: seq[SubItem]) =
self.subItems.appendItems(items)
proc appendSubItem*(self: Item, item: SubItem) =
self.subItems.appendItem(item)
proc prependSubItems*(self: Item, items: seq[SubItem]) =
self.subItems.prependItems(items)
proc prependSubItem*(self: Item, item: SubItem) =
self.subItems.prependItem(item)
proc setActiveSubItem*(self: Item, subItemId: string) =
self.subItems.setActiveItem(subItemId)

View File

@ -0,0 +1,33 @@
import controller_interface
import io_interface
import ../../../../../app_service/service/community/service as community_service
export controller_interface
type
Controller* = ref object of controller_interface.AccessInterface
delegate: io_interface.AccessInterface
id: string
isCommunityModule: bool
communityService: community_service.ServiceInterface
proc newController*(delegate: io_interface.AccessInterface, id: string, isCommunity: bool,
communityService: community_service.ServiceInterface): Controller =
result = Controller()
result.delegate = delegate
result.id = id
result.isCommunityModule = isCommunity
result.communityService = communityService
method delete*(self: Controller) =
discard
method init*(self: Controller) =
discard
method getId*(self: Controller): string =
return self.id
method isCommunity*(self: Controller): bool =
return self.isCommunityModule

View File

@ -0,0 +1,19 @@
import ../../../../../app_service/service/community/service_interface as community_service
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
## Abstract class for any input/interaction with this module.
method delete*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method init*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method getId*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available")
method isCommunity*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,12 @@
# Defines how parent module accesses this module
include ./private_interfaces/module_base_interface
include ./private_interfaces/module_access_interface
# Defines how this module view communicates with this module
include ./private_interfaces/module_view_delegate_interface
# Defines how this controller communicates with this module
include ./private_interfaces/module_controller_delegate_interface
# Defines how submodules of this module communicate with this module
# will be added if needed

View File

@ -0,0 +1,21 @@
import NimQml
QtObject:
type
Item* = ref object of QObject
proc setup(self: Item) =
self.QObject.setup
proc delete*(self: Item) =
self.QObject.delete
proc newItem*(): Item =
new(result, delete)
result.setup()
proc id*(self: Item): string {.slot.} =
self.id
QtProperty[string] id:
read = id

View File

@ -0,0 +1,17 @@
import NimQml
import item
QtObject:
type
Model* = ref object of QAbstractListModel
sections: seq[Item]
proc setup(self: Model) =
self.QAbstractListModel.setup
proc delete*(self: Model) =
self.QAbstractListModel.delete
proc newModel*(): Model =
new(result, delete)
result.setup

View File

@ -0,0 +1,46 @@
import NimQml
import io_interface
import ../io_interface as delegate_interface
import view, controller
import ../../../../core/global_singleton
import ../../../../../app_service/service/chat/service as chat_service
import ../../../../../app_service/service/community/service as community_service
export io_interface
type
Module* = ref object of io_interface.AccessInterface
delegate: delegate_interface.AccessInterface
view: View
viewVariant: QVariant
controller: controller.AccessInterface
moduleLoaded: bool
proc newModule*(delegate: delegate_interface.AccessInterface, id: string, isCommunity: bool,
chatService: chat_service.Service, communityService: community_service.Service):
Module =
result = Module()
result.delegate = delegate
result.view = view.newView(result)
result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, id, isCommunity, communityService)
result.moduleLoaded = false
method delete*(self: Module) =
self.view.delete
self.viewVariant.delete
self.controller.delete
method load*(self: Module) =
singletonInstance.engine.setRootContextProperty("messagesModule", self.viewVariant)
self.controller.init()
self.view.load()
method isLoaded*(self: Module): bool =
return self.moduleLoaded
method viewDidLoad*(self: Module) =
self.moduleLoaded = true
self.delegate.messagesDidLoad()

View File

@ -0,0 +1,8 @@
method delete*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method load*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method isLoaded*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,5 @@
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
# Since nim doesn't support using concepts in second level nested types we
# define delegate interfaces within access interface.

View File

@ -0,0 +1,2 @@
method viewDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,22 @@
import NimQml
import model
import io_interface
QtObject:
type
View* = ref object of QObject
delegate: io_interface.AccessInterface
model: Model
proc delete*(self: View) =
self.model.delete
self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete)
result.QObject.setup
result.delegate = delegate
result.model = newModel()
proc load*(self: View) =
self.delegate.viewDidLoad()

View File

@ -1,17 +1,142 @@
import NimQml
import item
import NimQml, Tables, strutils, strformat
import item, base_item
type
ModelRole {.pure.} = enum
Id = UserRole + 1
Name
Icon
Color
Description
Type
HasNotification
NotificationsCount
Muted
Active
SubItems
QtObject:
type
type
Model* = ref object of QAbstractListModel
sections: seq[Item]
proc setup(self: Model) =
self.QAbstractListModel.setup
items: seq[Item]
proc delete*(self: Model) =
for i in 0 ..< self.items.len:
self.items[i].delete
self.items = @[]
self.QAbstractListModel.delete
proc setup(self: Model) =
self.QAbstractListModel.setup
proc newModel*(): Model =
new(result, delete)
result.setup
result.setup
proc `$`*(self: Model): string =
for i in 0 ..< self.items.len:
result &= fmt"""
[{i}]:({$self.items[i]})
"""
proc countChanged(self: Model) {.signal.}
proc getCount(self: Model): int {.slot.} =
self.items.len
QtProperty[int] count:
read = getCount
notify = countChanged
method rowCount(self: Model, index: QModelIndex = nil): int =
return self.items.len
method roleNames(self: Model): Table[int, string] =
{
ModelRole.Id.int:"id",
ModelRole.Name.int:"name",
ModelRole.Icon.int:"icon",
ModelRole.Color.int:"color",
ModelRole.Description.int:"description",
ModelRole.Type.int:"type",
ModelRole.HasNotification.int:"hasNotification",
ModelRole.NotificationsCount.int:"notificationsCount",
ModelRole.Muted.int:"muted",
ModelRole.Active.int:"active",
ModelRole.SubItems.int:"subItems",
}.toTable
method data(self: Model, index: QModelIndex, role: int): QVariant =
if (not index.isValid):
return
if (index.row < 0 or index.row >= self.items.len):
return
let item = self.items[index.row]
let enumRole = role.ModelRole
case enumRole:
of ModelRole.Id:
result = newQVariant(item.id)
of ModelRole.Name:
result = newQVariant(item.name)
of ModelRole.Icon:
result = newQVariant(item.icon)
of ModelRole.Color:
result = newQVariant(item.color)
of ModelRole.Description:
result = newQVariant(item.description)
of ModelRole.Type:
result = newQVariant(item.`type`)
of ModelRole.HasNotification:
result = newQVariant(item.hasNotification)
of ModelRole.NotificationsCount:
result = newQVariant(item.notificationsCount)
of ModelRole.Muted:
result = newQVariant(item.muted)
of ModelRole.Active:
result = newQVariant(item.active)
of ModelRole.SubItems:
result = newQVariant(item.subItems)
proc appendItem*(self: Model, item: Item) =
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
self.beginInsertRows(parentModelIndex, self.items.len, self.items.len)
self.items.add(item)
self.endInsertRows()
self.countChanged()
proc prependItem*(self: Model, item: Item) =
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
self.beginInsertRows(parentModelIndex, 0, 0)
self.items = item & self.items
self.endInsertRows()
self.countChanged()
proc getItemById*(self: Model, id: string): Item =
for it in self.items:
if(it.id == id):
return it
proc setActiveItemSubItem*(self: Model, id: string, subItemId: string) =
for i in 0 ..< self.items.len:
self.items[i].setActiveSubItem(subItemId)
if(self.items[i].active):
let index = self.createIndex(i, 0, nil)
self.items[i].BaseItem.active = false
self.dataChanged(index, index, @[ModelRole.Active.int])
if(self.items[i].id == id):
let index = self.createIndex(i, 0, nil)
self.items[i].BaseItem.active = true
self.dataChanged(index, index, @[ModelRole.Active.int])

View File

@ -1,56 +1,187 @@
import NimQml
import NimQml, chronicles
import io_interface
import ../io_interface as delegate_interface
import view, controller
import view, controller, item, sub_item, model, sub_model
import ../../../core/global_singleton
import input_area/module as input_area_module
import messages/module as messages_module
import users/module as users_module
import ../../../../app_service/service/chat/service as chat_service
import ../../../../app_service/service/community/service as community_service
export io_interface
logScope:
topics = "chat-section-module"
type
Module* = ref object of io_interface.AccessInterface
delegate: delegate_interface.AccessInterface
view: View
viewVariant: QVariant
controller: controller.AccessInterface
inputAreaModule: input_area_module.AccessInterface
messagesModule: messages_module.AccessInterface
usersModule: users_module.AccessInterface
moduleLoaded: bool
proc newModule*(delegate: delegate_interface.AccessInterface, id: string,
isCommunity: bool, chatService: chat_service.Service,
communityService: community_service.Service):
proc newModule*(delegate: delegate_interface.AccessInterface, id: string, isCommunity: bool,
chatService: chat_service.Service, communityService: community_service.Service):
Module =
result = Module()
result.delegate = delegate
result.view = view.newView(result)
result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, id, isCommunity,
communityService)
result.controller = controller.newController(result, id, isCommunity, chatService, communityService)
result.moduleLoaded = false
result.inputAreaModule = input_area_module.newModule(result, id, isCommunity, chatService, communityService)
result.messagesModule = messages_module.newModule(result, id, isCommunity, chatService, communityService)
result.usersModule = users_module.newModule(result, id, isCommunity, chatService, communityService)
method delete*(self: Module) =
self.view.delete
self.viewVariant.delete
self.controller.delete
proc buildChatUI(self: Module) =
let types = @[ChatType.OneToOne, ChatType.Public, ChatType.PrivateGroupChat, ChatType.Profile]
let chats = self.controller.getChatDetailsForChatTypes(types)
var selectedItemId = ""
for c in chats:
let hasNotification = c.unviewedMessagesCount > 0 or c.unviewedMentionsCount > 0
let notificationsCount = c.unviewedMentionsCount
let item = initItem(c.id, if c.alias.len > 0: c.alias else: c.name, c.identicon, c.color, c.description,
c.chatType.int, hasNotification, notificationsCount, c.muted, false)
self.view.appendItem(item)
# make the first chat active when load the app
if(selectedItemId.len == 0):
selectedItemId = item.id
self.controller.setActiveItemSubItem(selectedItemId, "")
proc buildCommunityUI(self: Module) =
var selectedItemId = ""
var selectedSubItemId = ""
let communityIds = self.controller.getCommunityIds()
for cId in communityIds:
# handle channels which don't belong to any category
let chats = self.controller.getChats(cId, "")
for c in chats:
let chatDto = self.controller.getChatDetails(cId, c.id)
let hasNotification = chatDto.unviewedMessagesCount > 0 or chatDto.unviewedMentionsCount > 0
let notificationsCount = chatDto.unviewedMentionsCount
let channelItem = initItem(chatDto.id, if chatDto.alias.len > 0: chatDto.alias else: chatDto.name,
chatDto.identicon, chatDto.color, chatDto.description, chatDto.chatType.int, hasNotification, notificationsCount,
chatDto.muted, false)
self.view.appendItem(channelItem)
# make the first channel which doesn't belong to any category active when load the app
if(selectedItemId.len == 0):
selectedItemId = channelItem.id
# handle categories and channels for each category
let categories = self.controller.getCategories(cId)
for cat in categories:
var hasNotificationPerCategory = false
var notificationsCountPerCategory = 0
var categoryChannels: seq[SubItem]
let categoryChats = self.controller.getChats(cId, cat.id)
for c in categoryChats:
let chatDto = self.controller.getChatDetails(cId, c.id)
let hasNotification = chatDto.unviewedMessagesCount > 0 or chatDto.unviewedMentionsCount > 0
let notificationsCount = chatDto.unviewedMentionsCount
hasNotificationPerCategory = hasNotificationPerCategory or hasNotification
notificationsCountPerCategory += notificationsCount
let channelItem = initSubItem(chatDto.id, if chatDto.alias.len > 0: chatDto.alias else: chatDto.name,
chatDto.identicon, chatDto.color, chatDto.description, hasNotification, notificationsCount, chatDto.muted,
false)
categoryChannels.add(channelItem)
# in case there is no channels beyond categories,
# make the first channel of the first category active when load the app
if(selectedItemId.len == 0):
selectedItemId = cat.id
selectedSubItemId = channelItem.id
var categoryItem = initItem(cat.id, cat.name, "", "", "", ChatType.Unknown.int, hasNotificationPerCategory,
notificationsCountPerCategory, false, false)
categoryItem.prependSubItems(categoryChannels)
self.view.appendItem(categoryItem)
self.setActiveItemSubItem(selectedItemId, selectedSubItemId)
method load*(self: Module) =
if(self.controller.isCommunity()):
singletonInstance.engine.setRootContextProperty("communitySectionModule",
self.viewVariant)
else:
singletonInstance.engine.setRootContextProperty("chatSectionModule",
self.viewVariant)
self.controller.init()
self.view.load()
if(self.controller.isCommunity()):
singletonInstance.engine.setRootContextProperty("communitySectionModule", self.viewVariant)
self.buildCommunityUI()
else:
singletonInstance.engine.setRootContextProperty("chatSectionModule", self.viewVariant)
self.buildChatUI()
self.inputAreaModule.load()
self.messagesModule.load()
self.usersModule.load()
proc checkIfModuleDidLoad(self: Module) =
if self.moduleLoaded:
return
if(not self.inputAreaModule.isLoaded()):
return
if (not self.messagesModule.isLoaded()):
return
if (not self.usersModule.isLoaded()):
return
self.moduleLoaded = true
if(self.controller.isCommunity()):
self.delegate.communitySectionDidLoad()
else:
self.delegate.chatSectionDidLoad()
method isLoaded*(self: Module): bool =
return self.moduleLoaded
method viewDidLoad*(self: Module) =
self.moduleLoaded = true
if(self.controller.isCommunity()):
self.delegate.communitySectionDidLoad()
else:
self.delegate.chatSectionDidLoad()
self.checkIfModuleDidLoad()
method inputAreaDidLoad*(self: Module) =
self.checkIfModuleDidLoad()
method messagesDidLoad*(self: Module) =
self.checkIfModuleDidLoad()
method usersDidLoad*(self: Module) =
self.checkIfModuleDidLoad()
method setActiveItemSubItem*(self: Module, itemId: string, subItemId: string) =
self.controller.setActiveItemSubItem(itemId, subItemId)
method activeItemSubItemSet*(self: Module, itemId: string, subItemId: string) =
let item = self.view.model().getItemById(itemId)
if(item.isNil):
# Should never be here
error "chat-view unexisting item id: ", itemId
return
# Chats from Chat section and chats from Community section which don't belong
# to any category have empty `subItemId`
let subItem = item.subItems.getItemById(subItemId)
self.view.model().setActiveItemSubItem(itemId, subItemId)
self.view.activeItemSubItemSet(item, subItem)

View File

@ -0,0 +1,2 @@
method activeItemSubItemSet*(self: AccessInterface, itemId: string, subItemId: string) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,2 @@
method inputAreaDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,2 @@
method messagesDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,2 @@
method usersDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -1,2 +1,5 @@
method viewDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
raise newException(ValueError, "No implementation available")
method setActiveItemSubItem*(self: AccessInterface, itemId: string, subItemId: string) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,28 @@
import strformat
import base_item
export base_item
type
SubItem* = ref object of BaseItem
proc initSubItem*(id, name, icon, color, description: string, hasNotification: bool, notificationsCount: int,
muted, active: bool): SubItem =
result = SubItem()
result.setup(id, name, icon, color, description, hasNotification, notificationsCount, muted, active)
proc delete*(self: SubItem) =
self.BaseItem.delete
proc `$`*(self: SubItem): string =
result = fmt"""ChatSectionSubItem(
id: {self.id},
name: {self.name},
icon: {self.icon},
color: {self.color},
description: {self.description},
hasNotification: {self.hasNotification},
notificationsCount: {self.notificationsCount},
muted: {self.muted},
active: {self.active}
]"""

View File

@ -0,0 +1,156 @@
import NimQml, Tables, strformat
import sub_item
type
ModelRole {.pure.} = enum
Id = UserRole + 1
Name
Icon
Color
Description
HasNotification
NotificationsCount
Muted
Active
QtObject:
type
SubModel* = ref object of QAbstractListModel
items: seq[SubItem]
proc delete*(self: SubModel) =
for i in 0 ..< self.items.len:
self.items[i].delete
self.items = @[]
self.QAbstractListModel.delete
proc setup(self: SubModel) =
self.QAbstractListModel.setup
proc newSubModel*(): SubModel =
new(result, delete)
result.setup
proc `$`*(self: SubModel): string =
for i in 0 ..< self.items.len:
result &= fmt"""
[{i}]:({$self.items[i]})
"""
proc countChanged(self: SubModel) {.signal.}
proc getCount(self: SubModel): int {.slot.} =
self.items.len
QtProperty[int] count:
read = getCount
notify = countChanged
method rowCount(self: SubModel, index: QModelIndex = nil): int =
return self.items.len
method roleNames(self: SubModel): Table[int, string] =
{
ModelRole.Id.int:"id",
ModelRole.Name.int:"name",
ModelRole.Icon.int:"icon",
ModelRole.Color.int:"color",
ModelRole.Description.int:"description",
ModelRole.HasNotification.int:"hasNotification",
ModelRole.NotificationsCount.int:"notificationsCount",
ModelRole.Muted.int:"muted",
ModelRole.Active.int:"active"
}.toTable
method data(self: SubModel, index: QModelIndex, role: int): QVariant =
if (not index.isValid):
return
if (index.row < 0 or index.row >= self.items.len):
return
let item = self.items[index.row]
let enumRole = role.ModelRole
case enumRole:
of ModelRole.Id:
result = newQVariant(item.id)
of ModelRole.Name:
result = newQVariant(item.name)
of ModelRole.Icon:
result = newQVariant(item.icon)
of ModelRole.Color:
result = newQVariant(item.color)
of ModelRole.Description:
result = newQVariant(item.description)
of ModelRole.HasNotification:
result = newQVariant(item.hasNotification)
of ModelRole.NotificationsCount:
result = newQVariant(item.notificationsCount)
of ModelRole.Muted:
result = newQVariant(item.muted)
of ModelRole.Active:
result = newQVariant(item.active)
proc appendItems*(self: SubModel, items: seq[SubItem]) =
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
let first = self.items.len
let last = first + items.len - 1
self.beginInsertRows(parentModelIndex, first, last)
self.items.add(items)
self.endInsertRows()
self.countChanged()
proc appendItem*(self: SubModel, item: SubItem) =
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
self.beginInsertRows(parentModelIndex, self.items.len, self.items.len)
self.items.add(item)
self.endInsertRows()
self.countChanged()
proc prependItems*(self: SubModel, items: seq[SubItem]) =
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
let first = 0
let last = items.len - 1
self.beginInsertRows(parentModelIndex, first, last)
self.items = items & self.items
self.endInsertRows()
self.countChanged()
proc prependItem*(self: SubModel, item: SubItem) =
let parentModelIndex = newQModelIndex()
defer: parentModelIndex.delete
self.beginInsertRows(parentModelIndex, 0, 0)
self.items = item & self.items
self.endInsertRows()
self.countChanged()
proc getItemById*(self: SubModel, id: string): SubItem =
for it in self.items:
if(it.id == id):
return it
proc setActiveItem*(self: SubModel, id: string) =
for i in 0 ..< self.items.len:
if(self.items[i].active):
let index = self.createIndex(i, 0, nil)
self.items[i].BaseItem.active = false
self.dataChanged(index, index, @[ModelRole.Active.int])
if(self.items[i].id == id):
let index = self.createIndex(i, 0, nil)
self.items[i].BaseItem.active= true
self.dataChanged(index, index, @[ModelRole.Active.int])

View File

@ -0,0 +1,33 @@
import controller_interface
import io_interface
import ../../../../../app_service/service/community/service as community_service
export controller_interface
type
Controller* = ref object of controller_interface.AccessInterface
delegate: io_interface.AccessInterface
id: string
isCommunityModule: bool
communityService: community_service.ServiceInterface
proc newController*(delegate: io_interface.AccessInterface, id: string, isCommunity: bool,
communityService: community_service.ServiceInterface): Controller =
result = Controller()
result.delegate = delegate
result.id = id
result.isCommunityModule = isCommunity
result.communityService = communityService
method delete*(self: Controller) =
discard
method init*(self: Controller) =
discard
method getId*(self: Controller): string =
return self.id
method isCommunity*(self: Controller): bool =
return self.isCommunityModule

View File

@ -0,0 +1,19 @@
import ../../../../../app_service/service/community/service_interface as community_service
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
## Abstract class for any input/interaction with this module.
method delete*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method init*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method getId*(self: AccessInterface): string {.base.} =
raise newException(ValueError, "No implementation available")
method isCommunity*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,12 @@
# Defines how parent module accesses this module
include ./private_interfaces/module_base_interface
include ./private_interfaces/module_access_interface
# Defines how this module view communicates with this module
include ./private_interfaces/module_view_delegate_interface
# Defines how this controller communicates with this module
include ./private_interfaces/module_controller_delegate_interface
# Defines how submodules of this module communicate with this module
# will be added if needed

View File

@ -0,0 +1,21 @@
import NimQml
QtObject:
type
Item* = ref object of QObject
proc setup(self: Item) =
self.QObject.setup
proc delete*(self: Item) =
self.QObject.delete
proc newItem*(): Item =
new(result, delete)
result.setup()
proc id*(self: Item): string {.slot.} =
self.id
QtProperty[string] id:
read = id

View File

@ -0,0 +1,17 @@
import NimQml
import item
QtObject:
type
Model* = ref object of QAbstractListModel
sections: seq[Item]
proc setup(self: Model) =
self.QAbstractListModel.setup
proc delete*(self: Model) =
self.QAbstractListModel.delete
proc newModel*(): Model =
new(result, delete)
result.setup

View File

@ -0,0 +1,46 @@
import NimQml
import io_interface
import ../io_interface as delegate_interface
import view, controller
import ../../../../core/global_singleton
import ../../../../../app_service/service/chat/service as chat_service
import ../../../../../app_service/service/community/service as community_service
export io_interface
type
Module* = ref object of io_interface.AccessInterface
delegate: delegate_interface.AccessInterface
view: View
viewVariant: QVariant
controller: controller.AccessInterface
moduleLoaded: bool
proc newModule*(delegate: delegate_interface.AccessInterface, id: string, isCommunity: bool,
chatService: chat_service.Service, communityService: community_service.Service):
Module =
result = Module()
result.delegate = delegate
result.view = view.newView(result)
result.viewVariant = newQVariant(result.view)
result.controller = controller.newController(result, id, isCommunity, communityService)
result.moduleLoaded = false
method delete*(self: Module) =
self.view.delete
self.viewVariant.delete
self.controller.delete
method load*(self: Module) =
singletonInstance.engine.setRootContextProperty("usersModule", self.viewVariant)
self.controller.init()
self.view.load()
method isLoaded*(self: Module): bool =
return self.moduleLoaded
method viewDidLoad*(self: Module) =
self.moduleLoaded = true
self.delegate.usersDidLoad()

View File

@ -0,0 +1,8 @@
method delete*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method load*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")
method isLoaded*(self: AccessInterface): bool {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,5 @@
type
AccessInterface* {.pure inheritable.} = ref object of RootObj
# Since nim doesn't support using concepts in second level nested types we
# define delegate interfaces within access interface.

View File

@ -0,0 +1,2 @@
method viewDidLoad*(self: AccessInterface) {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -0,0 +1,22 @@
import NimQml
import model
import io_interface
QtObject:
type
View* = ref object of QObject
delegate: io_interface.AccessInterface
model: Model
proc delete*(self: View) =
self.model.delete
self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View =
new(result, delete)
result.QObject.setup
result.delegate = delegate
result.model = newModel()
proc load*(self: View) =
self.delegate.viewDidLoad()

View File

@ -1,5 +1,5 @@
import NimQml
import model
import model, item, sub_item, active_item
import io_interface
QtObject:
@ -7,9 +7,15 @@ QtObject:
View* = ref object of QObject
delegate: io_interface.AccessInterface
model: Model
modelVariant: QVariant
activeItem: ActiveItem
activeItemVariant: QVariant
proc delete*(self: View) =
self.model.delete
self.modelVariant.delete
self.activeItem.delete
self.activeItemVariant.delete
self.QObject.delete
proc newView*(delegate: io_interface.AccessInterface): View =
@ -17,6 +23,43 @@ QtObject:
result.QObject.setup
result.delegate = delegate
result.model = newModel()
result.modelVariant = newQVariant(result.model)
result.activeItem = newActiveItem()
result.activeItemVariant = newQVariant(result.activeItem)
proc load*(self: View) =
self.delegate.viewDidLoad()
self.delegate.viewDidLoad()
proc model*(self: View): Model =
return self.model
proc modelChanged*(self: View) {.signal.}
proc getModel(self: View): QVariant {.slot.} =
return self.modelVariant
QtProperty[QVariant] model:
read = getModel
notify = modelChanged
proc appendItem*(self: View, item: Item) =
self.model.appendItem(item)
proc prependItem*(self: View, item: Item) =
self.model.prependItem(item)
proc activeItemChanged*(self:View) {.signal.}
proc getActiveItem(self: View): QVariant {.slot.} =
return self.activeItemVariant
QtProperty[QVariant] activeItem:
read = getActiveItem
notify = activeItemChanged
method activeItemSubItemSet*(self: View, item: Item, subItem: SubItem) =
self.activeItem.setActiveItemData(item, subItem)
self.activeItemChanged()
proc setActiveItem*(self: View, itemId: string, subItemId: string = "") {.slot.} =
self.delegate.setActiveItemSubItem(itemId, subItemId)

View File

@ -37,3 +37,12 @@ method init*(self: Service) =
method getAllChats*(self: Service): seq[ChatDto] =
return toSeq(self.chats.values)
method getChatsOfChatTypes*(self: Service, types: seq[ChatType]): seq[ChatDto] =
return self.getAllChats().filterIt(it.chatType in types)
method getChatById*(self: Service, chatId: string): ChatDto =
if(not self.chats.contains(chatId)):
error "trying to get chat data for an unexisting chat id"
return
return self.chats[chatId]

View File

@ -15,3 +15,8 @@ method init*(self: ServiceInterface) {.base.} =
method getAllChats*(self: ServiceInterface): seq[ChatDto] {.base.} =
raise newException(ValueError, "No implementation available")
method getChatsOfChatTypes*(self: ServiceInterface, types: seq[ChatType]): seq[ChatDto] {.base.} =
raise newException(ValueError, "No implementation available")
method getChatById*(self: ServiceInterface, chatId: string): ChatDto {.base.} =
raise newException(ValueError, "No implementation available")

View File

@ -1,4 +1,4 @@
import Tables, json, sequtils, strformat, chronicles
import Tables, json, sequtils, std/algorithm, strformat, chronicles
import service_interface, ./dto/community
@ -40,4 +40,50 @@ method init*(self: Service) =
return
method getCommunities*(self: Service): seq[CommunityDto] =
return toSeq(self.communities.values)
return toSeq(self.communities.values)
method getCommunityIds*(self: Service): seq[string] =
return toSeq(self.communities.keys)
proc sortAsc[T](t1, t2: T): int =
if(t1.position > t2.position):
return 1
elif (t1.position < t2.position):
return -1
else:
return 0
proc sortDesc[T](t1, t2: T): int =
if(t1.position < t2.position):
return 1
elif (t1.position > t2.position):
return -1
else:
return 0
method getCategories*(self: Service, communityId: string, order: SortOrder = SortOrder.Ascending): seq[Category] =
if(not self.communities.contains(communityId)):
error "trying to get community for an unexisting community id"
return
result = self.communities[communityId].categories
if(order == SortOrder.Ascending):
result.sort(sortAsc[Category])
else:
result.sort(sortDesc[Category])
method getChats*(self: Service, communityId: string, categoryId = "", order = SortOrder.Ascending): seq[Chat] =
if(not self.communities.contains(communityId)):
error "trying to get community for an unexisting community id"
return
for chat in self.communities[communityId].chats:
if(chat.categoryId != categoryId):
continue
result.add(chat)
if(order == SortOrder.Ascending):
result.sort(sortAsc[Chat])
else:
result.sort(sortDesc[Chat])

View File

@ -1,3 +1,4 @@
import std/algorithm
import ./dto/community as community_dto
export community_dto
@ -14,3 +15,14 @@ method init*(self: ServiceInterface) {.base.} =
method getCommunities*(self: ServiceInterface): seq[CommunityDto] {.base.} =
raise newException(ValueError, "No implementation available")
method getCommunityIds*(self: ServiceInterface): seq[string] {.base.} =
raise newException(ValueError, "No implementation available")
method getCategories*(self: ServiceInterface, communityId: string, order: SortOrder = SortOrder.Ascending): seq[Category]
{.base.} =
raise newException(ValueError, "No implementation available")
method getChats*(self: ServiceInterface, communityId: string, categoryId = "", order = SortOrder.Ascending): seq[Chat]
{.base.} =
raise newException(ValueError, "No implementation available")