This commit is contained in:
2026-06-30 10:39:29 +07:00
parent aaafa6ada9
commit d9c8e3930b
28 changed files with 3040 additions and 167 deletions

View File

@@ -104,6 +104,8 @@ type App struct {
wifiRejected bool
quitDialogShown bool
monitoringTornDown bool
chatUnread int
replyStaffID uint
}
type LocalStats struct {
@@ -274,10 +276,7 @@ func (a *App) startLocalServer() {
blocker.Instance.Start()
// Kiểm tra điều kiện app được phép và giờ học trước khi tải giao diện
go a.fetchAllowedApps(st.SystemID)
go a.fetchStudentStatus(st.StudentID)
go a.fetchWifiPolicy()
go a.refreshNetworkStatus()
go a.refreshStudentData()
// Chuyển hướng WebView về trang dashboard của app bằng cách reload app assets
runtime.WindowReloadApp(a.ctx)
@@ -315,10 +314,8 @@ func (a *App) loadSession() {
log.Printf("[APP] Loaded local session: %s (%s)", s.FullName, s.StudentCode)
blocker.Instance.Start()
go a.fetchAllowedApps(s.SystemID)
go a.fetchStudentStatus(s.StudentID)
go a.refreshStudentData()
go a.fetchWifiPolicy()
go a.refreshNetworkStatus()
go a.syncProfileToServer(&s)
}
}
@@ -589,6 +586,33 @@ func (a *App) runMonitorTick() {
}
}
// refreshNetworkStatus — đọc WiFi + ping server ngay (không cộng thời gian online/offline).
func (a *App) resolveClassRkID() int64 {
a.mu.Lock()
defer a.mu.Unlock()
if a.dashboard.ClassRkID > 0 {
return a.dashboard.ClassRkID
}
return 0
}
func (a *App) refreshStudentData() {
a.mu.Lock()
student := a.student
a.mu.Unlock()
if student == nil {
return
}
a.fetchStudentStatus(student.StudentID)
classID := a.resolveClassRkID()
if classID > 0 {
a.fetchAllowedApps(classID)
} else {
a.fetchAllowedApps(0)
}
a.refreshNetworkStatus()
}
// refreshNetworkStatus — đọc WiFi + ping server ngay (không cộng thời gian online/offline).
func (a *App) refreshNetworkStatus() {
if !a.isMonitoringActive() || !a.CheckLoginStatus() {
@@ -601,11 +625,14 @@ func (a *App) refreshNetworkStatus() {
a.wifiBSSID = wifi.BSSID
a.backendOnline = backendOnline
a.mu.Unlock()
if backendOnline {
a.connectWS()
}
}
func (a *App) pingBackend() bool {
client := http.Client{Timeout: 3 * time.Second}
resp, err := client.Get("http://127.0.0.1:8080/api/stats")
resp, err := client.Get("http://127.0.0.1:8080/api/health")
if err != nil {
return false
}
@@ -628,12 +655,18 @@ func (a *App) syncLogsToServer() {
return
}
go a.fetchStudentStatus(student.StudentID)
go a.fetchAllowedApps(student.SystemID)
go func() {
a.fetchStudentStatus(student.StudentID)
classID := a.resolveClassRkID()
if classID <= 0 {
classID = student.SystemID
}
a.fetchAllowedApps(classID)
}()
payload := map[string]any{
"studentRkId": student.StudentID,
"classRkId": student.SystemID,
"classRkId": a.resolveClassRkID(),
"sessionDate": time.Now().Format("2006-01-02"),
"addOnlineSeconds": unsyncedOn,
"addOfflineSeconds": unsyncedOff,
@@ -809,8 +842,9 @@ func (a *App) fetchAllowedApps(classId int64) {
}
a.mu.Unlock()
url := fmt.Sprintf("http://127.0.0.1:8080/api/classes/%d/allowed-apps?studentId=%d", classId, studentID)
client := http.Client{Timeout: 4 * time.Second}
resp, err := client.Get(fmt.Sprintf("http://127.0.0.1:8080/api/classes/%d/allowed-apps?studentId=%d", classId, studentID))
resp, err := client.Get(url)
if err != nil {
return
}
@@ -888,7 +922,7 @@ func (a *App) fetchStudentStatus(studentID int64) {
}
func (a *App) connectWS() {
if !a.isMonitoringActive() {
if !a.CheckLoginStatus() {
return
}
a.mu.Lock()
@@ -897,9 +931,13 @@ func (a *App) connectWS() {
return
}
student := a.student
classID := a.dashboard.ClassRkID
if classID <= 0 {
classID = student.SystemID
}
a.mu.Unlock()
wsUrl := fmt.Sprintf("ws://127.0.0.1:8080/ws?role=student&studentId=%d&classId=%d", student.StudentID, student.SystemID)
wsUrl := fmt.Sprintf("ws://127.0.0.1:8080/ws?role=student&studentId=%d&classId=%d", student.StudentID, classID)
dialer := websocket.Dialer{HandshakeTimeout: 4 * time.Second}
conn, _, err := dialer.Dial(wsUrl, nil)
if err != nil {
@@ -934,6 +972,20 @@ func (a *App) connectWS() {
runtime.EventsEmit(a.ctx, "start_webcam_stream")
case "stop_webcam_stream":
runtime.EventsEmit(a.ctx, "stop_webcam_stream")
case "chat:message":
if staffVal, ok := msg.Data["staffId"].(float64); ok {
a.mu.Lock()
a.replyStaffID = uint(staffVal)
a.chatUnread++
unread := a.chatUnread
a.mu.Unlock()
runtime.EventsEmit(a.ctx, "chat:notify", map[string]any{
"unread": unread,
"preview": msg.Data["body"],
"from": msg.Data["staffName"],
})
}
runtime.EventsEmit(a.ctx, "chat:message", msg.Data)
}
}
}()
@@ -1003,3 +1055,104 @@ func (a *App) stopScreenshotStream() {
close(a.streamScStop)
log.Println("[WS] Screenshot screen-streaming stopped.")
}
func (a *App) GetChatUnread() int {
a.mu.Lock()
defer a.mu.Unlock()
return a.chatUnread
}
func (a *App) ClearChatUnread() {
a.mu.Lock()
a.chatUnread = 0
a.mu.Unlock()
}
func (a *App) GetChatConversations() ([]map[string]any, error) {
a.mu.Lock()
student := a.student
a.mu.Unlock()
if student == nil {
return nil, errors.New("chưa đăng nhập")
}
url := fmt.Sprintf("http://127.0.0.1:8080/api/student/chat/conversations?studentRkId=%d", student.StudentID)
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var payload struct {
Data []map[string]any `json:"data"`
}
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
return nil, err
}
return payload.Data, nil
}
func (a *App) GetChatMessages(staffID uint) ([]map[string]any, error) {
a.mu.Lock()
student := a.student
a.mu.Unlock()
if student == nil {
return nil, errors.New("chưa đăng nhập")
}
url := fmt.Sprintf("http://127.0.0.1:8080/api/student/chat/messages?studentRkId=%d&staffId=%d", student.StudentID, staffID)
resp, err := http.Get(url)
if err != nil {
return nil, err
}
defer resp.Body.Close()
var payload struct {
Data []map[string]any `json:"data"`
ReplyStaffID uint `json:"replyStaffId"`
}
if err := json.NewDecoder(resp.Body).Decode(&payload); err != nil {
return nil, err
}
if staffID > 0 {
a.mu.Lock()
a.replyStaffID = staffID
a.mu.Unlock()
} else if payload.ReplyStaffID > 0 {
a.mu.Lock()
a.replyStaffID = payload.ReplyStaffID
a.mu.Unlock()
}
a.ClearChatUnread()
return payload.Data, nil
}
func (a *App) SendChatMessage(body string) error {
body = strings.TrimSpace(body)
if body == "" {
return errors.New("tin nhắn trống")
}
a.mu.Lock()
student := a.student
staffID := a.replyStaffID
a.mu.Unlock()
if student == nil {
return errors.New("chưa đăng nhập")
}
payload := map[string]any{
"studentRkId": student.StudentID,
"staffId": staffID,
"body": body,
}
b, _ := json.Marshal(payload)
resp, err := http.Post("http://127.0.0.1:8080/api/student/chat/messages", "application/json", bytes.NewReader(b))
if err != nil {
return err
}
defer resp.Body.Close()
if resp.StatusCode >= 300 {
var errBody map[string]any
_ = json.NewDecoder(resp.Body).Decode(&errBody)
if msg, ok := errBody["error"].(string); ok {
return errors.New(msg)
}
return fmt.Errorf("gửi tin thất bại (%d)", resp.StatusCode)
}
return nil
}