Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
129 changes: 129 additions & 0 deletions app/lib/screens/settings_screen.dart
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,7 @@
// Écran de configuration — sync WebDAV NAS

import 'dart:async';
import 'dart:io';
import 'package:flutter/material.dart';
import 'package:provider/provider.dart';
import 'package:shared_preferences/shared_preferences.dart';
Expand Down Expand Up @@ -31,6 +32,9 @@ class _SettingsScreenState extends State<SettingsScreen> {
Color _syncColor = Colors.green;
Map<String, dynamic> _syncStatus = {};
Map<String, dynamic> _stats = {};
Map<String, dynamic> _storageInfo = {};
final _dataDirCtrl = TextEditingController();
bool _dataDirSaved = false;

// Sync auto
bool _autoSync = false;
Expand All @@ -41,6 +45,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
super.initState();
_loadSettings();
_loadStats();
_loadStorageInfo();
}

Future<void> _loadSettings() async {
Expand All @@ -65,6 +70,38 @@ class _SettingsScreenState extends State<SettingsScreen> {
} catch (_) {}
}

Future<void> _loadStorageInfo() async {
final state = context.read<AppState>();
try {
final info = await state.client.getStorageInfo();
final prefs = await SharedPreferences.getInstance();
final savedDir = prefs.getString('data_dir') ?? info['data_dir'] ?? '';
setState(() {
_storageInfo = info;
_dataDirCtrl.text = savedDir;
});
} catch (_) {}
}

Future<void> _saveDataDir() async {
final prefs = await SharedPreferences.getInstance();
final newDir = _dataDirCtrl.text.trim();
if (newDir.isEmpty) return;
await prefs.setString('data_dir', newDir);

// Écrire dans ~/.nexanote-config pour que nexanote.sh le lise au prochain démarrage
try {
final home = Platform.environment['HOME'] ?? '/tmp';
final configFile = File('$home/.nexanote-config');
await configFile.writeAsString('data_dir=$newDir\n');
} catch (_) {}

setState(() => _dataDirSaved = true);
Future.delayed(const Duration(seconds: 3), () {
if (mounted) setState(() => _dataDirSaved = false);
});
}

Future<void> _loadSyncStatus() async {
final state = context.read<AppState>();
try {
Expand Down Expand Up @@ -129,6 +166,7 @@ class _SettingsScreenState extends State<SettingsScreen> {
_nasUrlCtrl.dispose();
_nasUserCtrl.dispose();
_nasPassCtrl.dispose();
_dataDirCtrl.dispose();
super.dispose();
}

Expand Down Expand Up @@ -349,6 +387,97 @@ class _SettingsScreenState extends State<SettingsScreen> {

const SizedBox(height: 24),

// ── Dossier de notes ───────────────────────────────────
_SectionTitle(icon: Icons.folder_outlined, title: 'Dossier de notes'),
Card(
child: Padding(
padding: const EdgeInsets.all(16),
child: Column(
crossAxisAlignment: CrossAxisAlignment.start,
children: [
// Dossier actuel (lu depuis l'API)
if (_storageInfo['data_dir'] != null) ...[
Row(children: [
Icon(Icons.folder, size: 16, color: const Color(0xFF6366F1)),
const SizedBox(width: 8),
Expanded(
child: Text(
'Actuel : ${_storageInfo['data_dir']}',
style: TextStyle(
fontSize: 12,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
fontFamily: 'monospace',
),
overflow: TextOverflow.ellipsis,
),
),
if (_storageInfo['db_size_mb'] != null)
Text(
'${_storageInfo['db_size_mb']} MB',
style: TextStyle(
fontSize: 11,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.4),
),
),
]),
const SizedBox(height: 12),
],

// Champ pour choisir un nouveau dossier
TextField(
controller: _dataDirCtrl,
decoration: const InputDecoration(
labelText: 'Dossier de données',
hintText: '/home/user/mes-notes',
prefixIcon: Icon(Icons.folder_open_outlined),
),
),
const SizedBox(height: 8),

// Info redémarrage
Container(
padding: const EdgeInsets.all(10),
decoration: BoxDecoration(
color: const Color(0xFF6366F1).withOpacity(0.08),
borderRadius: BorderRadius.circular(8),
),
child: Row(children: [
const Icon(Icons.info_outline, size: 14, color: Color(0xFF6366F1)),
const SizedBox(width: 8),
Expanded(
child: Text(
'Le changement sera pris en compte au prochain démarrage via nexanote.sh',
style: TextStyle(
fontSize: 11,
color: Theme.of(context).colorScheme.onSurface.withOpacity(0.6),
),
),
),
]),
),
const SizedBox(height: 12),

// Bouton sauvegarder
SizedBox(
width: double.infinity,
child: OutlinedButton.icon(
icon: _dataDirSaved
? const Icon(Icons.check_circle, color: Colors.green, size: 16)
: const Icon(Icons.save_outlined, size: 16),
label: Text(_dataDirSaved ? 'Sauvegardé !' : 'Sauvegarder le dossier'),
onPressed: _saveDataDir,
style: _dataDirSaved
? OutlinedButton.styleFrom(foregroundColor: Colors.green)
: null,
),
),
],
),
),
),

const SizedBox(height: 24),

// ── Backend API ────────────────────────────────────────
_SectionTitle(icon: Icons.api, title: 'Backend API'),
Card(
Expand Down
6 changes: 6 additions & 0 deletions app/lib/services/api_client.dart
Original file line number Diff line number Diff line change
Expand Up @@ -305,4 +305,10 @@ class ApiClient {
if (resp.statusCode == 200) return jsonDecode(resp.body);
return {};
}

Future<Map<String, dynamic>> getStorageInfo() async {
final resp = await http.get(Uri.parse('$baseUrl/storage'));
if (resp.statusCode == 200) return jsonDecode(resp.body);
return {};
}
}
13 changes: 12 additions & 1 deletion nexanote.sh
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,16 @@ APP_DIR="$NEXANOTE_DIR/app"
VENV_ACTIVATE="$NEXANOTE_DIR/venv/bin/activate"
LOG_FILE="/tmp/nexanote_backend.log"
BACKEND_PID=""
CONFIG_FILE="$HOME/.nexanote-config"

# Lire le dossier de données depuis ~/.nexanote-config (si existant)
DATA_DIR="$HOME/.nexanote"
if [[ -f "$CONFIG_FILE" ]]; then
SAVED_DIR=$(grep -E '^data_dir=' "$CONFIG_FILE" | cut -d'=' -f2-)
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

P1 Badge Tolerate missing data_dir in config parsing

Because the script runs with set -euo pipefail, the grep pipeline in SAVED_DIR=$(...) exits non-zero when ~/.nexanote-config exists but does not contain a data_dir= line, which aborts nexanote.sh before the backend starts. This is a startup regression for users with an empty/legacy config file or any config missing that key.

Useful? React with 👍 / 👎.

if [[ -n "$SAVED_DIR" ]]; then
DATA_DIR="$SAVED_DIR"
fi
fi

cleanup() {
if [[ -n "${BACKEND_PID}" ]]; then
Expand All @@ -17,6 +27,7 @@ trap cleanup EXIT

if ! curl -s http://127.0.0.1:8766/health >/dev/null 2>&1; then
echo "🚀 Démarrage du backend..."
echo "📂 Dossier de données : $DATA_DIR"
if [[ -f "$VENV_ACTIVATE" ]]; then
# shellcheck source=/dev/null
source "$VENV_ACTIVATE"
Expand All @@ -25,7 +36,7 @@ if ! curl -s http://127.0.0.1:8766/health >/dev/null 2>&1; then
fi

cd "$NEXANOTE_DIR"
python main.py >"$LOG_FILE" 2>&1 &
python main.py --data-dir "$DATA_DIR" >"$LOG_FILE" 2>&1 &
BACKEND_PID=$!

echo -n "⏳ Attente"
Expand Down
6 changes: 5 additions & 1 deletion nexanote/api/routes.py
Original file line number Diff line number Diff line change
Expand Up @@ -549,7 +549,9 @@ def search(q: str = Query(..., min_length=1)):
notes = db.list_notes(search_title=q)
return [_note_to_schema(n) for n in notes]

return app
# ------------------------------------------------------------------
# Stockage
# ------------------------------------------------------------------

@app.get("/storage")
def get_storage_info():
Expand All @@ -561,3 +563,5 @@ def get_storage_info():
"db_path": str(db.db_path),
"db_size_mb": round(db_size / 1024 / 1024, 2),
}

return app
Loading