v3 (fckn yeaaah ya zaebalsa)

This commit is contained in:
Lain Iwakura 2025-08-05 23:54:04 +03:00
parent a9d2b95886
commit d82a294c27
No known key found for this signature in database
GPG Key ID: C7C18257F2ADC6F8
9 changed files with 494 additions and 37 deletions

View File

@ -9,7 +9,7 @@
- Просмотр деталей треда и комментариев - Просмотр деталей треда и комментариев
- Поддержка изображений и видео - Поддержка изображений и видео
- Темная/светлая тема - Темная/светлая тема
- Навигация с кнопкой "Назад" (index lore) - Навигация с кнопкой "Назад"
- Улучшенный заголовок с статическими кнопками - Улучшенный заголовок с статическими кнопками
- Система настроек с сохранением: - Система настроек с сохранением:
- Тема (темная/светлая) - Тема (темная/светлая)
@ -17,22 +17,58 @@
- Автообновление - Автообновление
- Показ файлов - Показ файлов
- Компактный режим - Компактный режим
- Поддержка Android и iOS (с сохранением настроек (блять нахуй я туда полез)) - **Полная поддержка постинга:**
- Аутентификация по ключу
- Аутентификация по passcode
- Создание тредов
- Добавление комментариев
- Автоматическое обновление после постинга
- Кнопка "Обновить" для ручного обновления
- Поддержка Android и iOS
- **Оптимизации для мобильных устройств:** - **Оптимизации для мобильных устройств:**
- Кэширование данных для быстрой загрузки - Кэширование данных для быстрой загрузки
- Дебаунсинг UI обновлений - Дебаунсинг UI обновлений
- Оптимизация потребления батареи - Оптимизация потребления батареи
- Агрессивная сборка мусора на мобильных - Агрессивная сборка мусора на мобильных
- Ограничение частоты сетевых запросов - Ограничение частоты сетевых запросов
- **Многостраничность для списка тредов:** - **Многостраничность для списка тредов:**
- Настраиваемый размер страницы (5-20 тредов) - Настраиваемый размер страницы (5-20 тредов)
- Навигация между страницами - Навигация между страницами
- Ограничение тредов для больших досок - Ограничение тредов для больших досок
- **Управление кэшем:** - **Управление кэшем:**
- Очистка кэша досок - Очистка кэша досок
- Очистка кэша тредов для всех досок - Очистка кэша тредов для всех досок
- Очистка кэша деталей тредов - Очистка кэша деталей тредов
- Очистка всего кэша - Очистка всего кэша
## Аутентификация и постинг
### Настройка аутентификации
1. Откройте настройки в приложении
2. Введите ключ аутентификации (если есть)
3. Введите passcode для постинга (если есть)
4. Используйте кнопки "Тест ключа" и "Тест passcode" для проверки
### Создание тредов
1. Перейдите в нужную доску
2. Нажмите "Создать тред"
3. Заполните заголовок и текст
4. Нажмите "Создать тред"
### Добавление комментариев
1. Откройте тред
2. Нажмите "Добавить комментарий"
3. Введите текст комментария
4. Нажмите "Добавить комментарий"
### Автоматическое обновление
- После создания треда автоматически обновляется список тредов
- После добавления комментария автоматически обновляется список комментариев
- Используйте кнопку "Обновить" для принудительного обновления
## Сборка ## Сборка
@ -97,7 +133,6 @@ open MobileMkch.xcodeproj
**✅ iOS И Android сборка протестирована и работает!** **✅ iOS И Android сборка протестирована и работает!**
## Требования ## Требования
- Go 1.24+ - Go 1.24+
@ -109,12 +144,13 @@ open MobileMkch.xcodeproj
- Go 1.24+ - Go 1.24+
- Fyne v2.6.2 - Fyne v2.6.2
- HTTP клиент для API - HTTP клиент для API с поддержкой сессий
- Система кэширования с TTL
## Структура ## Структура
- `main.go` - точка входа - `main.go` - точка входа
- `api/client.go` - HTTP клиент для mkch API - `api/client.go` - HTTP клиент для mkch API с поддержкой аутентификации
- `models/models.go` - структуры данных - `models/models.go` - структуры данных
- `settings/settings.go` - система настроек - `settings/settings.go` - система настроек
- `cache/cache.go` - система кэширования с поддержкой пагинации - `cache/cache.go` - система кэширования с поддержкой пагинации
@ -122,7 +158,9 @@ open MobileMkch.xcodeproj
- `manager.go` - управление экранами - `manager.go` - управление экранами
- `boards_screen.go` - список досок - `boards_screen.go` - список досок
- `threads_screen.go` - треды доски - `threads_screen.go` - треды доски
- `thread_detail_screen.go` - детали треда - `thread_detail_screen.go` - детали треда с кнопкой обновления
- `settings_screen.go` - экран настроек - `create_thread_screen.go` - создание тредов
- `add_comment_screen.go` - добавление комментариев
- `settings_screen.go` - экран настроек с тестированием аутентификации
- `optimization.go` - утилиты оптимизации - `optimization.go` - утилиты оптимизации
- `mobile_optimizations.go` - оптимизации для мобильных устройств - `mobile_optimizations.go` - оптимизации для мобильных устройств

View File

@ -1,10 +1,14 @@
package api package api
import ( import (
"bytes"
"encoding/json" "encoding/json"
"fmt" "fmt"
"io" "io"
"net/http" "net/http"
"net/http/cookiejar"
"net/url"
"regexp"
"time" "time"
"MobileMkch/cache" "MobileMkch/cache"
@ -20,20 +24,121 @@ type Client struct {
httpClient *http.Client httpClient *http.Client
baseURL string baseURL string
debug bool debug bool
authKey string
passcode string
} }
func NewClient() *Client { func NewClient() *Client {
jar, _ := cookiejar.New(nil)
return &Client{ return &Client{
httpClient: &http.Client{ httpClient: &http.Client{
Timeout: 30 * time.Second, Timeout: 30 * time.Second,
Jar: jar,
}, },
baseURL: ApiURL, baseURL: BaseURL,
debug: false, debug: false,
} }
} }
func (c *Client) EnableDebug(enable bool) { func (c *Client) EnableDebug(enable bool) {
c.debug = enable c.debug = enable
if c.debug {
fmt.Printf("[DEBUG] Debug режим включен\n")
}
}
func (c *Client) Authenticate(authKey string) error {
resp, err := c.httpClient.Get(fmt.Sprintf("%s/key/auth/", c.baseURL))
if err != nil {
return fmt.Errorf("ошибка получения формы аутентификации: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("ошибка получения формы аутентификации: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("ошибка чтения формы аутентификации: %w", err)
}
csrfToken := extractCSRFToken(string(body))
if csrfToken == "" {
return fmt.Errorf("не удалось извлечь CSRF токен для аутентификации")
}
formData := url.Values{}
formData.Set("csrfmiddlewaretoken", csrfToken)
formData.Set("key", authKey)
req, err := http.NewRequest("POST", fmt.Sprintf("%s/key/auth/", c.baseURL), bytes.NewBufferString(formData.Encode()))
if err != nil {
return fmt.Errorf("ошибка создания запроса аутентификации: %w", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Referer", fmt.Sprintf("%s/key/auth/", c.baseURL))
resp, err = c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("ошибка отправки аутентификации: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusFound {
return fmt.Errorf("ошибка аутентификации: %d", resp.StatusCode)
}
c.authKey = authKey
return nil
}
func (c *Client) LoginWithPasscode(passcode string) error {
resp, err := c.httpClient.Get(fmt.Sprintf("%s/passcode/enter/", c.baseURL))
if err != nil {
return fmt.Errorf("ошибка получения формы passcode: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("ошибка получения формы passcode: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("ошибка чтения формы passcode: %w", err)
}
csrfToken := extractCSRFToken(string(body))
if csrfToken == "" {
return fmt.Errorf("не удалось извлечь CSRF токен для passcode")
}
formData := url.Values{}
formData.Set("csrfmiddlewaretoken", csrfToken)
formData.Set("passcode", passcode)
req, err := http.NewRequest("POST", fmt.Sprintf("%s/passcode/enter/", c.baseURL), bytes.NewBufferString(formData.Encode()))
if err != nil {
return fmt.Errorf("ошибка создания запроса passcode: %w", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Referer", fmt.Sprintf("%s/passcode/enter/", c.baseURL))
resp, err = c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("ошибка отправки passcode: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusFound {
return fmt.Errorf("ошибка входа с passcode: %d", resp.StatusCode)
}
c.passcode = passcode
return nil
} }
func (c *Client) makeRequest(url string) ([]byte, error) { func (c *Client) makeRequest(url string) ([]byte, error) {
@ -78,7 +183,7 @@ func (c *Client) GetBoards() ([]models.Board, error) {
return boards, nil return boards, nil
} }
url := c.baseURL + "/boards/" url := ApiURL + "/boards/"
body, err := c.makeRequest(url) body, err := c.makeRequest(url)
if err != nil { if err != nil {
return nil, fmt.Errorf("ошибка получения досок: %w", err) return nil, fmt.Errorf("ошибка получения досок: %w", err)
@ -111,7 +216,7 @@ func (c *Client) GetThreads(boardCode string) ([]models.Thread, error) {
return threads, nil return threads, nil
} }
url := fmt.Sprintf("%s/board/%s", c.baseURL, boardCode) url := fmt.Sprintf("%s/board/%s", ApiURL, boardCode)
body, err := c.makeRequest(url) body, err := c.makeRequest(url)
if err != nil { if err != nil {
return nil, fmt.Errorf("ошибка получения тредов доски %s: %w", boardCode, err) return nil, fmt.Errorf("ошибка получения тредов доски %s: %w", boardCode, err)
@ -126,13 +231,6 @@ func (c *Client) GetThreads(boardCode string) ([]models.Thread, error) {
return nil, fmt.Errorf("ошибка парсинга тредов: %w", err) return nil, fmt.Errorf("ошибка парсинга тредов: %w", err)
} }
// if len(threads) > 12 {
// if c.debug {
// fmt.Printf("[API] Ограничиваем количество тредов с %d до 5\n", len(threads))
// }
// threads = threads[:12]
// }
cache.GetCache().SetThreads(boardCode, threads) cache.GetCache().SetThreads(boardCode, threads)
if c.debug { if c.debug {
@ -150,7 +248,7 @@ func (c *Client) GetThread(boardCode string, threadID int) (*models.ThreadDetail
return thread, nil return thread, nil
} }
url := fmt.Sprintf("%s/board/%s/thread/%d", c.baseURL, boardCode, threadID) url := fmt.Sprintf("%s/board/%s/thread/%d", ApiURL, boardCode, threadID)
body, err := c.makeRequest(url) body, err := c.makeRequest(url)
if err != nil { if err != nil {
return nil, fmt.Errorf("ошибка получения треда %d: %w", threadID, err) return nil, fmt.Errorf("ошибка получения треда %d: %w", threadID, err)
@ -182,7 +280,7 @@ func (c *Client) GetComments(boardCode string, threadID int) ([]models.Comment,
return comments, nil return comments, nil
} }
url := fmt.Sprintf("%s/board/%s/thread/%d/comments", c.baseURL, boardCode, threadID) url := fmt.Sprintf("%s/board/%s/thread/%d/comments", ApiURL, boardCode, threadID)
body, err := c.makeRequest(url) body, err := c.makeRequest(url)
if err != nil { if err != nil {
return nil, fmt.Errorf("ошибка получения комментариев треда %d: %w", threadID, err) return nil, fmt.Errorf("ошибка получения комментариев треда %d: %w", threadID, err)
@ -252,3 +350,133 @@ func (c *Client) GetFullThread(boardCode string, threadID int) (*models.ThreadDe
return thread, comments, nil return thread, comments, nil
} }
func (c *Client) CreateThread(boardCode, title, text, passcode string) error {
if passcode != "" {
if err := c.LoginWithPasscode(passcode); err != nil {
return fmt.Errorf("ошибка входа с passcode: %w", err)
}
}
formURL := fmt.Sprintf("%s/boards/board/%s/new", c.baseURL, boardCode)
resp, err := c.httpClient.Get(formURL)
if err != nil {
return fmt.Errorf("ошибка получения формы: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("ошибка получения формы: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("ошибка чтения формы: %w", err)
}
csrfToken := extractCSRFToken(string(body))
if csrfToken == "" {
return fmt.Errorf("не удалось извлечь CSRF токен")
}
formData := url.Values{}
formData.Set("csrfmiddlewaretoken", csrfToken)
formData.Set("title", title)
formData.Set("text", text)
req, err := http.NewRequest("POST", formURL, bytes.NewBufferString(formData.Encode()))
if err != nil {
return fmt.Errorf("ошибка создания запроса: %w", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Referer", formURL)
resp, err = c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("ошибка отправки: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusFound {
return fmt.Errorf("ошибка сервера: %d", resp.StatusCode)
}
cache.GetCache().Delete("threads_" + boardCode)
return nil
}
func (c *Client) AddComment(boardCode string, threadID int, text, passcode string) error {
if passcode != "" {
if err := c.LoginWithPasscode(passcode); err != nil {
return fmt.Errorf("ошибка входа с passcode: %w", err)
}
}
formURL := fmt.Sprintf("%s/boards/board/%s/thread/%d/comment", c.baseURL, boardCode, threadID)
resp, err := c.httpClient.Get(formURL)
if err != nil {
return fmt.Errorf("ошибка получения формы: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK {
return fmt.Errorf("ошибка получения формы: %d", resp.StatusCode)
}
body, err := io.ReadAll(resp.Body)
if err != nil {
return fmt.Errorf("ошибка чтения формы: %w", err)
}
csrfToken := extractCSRFToken(string(body))
if csrfToken == "" {
return fmt.Errorf("не удалось извлечь CSRF токен")
}
formData := url.Values{}
formData.Set("csrfmiddlewaretoken", csrfToken)
formData.Set("text", text)
req, err := http.NewRequest("POST", formURL, bytes.NewBufferString(formData.Encode()))
if err != nil {
return fmt.Errorf("ошибка создания запроса: %w", err)
}
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
req.Header.Set("Referer", formURL)
resp, err = c.httpClient.Do(req)
if err != nil {
return fmt.Errorf("ошибка отправки: %w", err)
}
defer resp.Body.Close()
if resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusFound {
return fmt.Errorf("ошибка сервера: %d", resp.StatusCode)
}
cacheKey := fmt.Sprintf("comments_%d", threadID)
cache.GetCache().Delete(cacheKey)
return nil
}
func extractCSRFToken(html string) string {
re := regexp.MustCompile(`name=['"]csrfmiddlewaretoken['"]\s+value=['"]([^'"]+)['"]`)
matches := re.FindStringSubmatch(html)
if len(matches) > 1 {
return matches[1]
}
return ""
}
func min(a, b int) int {
if a < b {
return a
}
return b
}

8
cache/cache.go vendored
View File

@ -164,11 +164,11 @@ func (c *Cache) GetThreadsPage(boardCode string, page int) ([]models.Thread, boo
func (c *Cache) SetThreadDetail(threadID int, thread *models.ThreadDetail) { func (c *Cache) SetThreadDetail(threadID int, thread *models.ThreadDetail) {
data, _ := json.Marshal(thread) data, _ := json.Marshal(thread)
c.Set("thread_"+string(rune(threadID)), data, 3*time.Minute) c.Set("thread_detail_"+fmt.Sprintf("%d", threadID), data, 3*time.Minute)
} }
func (c *Cache) GetThreadDetail(threadID int) (*models.ThreadDetail, bool) { func (c *Cache) GetThreadDetail(threadID int) (*models.ThreadDetail, bool) {
data, exists := c.Get("thread_" + string(rune(threadID))) data, exists := c.Get("thread_detail_" + fmt.Sprintf("%d", threadID))
if !exists { if !exists {
return nil, false return nil, false
} }
@ -184,11 +184,11 @@ func (c *Cache) GetThreadDetail(threadID int) (*models.ThreadDetail, bool) {
func (c *Cache) SetComments(threadID int, comments []models.Comment) { func (c *Cache) SetComments(threadID int, comments []models.Comment) {
data, _ := json.Marshal(comments) data, _ := json.Marshal(comments)
c.Set("comments_"+string(rune(threadID)), data, 3*time.Minute) c.Set("comments_"+fmt.Sprintf("%d", threadID), data, 3*time.Minute)
} }
func (c *Cache) GetComments(threadID int) ([]models.Comment, bool) { func (c *Cache) GetComments(threadID int) ([]models.Comment, bool) {
data, exists := c.Get("comments_" + string(rune(threadID))) data, exists := c.Get("comments_" + fmt.Sprintf("%d", threadID))
if !exists { if !exists {
return nil, false return nil, false
} }

19
main.go
View File

@ -27,6 +27,25 @@ func main() {
apiClient := api.NewClient() apiClient := api.NewClient()
appSettings, err := settings.Load()
if err == nil {
if appSettings.Key != "" {
if err := apiClient.Authenticate(appSettings.Key); err != nil {
fmt.Printf("Ошибка аутентификации по ключу: %v\n", err)
} else {
fmt.Println("Аутентификация по ключу успешна")
}
}
if appSettings.Passcode != "" {
if err := apiClient.LoginWithPasscode(appSettings.Passcode); err != nil {
fmt.Printf("Ошибка входа с passcode: %v\n", err)
} else {
fmt.Println("Вход с passcode успешен")
}
}
}
uiManager := ui.NewUIManager(a, w, apiClient) uiManager := ui.NewUIManager(a, w, apiClient)
w.SetContent(uiManager.GetMainContent()) w.SetContent(uiManager.GetMainContent())

View File

@ -18,6 +18,8 @@ type Settings struct {
ShowFiles bool `json:"show_files"` ShowFiles bool `json:"show_files"`
CompactMode bool `json:"compact_mode"` CompactMode bool `json:"compact_mode"`
PageSize int `json:"page_size"` PageSize int `json:"page_size"`
Passcode string `json:"passcode"`
Key string `json:"key"`
} }
var settingsPath string var settingsPath string

View File

@ -4,11 +4,13 @@ import (
"MobileMkch/api" "MobileMkch/api"
"MobileMkch/models" "MobileMkch/models"
"MobileMkch/settings" "MobileMkch/settings"
"fmt"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/theme" "fyne.io/fyne/v2/theme"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
"MobileMkch/cache"
) )
type UIManager struct { type UIManager struct {
@ -42,6 +44,7 @@ func NewUIManager(app fyne.App, window fyne.Window, apiClient *api.Client) *UIMa
AutoRefresh: true, AutoRefresh: true,
ShowFiles: true, ShowFiles: true,
CompactMode: false, CompactMode: false,
Key: "",
} }
} }
@ -186,6 +189,10 @@ func (ui *UIManager) ShowThreadDetail(board *models.Board, thread *models.Thread
ui.ShowScreen(threadScreen) ui.ShowScreen(threadScreen)
} }
func (ui *UIManager) PushScreen(screen Screen) {
ui.ShowScreen(screen)
}
func (ui *UIManager) GetAPIClient() *api.Client { func (ui *UIManager) GetAPIClient() *api.Client {
return ui.apiClient return ui.apiClient
} }
@ -195,14 +202,15 @@ func (ui *UIManager) GetWindow() fyne.Window {
} }
func (ui *UIManager) ShowError(title, message string) { func (ui *UIManager) ShowError(title, message string) {
dialog := widget.NewModalPopUp( var dialog *widget.PopUp
dialog = widget.NewModalPopUp(
container.NewVBox( container.NewVBox(
widget.NewLabel(title), widget.NewLabel(title),
widget.NewLabel(""), widget.NewLabel(""),
widget.NewLabel(message), widget.NewLabel(message),
widget.NewLabel(""), widget.NewLabel(""),
widget.NewButton("OK", func() { widget.NewButton("OK", func() {
// Закрыть диалог - пока что просто прячем popup dialog.Hide()
}), }),
), ),
ui.window.Canvas(), ui.window.Canvas(),
@ -211,14 +219,15 @@ func (ui *UIManager) ShowError(title, message string) {
} }
func (ui *UIManager) ShowInfo(title, message string) { func (ui *UIManager) ShowInfo(title, message string) {
dialog := widget.NewModalPopUp( var dialog *widget.PopUp
dialog = widget.NewModalPopUp(
container.NewVBox( container.NewVBox(
widget.NewLabel(title), widget.NewLabel(title),
widget.NewLabel(""), widget.NewLabel(""),
widget.NewLabel(message), widget.NewLabel(message),
widget.NewLabel(""), widget.NewLabel(""),
widget.NewButton("OK", func() { widget.NewButton("OK", func() {
// Закрыть диалог dialog.Hide()
}), }),
), ),
ui.window.Canvas(), ui.window.Canvas(),
@ -226,6 +235,32 @@ func (ui *UIManager) ShowInfo(title, message string) {
dialog.Show() dialog.Show()
} }
func (ui *UIManager) ShowInfoDialog() {
var dialog *widget.PopUp
content := container.NewVBox(
widget.NewLabel("Информация о НЕОЖИДАНЫХ проблемах"),
widget.NewLabel(""),
widget.NewLabel("Если тебя направили сюда, то значит\nты попал на НЕИЗВЕДАННЫЕ ТЕРРИТОРИИ"),
widget.NewLabel("ДА ДА, не ослышались, это не ошибка,\nэто особенность"),
widget.NewLabel("Увы, разработчик имиджборда\nвставил палки в колеса"),
widget.NewLabel("И без доната ему, например,\nпостинг работать не будет"),
widget.NewLabel(""),
widget.NewLabel("Увы, постинг не работает без доната"),
widget.NewLabel("а разработчик боится что на его сайте\nбудут спам"),
widget.NewLabel("Вкратце - на сайте работает капча"),
widget.NewLabel("а наличие пасскода ее для вас отключает"),
widget.NewLabel("увы, конфет много,\nно на всех не хватит"),
widget.NewLabel(""),
widget.NewButton("Закрыть", func() {
dialog.Hide()
}),
)
dialog = widget.NewModalPopUp(content, ui.window.Canvas())
dialog.Show()
}
func (ui *UIManager) GetSettings() *settings.Settings { func (ui *UIManager) GetSettings() *settings.Settings {
return ui.settings return ui.settings
} }
@ -234,6 +269,29 @@ func (ui *UIManager) SaveSettings() error {
return settings.Save(ui.settings) return settings.Save(ui.settings)
} }
func (ui *UIManager) RefreshCurrentScreen() {
if ui.currentScreen != nil {
ui.currentScreen.OnShow()
}
}
func (ui *UIManager) ClearCacheForBoard(boardCode string) {
cacheKey := "threads_" + boardCode
cache.GetCache().Delete(cacheKey)
}
func (ui *UIManager) ClearCacheForThread(threadID int) {
commentsKey := fmt.Sprintf("comments_%d", threadID)
threadDetailKey := fmt.Sprintf("thread_detail_%d", threadID)
cache.GetCache().Delete(commentsKey)
cache.GetCache().Delete(threadDetailKey)
}
func (ui *UIManager) GetCurrentScreen() Screen {
return ui.currentScreen
}
func (ui *UIManager) SetLastBoard(boardCode string) { func (ui *UIManager) SetLastBoard(boardCode string) {
ui.settings.LastBoard = boardCode ui.settings.LastBoard = boardCode
ui.SaveSettings() ui.SaveSettings()
@ -243,6 +301,14 @@ func (ui *UIManager) GetLastBoard() string {
return ui.settings.LastBoard return ui.settings.LastBoard
} }
func (ui *UIManager) GetPasscode() string {
return ui.settings.Passcode
}
func (ui *UIManager) GetKey() string {
return ui.settings.Key
}
func (ui *UIManager) ShowAbout() { func (ui *UIManager) ShowAbout() {
aboutWindow := ui.app.NewWindow("Об аппке") aboutWindow := ui.app.NewWindow("Об аппке")
aboutWindow.Resize(fyne.NewSize(400, 300)) aboutWindow.Resize(fyne.NewSize(400, 300))
@ -251,7 +317,7 @@ func (ui *UIManager) ShowAbout() {
widget.NewLabel("MobileMkch"), widget.NewLabel("MobileMkch"),
widget.NewLabel("Мобильный клиент для мкача"), widget.NewLabel("Мобильный клиент для мкача"),
widget.NewSeparator(), widget.NewSeparator(),
widget.NewLabel("Версия: 2.0.0-alpha (Always in alpha lol)"), widget.NewLabel("Версия: 3.0.0-alpha (Always in alpha lol)"),
widget.NewLabel("Автор: w^x (лейн, платон, а похуй как угодно)"), widget.NewLabel("Автор: w^x (лейн, платон, а похуй как угодно)"),
widget.NewLabel("Разработано с ❤️ на Go + Fyne"), widget.NewLabel("Разработано с ❤️ на Go + Fyne"),
) )

View File

@ -1,6 +1,7 @@
package ui package ui
import ( import (
"fmt"
"fyne.io/fyne/v2" "fyne.io/fyne/v2"
"fyne.io/fyne/v2/container" "fyne.io/fyne/v2/container"
"fyne.io/fyne/v2/widget" "fyne.io/fyne/v2/widget"
@ -109,6 +110,76 @@ func (ss *SettingsScreen) setupContent() {
lastBoardContainer := container.NewHBox(lastBoardLabel, lastBoardValue) lastBoardContainer := container.NewHBox(lastBoardLabel, lastBoardValue)
ss.content.Add(lastBoardContainer) ss.content.Add(lastBoardContainer)
ss.content.Add(widget.NewSeparator())
passcodeHeader := widget.NewLabel("Passcode")
passcodeHeader.TextStyle = fyne.TextStyle{Bold: true}
ss.content.Add(passcodeHeader)
passcodeEntry := widget.NewEntry()
passcodeEntry.SetPlaceHolder("Введите passcode для постинга")
passcodeEntry.Text = ss.uiManager.GetSettings().Passcode
passcodeEntry.OnChanged = func(text string) {
ss.uiManager.GetSettings().Passcode = text
ss.uiManager.SaveSettings()
}
ss.content.Add(passcodeEntry)
keyHeader := widget.NewLabel("Ключ аутентификации")
keyHeader.TextStyle = fyne.TextStyle{Bold: true}
ss.content.Add(keyHeader)
keyEntry := widget.NewEntry()
keyEntry.SetPlaceHolder("Введите ключ для аутентификации")
keyEntry.Text = ss.uiManager.GetSettings().Key
keyEntry.OnChanged = func(text string) {
ss.uiManager.GetSettings().Key = text
ss.uiManager.SaveSettings()
}
ss.content.Add(keyEntry)
authButtonsContainer := container.NewHBox()
testKeyButton := widget.NewButton("Тест ключа", func() {
key := ss.uiManager.GetSettings().Key
if key == "" {
ss.uiManager.ShowError("Ошибка", "Ключ не введен")
return
}
err := ss.uiManager.GetAPIClient().Authenticate(key)
if err != nil {
ss.uiManager.ShowError("Ошибка аутентификации", fmt.Sprintf("Не удалось аутентифицироваться: %v", err))
} else {
ss.uiManager.ShowInfo("Успех", "Аутентификация по ключу прошла успешно")
}
})
testPasscodeButton := widget.NewButton("Тест passcode", func() {
passcode := ss.uiManager.GetSettings().Passcode
if passcode == "" {
ss.uiManager.ShowError("Ошибка", "Passcode не введен")
return
}
err := ss.uiManager.GetAPIClient().LoginWithPasscode(passcode)
if err != nil {
ss.uiManager.ShowError("Ошибка passcode", fmt.Sprintf("Не удалось войти с passcode: %v", err))
} else {
ss.uiManager.ShowInfo("Успех", "Вход с passcode прошел успешно")
}
})
authButtonsContainer.Add(testKeyButton)
authButtonsContainer.Add(testPasscodeButton)
ss.content.Add(authButtonsContainer)
// Debug
debugCheck := widget.NewCheck("Debug режим", func(checked bool) {
ss.uiManager.GetAPIClient().EnableDebug(checked)
})
ss.content.Add(debugCheck)
ss.content.Add(widget.NewSeparator()) ss.content.Add(widget.NewSeparator())
cacheHeader := widget.NewLabel("Управление кэшем") cacheHeader := widget.NewLabel("Управление кэшем")
cacheHeader.TextStyle = fyne.TextStyle{Bold: true} cacheHeader.TextStyle = fyne.TextStyle{Bold: true}
@ -166,6 +237,13 @@ func (ss *SettingsScreen) setupContent() {
}) })
ss.content.Add(aboutButton) ss.content.Add(aboutButton)
ss.content.Add(widget.NewSeparator())
infoButton := widget.NewButton("Я думаю тебя направили сюда)", func() {
ss.uiManager.ShowInfoDialog()
})
ss.content.Add(infoButton)
ss.content.Refresh() ss.content.Refresh()
} }

View File

@ -141,6 +141,22 @@ func (tds *ThreadDetailScreen) displayThreadDetail() {
header.Wrapping = fyne.TextWrapWord header.Wrapping = fyne.TextWrapWord
tds.content.Add(header) tds.content.Add(header)
buttonsContainer := container.NewHBox()
addCommentButton := widget.NewButton("Добавить комментарий", func() {
addCommentScreen := NewAddCommentScreen(tds.uiManager, tds.board.Code, tds.thread.ID)
tds.uiManager.PushScreen(addCommentScreen)
})
buttonsContainer.Add(addCommentButton)
refreshButton := widget.NewButton("Обновить", func() {
tds.uiManager.ClearCacheForThread(tds.thread.ID)
tds.loadThreadDetail()
})
buttonsContainer.Add(refreshButton)
tds.content.Add(buttonsContainer)
threadContainer := tds.createThreadContainer(tds.threadDetail) threadContainer := tds.createThreadContainer(tds.threadDetail)
tds.content.Add(threadContainer) tds.content.Add(threadContainer)

View File

@ -140,6 +140,16 @@ func (ts *ThreadsScreen) displayThreads() {
header.TextStyle = fyne.TextStyle{Bold: true} header.TextStyle = fyne.TextStyle{Bold: true}
ts.content.Add(header) ts.content.Add(header)
buttonsContainer := container.NewHBox()
createThreadButton := widget.NewButton("Создать тред", func() {
createThreadScreen := NewCreateThreadScreen(ts.uiManager, ts.board.Code)
ts.uiManager.PushScreen(createThreadScreen)
})
buttonsContainer.Add(createThreadButton)
ts.content.Add(buttonsContainer)
if len(ts.threads) == 0 { if len(ts.threads) == 0 {
ts.content.Add(widget.NewLabel("Тредов не найдено")) ts.content.Add(widget.NewLabel("Тредов не найдено"))
ts.content.Refresh() ts.content.Refresh()