Clean up: move docs to Obsidian vault, remove outdated md files

This commit is contained in:
2026-04-21 15:54:20 +05:00
parent 448403d7ee
commit 4c50036172
22 changed files with 1295 additions and 2869 deletions
+228
View File
@@ -0,0 +1,228 @@
import 'package:flutter_riverpod/flutter_riverpod.dart';
import '../core/gitea_sync_service.dart';
import '../core/offline_mindmap_repository.dart';
import '../core/markdown_mindmap_parser.dart';
import '../models/mind_map_model.dart';
/// Provider для GiteaSyncService
final giteaSyncServiceProvider = Provider<GiteaSyncService>((ref) {
return GiteaSyncService();
});
/// Provider для OfflineMindmapRepository
final offlineMindmapRepoProvider = Provider<OfflineMindmapRepository>((ref) {
return OfflineMindmapRepository();
});
/// State для mindmap синхронизации
class MindmapSyncState {
final List<GiteaFile> files;
final List<CachedMindmap> cachedMindmaps;
final bool isLoading;
final bool isSyncing;
final String? error;
final bool isConfigured;
MindmapSyncState({
this.files = const [],
this.cachedMindmaps = const [],
this.isLoading = false,
this.isSyncing = false,
this.error,
this.isConfigured = false,
});
MindmapSyncState copyWith({
List<GiteaFile>? files,
List<CachedMindmap>? cachedMindmaps,
bool? isLoading,
bool? isSyncing,
String? error,
bool? isConfigured,
}) {
return MindmapSyncState(
files: files ?? this.files,
cachedMindmaps: cachedMindmaps ?? this.cachedMindmaps,
isLoading: isLoading ?? this.isLoading,
isSyncing: isSyncing ?? this.isSyncing,
error: error ?? this.error,
isConfigured: isConfigured ?? this.isConfigured,
);
}
}
/// Notifier для управления синхронизацией mindmaps
class MindmapSyncNotifier extends StateNotifier<MindmapSyncState> {
final GiteaSyncService _gitea;
final OfflineMindmapRepository _repo;
MindmapSyncNotifier(this._gitea, this._repo) : super(MindmapSyncState());
/// Инициализация сервиса
Future<void> initialize() async {
await _repo.initialize();
await _gitea.initialize();
state = state.copyWith(isConfigured: _gitea.isConfigured);
if (state.isConfigured) {
await loadCachedMindmaps();
}
}
/// Настройка Gitea
Future<void> configure({
required String baseUrl,
required String token,
required String owner,
required String repo,
}) async {
state = state.copyWith(isLoading: true, error: null);
try {
await _gitea.configure(
baseUrl: baseUrl,
token: token,
owner: owner,
repo: repo,
);
final connected = await _gitea.testConnection();
if (!connected) {
throw Exception('Failed to connect to Gitea. Check token and URL.');
}
state = state.copyWith(isConfigured: true, isLoading: false);
await syncFromGitea();
} catch (e) {
state = state.copyWith(isLoading: false, error: e.toString());
}
}
/// Загрузить mindmaps из кэша
Future<void> loadCachedMindmaps() async {
final cached = _repo.getAllMindmaps();
state = state.copyWith(cachedMindmaps: cached);
}
/// Полная синхронизация с Gitea (download)
Future<void> syncFromGitea({String branch = 'main'}) async {
if (!state.isConfigured) return;
state = state.copyWith(isSyncing: true, error: null);
try {
// Получаем дерево файлов
final files = await _gitea.getTree(branch: branch);
final mindmapFiles = files.where((f) =>
f.path.endsWith('.md') || f.path.endsWith('.canvas')).toList();
// Загружаем каждый файл
for (final file in mindmapFiles) {
try {
final giteaFile = await _gitea.getFile(path: file.path, branch: branch);
final content = giteaFile.decodedContent;
if (content != null) {
await _repo.cacheMindmap(CachedMindmap(
path: file.path,
content: content,
sha: giteaFile.sha,
lastSync: DateTime.now(),
lastModified: DateTime.now(),
branch: branch,
));
}
} catch (e) {
// Логируем ошибку но продолжаем
print('Failed to sync ${file.path}: $e');
}
}
await loadCachedMindmaps();
state = state.copyWith(isSyncing: false);
} catch (e) {
state = state.copyWith(isSyncing: false, error: e.toString());
}
}
/// Парсить markdown-кэш в MindMapNode
List<MindMapNode>? parseMindmap(String path) {
final cached = _repo.getMindmap(path);
if (cached == null) return null;
return MarkdownMindmapParser.parse(
cached.content,
path.replaceAll('/', '_'),
);
}
/// Сохранить изменения mindmap и запушить в Gitea
Future<void> saveAndPush({
required String path,
required List<MindMapNode> nodes,
String? title,
String branch = 'main',
}) async {
if (!state.isConfigured) {
throw Exception('Gitea not configured');
}
final markdown = MarkdownMindmapParser.toMarkdown(nodes, title: title);
// Сохраняем локально
await _repo.cacheMindmap(CachedMindmap(
path: path,
content: markdown,
lastSync: DateTime.now(),
lastModified: DateTime.now(),
branch: branch,
));
// Пушим в Gitea
try {
await _gitea.updateFile(
path: path,
content: markdown,
commitMessage: 'mobile: update $path',
branch: branch,
authorName: 'NANAXE Mobile',
authorEmail: 'mobile@nanaxe.local',
);
} catch (e) {
// Если нет сети — кладём в очередь
await _repo.queueChange(
path: path,
content: markdown,
operation: 'update',
commitMessage: 'mobile: update $path',
);
}
await loadCachedMindmaps();
}
/// Отправить накопленные offline-изменения
Future<void> pushPendingChanges({String branch = 'main'}) async {
if (!state.isConfigured) return;
final pending = _repo.getPendingChanges();
for (final change in pending) {
try {
await _gitea.updateFile(
path: change['path'],
content: change['content'],
commitMessage: change['commitMessage'] ?? 'Mobile sync',
branch: branch,
);
// Удаляем из очереди по timestamp_path
final key = '${change['timestamp']}_${change['path']}';
await _repo.removeQueuedChange(key);
} catch (e) {
print('Failed to push queued change: $e');
}
}
}
}
/// StateNotifierProvider для mindmap синхронизации
final mindmapSyncProvider =
StateNotifierProvider<MindmapSyncNotifier, MindmapSyncState>((ref) {
final gitea = ref.watch(giteaSyncServiceProvider);
final repo = ref.watch(offlineMindmapRepoProvider);
return MindmapSyncNotifier(gitea, repo);
});