gsd_restapi 0.2.2 copy "gsd_restapi: ^0.2.2" to clipboard
gsd_restapi: ^0.2.2 copied to clipboard

DOCUframe REST API client for Flutter with encrypted login, session recovery, native V1/V2 endpoints, batching, uploads, and telemetry.

example/lib/main.dart

// ignore_for_file: deprecated_member_use

import 'dart:async';
import 'dart:convert';

import 'package:flutter/material.dart';
import 'package:flutter/services.dart';
import 'package:gsd_restapi/docuframe_api.dart' show DOCUframeApi;
import 'package:gsd_restapi/gsd_restapi.dart';

enum ApiArchitecture { legacy, modern }

enum V2ExampleEndpoint {
  modelStructure('model.structure', true),
  versionInfo('system.versionInfo', false),
  appConfig('system.appConfig', false),
  appTheme('system.appTheme', false),
  viewLoad('view.load', true),
  viewAction('view.action', true),
  objectDataById('objectData.getById', true),
  objectDataByQuery('objectData.getByQuery', true),
  objectDataByParentObject('objectData.getByParentObject', true);

  const V2ExampleEndpoint(this.label, this.acceptsBody);

  final String label;
  final bool acceptsBody;

  Object? get examplePayload => switch (this) {
    V2ExampleEndpoint.modelStructure => <Object>[
      <String, Object>{
        'className': 'Object',
        'skipDerivedClasses': false,
        'skipMembers': false,
        'skipBaseMembers': true,
        'skipIndices': false,
        'skipValueMapping': false,
      },
    ],
    V2ExampleEndpoint.viewLoad => <Object>[
      <String, Object>{
        'type': '1',
        'viewName': 'edit',
        'className': 'Dokument',
        'context': 'mobile',
      },
    ],
    V2ExampleEndpoint.viewAction => <String, Object>{
      'action': <String, Object>{'oid': 'ACTION-OID'},
      'data': <String, Object>{
        'objectOid': 'OBJECT-OID',
        'values': <String, Object>{'Name': 'Test'},
      },
    },
    V2ExampleEndpoint.objectDataById => <Object>[
      <String, Object>{
        'classId': 100,
        'type': '',
        'context': 'edit',
        'ids': <int>[200, 201],
      },
    ],
    V2ExampleEndpoint.objectDataByQuery => <Object>[
      <String, Object>{
        'className': 'Dokument',
        'type': '0',
        'query': 'Name=Test',
      },
    ],
    V2ExampleEndpoint.objectDataByParentObject => <Object>[
      <String, Object>{
        'parentOID': 'PARENT-OID',
        'member': 'Dokumente',
        'context': 'edit',
        'idList': true,
      },
    ],
    V2ExampleEndpoint.versionInfo ||
    V2ExampleEndpoint.appConfig ||
    V2ExampleEndpoint.appTheme => null,
  };
}

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'GSD RestAPI Example',
      theme: ThemeData(primarySwatch: Colors.blue),
      home: const RestApiExample(),
    );
  }
}

class RestApiExample extends StatefulWidget {
  const RestApiExample({super.key});

  @override
  State<RestApiExample> createState() => _RestApiExampleState();
}

class _RestApiExampleState extends State<RestApiExample> {
  RestApiDOCUframeManager? _legacyApiManager;
  DOCUframeApi? _modernApi;
  ApiArchitecture _architecture = ApiArchitecture.legacy;
  int _apiGeneration = 0;
  String _status = 'Not connected';
  String _sessionId = 'None';
  bool _isLoggedIn = false;
  final _accountForm = GlobalKey<FormState>();
  final List<String> _logs = [];
  final TextEditingController _urlController = TextEditingController(
    text: 'https://demo.docuframe.com',
  );
  final TextEditingController _aliasController = TextEditingController(
    text: 'gsd',
  );
  final TextEditingController _usernameController = TextEditingController(
    text: 'demo',
  );
  final TextEditingController _passwordController = TextEditingController(
    text: '',
  );
  final TextEditingController _appnameController = TextEditingController(
    text: 'gsd-restapi',
  );
  final TextEditingController _bufferFlushDelayController =
      TextEditingController(text: '100');
  final TextEditingController _v2BodyController = TextEditingController(
    text: '{}',
  );
  String _v2Response = 'Noch keine V2-Anfrage ausgeführt.';
  bool _allowSslError = false;
  bool _allowMultiRequest = false;
  bool _isConfigured = false;
  bool _isChecking = false;

  bool get _usesModernApi => _architecture == ApiArchitecture.modern;

  String get _architectureLabel => _usesModernApi ? 'Neue API' : 'Legacy';

  @override
  void dispose() {
    _legacyApiManager?.dispose();
    final DOCUframeApi? modernApi = _modernApi;
    if (modernApi != null) {
      unawaited(modernApi.dispose());
    }
    _urlController.dispose();
    _aliasController.dispose();
    _usernameController.dispose();
    _passwordController.dispose();
    _appnameController.dispose();
    _bufferFlushDelayController.dispose();
    _v2BodyController.dispose();
    super.dispose();
  }

  RestApiDOCUframeCallbacks _createCallbacks(String source, int generation) {
    return RestApiDOCUframeCallbacks(
      onSessionIdChanged: (sessionId) async {
        _setStateForGeneration(generation, () {
          _sessionId = sessionId;
          _addLog('Session ID changed: $_sessionId');
        });
      },
      onUserAndPassWrong: (exception) async {
        _setStateForGeneration(generation, () {
          _addLog('Authentication failed: ${exception.message}');
        });
      },
      onLicenseWrong: (exception) async {
        _setStateForGeneration(generation, () {
          _addLog('License error detected: ${exception.message}');
        });
      },
      onLogMessage: (message) async {
        _setStateForGeneration(generation, () {
          _addLog('$source: $message');
        });
      },
      onHttpMetricRecorded: (RestApiHttpMetric metric) async {
        _setStateForGeneration(generation, () {
          _addLog(
            "API Call: ${metric.path} - ${metric.duration?.inMilliseconds}ms",
          );
        });
      },
    );
  }

  void _setStateForGeneration(int generation, VoidCallback update) {
    if (!mounted || generation != _apiGeneration) {
      return;
    }
    setState(update);
  }

  void _addLog(String message) {
    _logs.add('${DateTime.now().toLocal()}: $message');
    if (_logs.length > 20) {
      _logs.removeAt(0);
    }
  }

  RestApiDOCUframeConfig _buildConfiguration() {
    return RestApiDOCUframeConfig(
      appKey: '123',
      userName: _usernameController.text,
      appNames: <String>[_appnameController.text],
      serverUrl: _urlController.text,
      alias: _aliasController.text,
      allowSslError: _allowSslError,
      device: RestApiDevice('gsd_utilities_example_app'),
      multiRequest: _allowMultiRequest,
      bufferFlushDelayMs: int.parse(_bufferFlushDelayController.text),
    );
  }

  Future<void> _releaseApis() async {
    _apiGeneration++;
    _legacyApiManager?.dispose();
    _legacyApiManager = null;

    final DOCUframeApi? modernApi = _modernApi;
    _modernApi = null;
    if (modernApi != null) {
      await modernApi.dispose();
    }
  }

  Future<void> _switchArchitecture(Set<ApiArchitecture> selection) async {
    if (selection.isEmpty || selection.first == _architecture || _isChecking) {
      return;
    }

    await _releaseApis();
    if (!mounted) {
      return;
    }

    setState(() {
      _architecture = selection.first;
      _isConfigured = false;
      _isLoggedIn = false;
      _sessionId = 'None';
      _status = 'Not connected';
      _v2Response = 'Noch keine V2-Anfrage ausgeführt.';
      _addLog('Switched to $_architectureLabel architecture');
    });
  }

  Future<void> _checkService() async {
    final int generation = _apiGeneration;
    final bool usesModernApi = _usesModernApi;
    try {
      _setStateForGeneration(generation, () {
        _status = 'Checking service...';
      });

      final RestApiCheckServiceResponse response = usesModernApi
          ? await _modernApi!.v1.service.checkService()
          : await _legacyApiManager!.checkService();

      _setStateForGeneration(generation, () {
        _status = response.isOk ? 'Service available' : 'Service unavailable';
        _addLog('Service check: ${response.isOk ? 'OK' : 'Failed'}');
      });
    } catch (e) {
      _setStateForGeneration(generation, () {
        _status = 'Service check failed';
        _addLog('Service check error: $e');
      });
    }
  }

  Future<void> _login() async {
    final int generation = _apiGeneration;
    final bool usesModernApi = _usesModernApi;
    try {
      _setStateForGeneration(generation, () {
        _status = 'Logging in...';
      });

      final String md5Password = _passwordController.text.toMd5Hash();
      final RestApiLoginResponse loginResponse = usesModernApi
          ? await _modernApi!.v1.authentication.login(md5Password)
          : await _legacyApiManager!.login(md5Password);

      _setStateForGeneration(generation, () {
        _isLoggedIn = loginResponse.isOk;
        _status = _isLoggedIn ? 'Logged in successfully' : 'Login failed';
        _sessionId = usesModernApi
            ? _modernApi!.sessionId
            : _legacyApiManager!.config.sessionId;
        _addLog('Login attempt: ${_isLoggedIn ? 'Success' : 'Failed'}');
      });
    } catch (e) {
      _setStateForGeneration(generation, () {
        _status = 'Login error';
        _isLoggedIn = false;
        _addLog('Login error: $e');
      });
    }
  }

  Future<void> _getDocumentCount() async {
    final int generation = _apiGeneration;
    final bool usesModernApi = _usesModernApi;
    try {
      _setStateForGeneration(generation, () {
        _status = 'Fetching document count...';
      });

      final int count;
      if (usesModernApi) {
        final RestApiResponse response = await _modernApi!.v1.objects
            .getObjects('Dokument');
        count = _documentCount(
          jsonDecode(response.httpResponse.body) as Map<String, dynamic>,
        );
      } else {
        final RestApiResponse response = await _legacyApiManager!.getObjects(
          'Dokument',
        );
        count = _documentCount(
          jsonDecode(response.httpResponse.body) as Map<String, dynamic>,
        );
      }

      _setStateForGeneration(generation, () {
        _status = 'Document count fetched: $count';
        _addLog('Document count: $count');
      });
    } catch (e) {
      _setStateForGeneration(generation, () {
        _status = 'Error fetching document count';
        _addLog('Document count error: $e');
      });
    }
  }

  Future<void> _createNote() async {
    final int generation = _apiGeneration;
    final bool usesModernApi = _usesModernApi;
    try {
      _setStateForGeneration(generation, () {
        _status = 'Creating note...';
      });

      final String body = jsonEncode({
        'Text': 'Dies ist eine automatisch erstellte Notiz.',
        'Name': 'Testnotiz ${DateTime.now().toIso8601String()}',
        'Beschreibung': 'Notiz erstellt via GSD RestAPI Flutter Beispiel',
        '~StoreTime': DateTime.now().toISOFormatString(),
      });

      if (usesModernApi) {
        await _modernApi!.v1.objects.postObject('Notiz', body, storeMode: 10);
      } else {
        await _legacyApiManager!.postObject('Notiz', body, storeMode: 10);
      }

      _setStateForGeneration(generation, () {
        _status = 'Note created successfully';
        _addLog('Note created successfully');
      });
    } catch (e) {
      _setStateForGeneration(generation, () {
        _status = 'Error creating note';
        _addLog('Note creation error: $e');
      });
    }
  }

  Future<void> _testMultiRequest() async {
    final int generation = _apiGeneration;
    final bool usesModernApi = _usesModernApi;
    try {
      _setStateForGeneration(generation, () {
        _status = 'Testing MultiRequest with 10 distinct pages...';
      });

      final List<int> counts;
      if (usesModernApi) {
        final List<RestApiResponse> responses = await Future.wait(
          List<Future<RestApiResponse>>.generate(
            10,
            (index) =>
                _modernApi!.v1.objects.getObjects('Dokument', page: index),
          ),
        );
        counts = responses
            .map(
              (response) => _documentCount(
                jsonDecode(response.httpResponse.body) as Map<String, dynamic>,
              ),
            )
            .toList(growable: false);
      } else {
        final List<RestApiResponse> responses = await Future.wait(
          List<Future<RestApiResponse>>.generate(
            10,
            (index) => _legacyApiManager!.getObjects('Dokument', page: index),
          ),
        );
        counts = responses
            .map(
              (response) => _documentCount(
                jsonDecode(response.httpResponse.body) as Map<String, dynamic>,
              ),
            )
            .toList(growable: false);
      }

      _setStateForGeneration(generation, () {
        _addLog('MultiRequest test completed for pages 0-9');
        for (var index = 0; index < counts.length; index++) {
          _addLog('Page $index: Document count: ${counts[index]}');
        }
        _status = 'MultiRequest test completed successfully';
      });
    } catch (e) {
      _setStateForGeneration(generation, () {
        _status = 'MultiRequest test failed';
        _addLog('MultiRequest test error: $e');
      });
    }
  }

  Future<void> _checkSession() async {
    final int generation = _apiGeneration;
    final bool usesModernApi = _usesModernApi;
    try {
      _setStateForGeneration(generation, () {
        _status = 'Checking session...';
      });

      final RestApiResponse sessionResponse = usesModernApi
          ? await _modernApi!.v1.authentication.checkSession()
          : await _legacyApiManager!.checkSession();

      _setStateForGeneration(generation, () {
        _status = sessionResponse.isOk ? 'Session valid' : 'Session invalid';
        _addLog('Session check: ${sessionResponse.isOk ? 'Valid' : 'Invalid'}');
      });
    } catch (e) {
      _setStateForGeneration(generation, () {
        _status = 'Session check failed';
        _addLog('Session check error: $e');
      });
    }
  }

  Future<void> _logout() async {
    final int generation = _apiGeneration;
    final bool usesModernApi = _usesModernApi;
    try {
      _setStateForGeneration(generation, () {
        _status = 'Logging out...';
      });

      if (usesModernApi) {
        await _modernApi!.v1.authentication.logout();
      } else {
        await _legacyApiManager!.logout();
      }

      _setStateForGeneration(generation, () {
        _isLoggedIn = false;
        _sessionId = 'None';
        _status = 'Logged out';
        _addLog('Logged out successfully');
      });
    } catch (e) {
      _setStateForGeneration(generation, () {
        _status = 'Logout error';
        _addLog('Logout error: $e');
      });
    }
  }

  Future<void> _executeV2Endpoint(V2ExampleEndpoint endpoint) async {
    final int generation = _apiGeneration;

    try {
      _setStateForGeneration(generation, () {
        _status = 'Calling v2.${endpoint.label}...';
      });

      String? body;
      if (endpoint.acceptsBody) {
        final String candidate = _v2BodyController.text.trim();
        if (candidate.isNotEmpty) {
          jsonDecode(candidate);
          body = candidate;
        }
      }

      final Future<RestApiResponse> request = switch (endpoint) {
        V2ExampleEndpoint.modelStructure => _modernApi!.v2.model.structure(
          body: body,
        ),
        V2ExampleEndpoint.versionInfo => _modernApi!.v2.system.versionInfo(),
        V2ExampleEndpoint.appConfig => _modernApi!.v2.system.appConfig(),
        V2ExampleEndpoint.appTheme => _modernApi!.v2.system.appTheme(),
        V2ExampleEndpoint.viewLoad => _modernApi!.v2.view.load(body: body),
        V2ExampleEndpoint.viewAction => _modernApi!.v2.view.action(body: body),
        V2ExampleEndpoint.objectDataById => _modernApi!.v2.objectData.getById(
          body: body,
        ),
        V2ExampleEndpoint.objectDataByQuery =>
          _modernApi!.v2.objectData.getByQuery(body: body),
        V2ExampleEndpoint.objectDataByParentObject =>
          _modernApi!.v2.objectData.getByParentObject(body: body),
      };
      final RestApiResponse response = await request;
      final String formattedResponse = _formatJson(response.httpResponse.body);

      _setStateForGeneration(generation, () {
        _status = 'v2.${endpoint.label} completed';
        _v2Response = formattedResponse;
        _addLog(
          'V2 ${endpoint.label}: HTTP ${response.httpResponse.statusCode}, '
          'internal status ${response.internalStatus}',
        );
      });
    } on FormatException catch (error) {
      _setStateForGeneration(generation, () {
        _status = 'Invalid V2 JSON body';
        _addLog('V2 ${endpoint.label}: invalid JSON body ($error)');
      });
    } catch (error) {
      _setStateForGeneration(generation, () {
        _status = 'V2 request failed';
        _addLog('V2 ${endpoint.label} error: $error');
      });
    }
  }

  void _loadV2BodyTemplate(V2ExampleEndpoint endpoint) {
    final Object? payload = endpoint.examplePayload;
    if (payload == null) {
      return;
    }

    _v2BodyController.text = const JsonEncoder.withIndent(
      '  ',
    ).convert(payload);
    setState(() {
      _status = 'Vorlage für v2.${endpoint.label} geladen';
    });
  }

  String _formatJson(String source) {
    try {
      return const JsonEncoder.withIndent('  ').convert(jsonDecode(source));
    } on FormatException {
      return source;
    }
  }

  int _documentCount(Map<String, dynamic> envelope) {
    final dynamic data = envelope['data'];
    final dynamic value = data is Map<String, dynamic> ? data['~Count'] : null;
    if (value is int) {
      return value;
    }
    if (value is num) {
      return value.toInt();
    }
    return int.parse(value.toString());
  }

  /// Zeigt einen Fehler-Dialog
  void _showErrorDialog(String title, String message) {
    showDialog(
      context: context,
      builder: (BuildContext context) {
        return AlertDialog(
          title: Text(title),
          content: Text(message),
          actions: [
            TextButton(
              onPressed: () => Navigator.of(context).pop(),
              child: const Text('OK'),
            ),
          ],
        );
      },
    );
  }

  /// Erstellt genau einen Client für die ausgewählte Architektur.
  Future<void> _configureApi() async {
    setState(() {
      _isChecking = true;
    });

    try {
      await _releaseApis();
      final int generation = _apiGeneration;
      final RestApiDOCUframeConfig configuration = _buildConfiguration();

      if (_usesModernApi) {
        _modernApi = DOCUframeApi(
          configuration: configuration,
          callbacks: _createCallbacks('DOCUframeApi', generation),
        );
      } else {
        _legacyApiManager = RestApiDOCUframeManager(
          config: configuration,
          callbacks: _createCallbacks('RestApiDOCUframeManager', generation),
        );
      }

      if (!mounted) {
        return;
      }
      setState(() {
        _isConfigured = true;
        _status = '$_architectureLabel API configured';
        _addLog('$_architectureLabel API configured');
      });
    } catch (e) {
      if (mounted) {
        _showErrorDialog(
          'Konfigurationsfehler',
          'API konnte nicht erstellt werden:\n$e',
        );
      }
    } finally {
      if (mounted) {
        setState(() {
          _isChecking = false;
        });
      }
    }
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: const Text('GSD RestAPI Example'),
        backgroundColor: Colors.blue,
        foregroundColor: Colors.white,
      ),
      body: SingleChildScrollView(
        child: Padding(
          padding: const EdgeInsets.all(16.0),
          child: Column(
            crossAxisAlignment: CrossAxisAlignment.start,
            children: [
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Text(
                        'Connection Status',
                        style: Theme.of(context).textTheme.headlineSmall,
                      ),
                      const SizedBox(height: 8),
                      Text('Status: $_status'),
                      Text('Session ID: $_sessionId'),
                      Text('Logged in: ${_isLoggedIn ? 'Yes' : 'No'}'),
                      Text('Architecture: $_architectureLabel'),
                      const SizedBox(height: 12),
                      SegmentedButton<ApiArchitecture>(
                        segments: const <ButtonSegment<ApiArchitecture>>[
                          ButtonSegment<ApiArchitecture>(
                            value: ApiArchitecture.legacy,
                            label: Text('Legacy'),
                            icon: Icon(Icons.history),
                          ),
                          ButtonSegment<ApiArchitecture>(
                            value: ApiArchitecture.modern,
                            label: Text('Neue API'),
                            icon: Icon(Icons.auto_awesome),
                          ),
                        ],
                        selected: <ApiArchitecture>{_architecture},
                        onSelectionChanged: _isChecking
                            ? null
                            : _switchArchitecture,
                      ),
                    ],
                  ),
                ),
              ),
              const SizedBox(height: 16),
              Card(
                child: Padding(
                  padding: const EdgeInsets.all(16.0),
                  child: Column(
                    crossAxisAlignment: CrossAxisAlignment.start,
                    children: [
                      Form(
                        key: _accountForm,
                        child: Column(
                          crossAxisAlignment: CrossAxisAlignment.start,
                          children: [
                            Row(
                              children: [
                                Expanded(
                                  child: Text(
                                    _usesModernApi
                                        ? 'DOCUframeApi konfigurieren'
                                        : 'RestApiDOCUframeManager konfigurieren',
                                    style: Theme.of(
                                      context,
                                    ).textTheme.headlineSmall,
                                  ),
                                ),
                                const SizedBox(width: 8),
                                if (_isConfigured)
                                  Icon(
                                    Icons.check_circle,
                                    color: Colors.green,
                                    size: 20,
                                  ),
                              ],
                            ),
                            const SizedBox(height: 16),

                            // Server URL
                            TextFormField(
                              controller: _urlController,
                              enabled: !_isConfigured,
                              decoration: const InputDecoration(
                                labelText: 'Server URL',
                                border: OutlineInputBorder(),
                                prefixIcon: Icon(Icons.link),
                              ),
                            ),
                            const SizedBox(height: 12),

                            // Alias und Username
                            Row(
                              children: [
                                Expanded(
                                  child: TextFormField(
                                    controller: _aliasController,
                                    enabled: !_isConfigured,
                                    decoration: const InputDecoration(
                                      labelText: 'Alias',
                                      border: OutlineInputBorder(),
                                      prefixIcon: Icon(Icons.alternate_email),
                                    ),
                                  ),
                                ),
                                const SizedBox(width: 8),
                                Expanded(
                                  child: TextFormField(
                                    controller: _usernameController,
                                    enabled: !_isConfigured,
                                    decoration: const InputDecoration(
                                      labelText: 'Username',
                                      border: OutlineInputBorder(),
                                      prefixIcon: Icon(Icons.person),
                                    ),
                                  ),
                                ),
                              ],
                            ),
                            const SizedBox(height: 12),

                            // Passwort
                            TextFormField(
                              controller: _passwordController,
                              enabled: !_isConfigured,
                              obscureText: true,
                              decoration: const InputDecoration(
                                labelText: 'Passwort',
                                border: OutlineInputBorder(),
                                prefixIcon: Icon(Icons.lock),
                              ),
                            ),
                            const SizedBox(height: 12),

                            // Appname
                            TextFormField(
                              controller: _appnameController,
                              enabled: !_isConfigured,
                              decoration: const InputDecoration(
                                labelText: 'Appname',
                                border: OutlineInputBorder(),
                                prefixIcon: Icon(Icons.apps),
                              ),
                            ),
                            const SizedBox(height: 12),

                            // Buffer Flush Delay
                            TextFormField(
                              controller: _bufferFlushDelayController,
                              enabled: !_isConfigured,
                              decoration: const InputDecoration(
                                labelText: 'Buffer Flush Delay (ms)',
                                border: OutlineInputBorder(),
                                prefixIcon: Icon(Icons.timer),
                              ),
                              keyboardType: TextInputType.number,
                              inputFormatters: <TextInputFormatter>[
                                FilteringTextInputFormatter.digitsOnly,
                              ],
                            ),
                            const SizedBox(height: 12),

                            // Allow SSL Error Checkbox
                            CheckboxListTile(
                              title: const Text('SSL-Fehler ignorieren'),
                              subtitle: const Text(
                                'Aktivieren für Entwicklung/Test-Umgebungen',
                              ),
                              value: _allowSslError,
                              enabled: !_isConfigured,
                              onChanged: (value) {
                                setState(() {
                                  _allowSslError = value ?? false;
                                });
                              },
                            ),
                            // Allow Multi-Request Checkbox
                            CheckboxListTile(
                              title: const Text('Multi-Request erlauben'),
                              subtitle: Text(
                                _usesModernApi
                                    ? 'Wird als unveränderlicher Snapshot für '
                                          'native multi-fähige Endpoints übernommen'
                                    : 'Aktivieren um mehrere Anfragen in einer '
                                          'HTTP-Verbindung zu bündeln',
                              ),
                              value: _allowMultiRequest,
                              enabled: !_isConfigured,
                              onChanged: (value) {
                                setState(() {
                                  _allowMultiRequest = value ?? false;
                                });
                              },
                            ),
                            const SizedBox(height: 16),

                            // Konfigurieren Button
                            SizedBox(
                              width: double.infinity,
                              child: OutlinedButton.icon(
                                onPressed: (_isConfigured || _isChecking)
                                    ? null
                                    : () {
                                        if (!_accountForm.currentState!
                                            .validate()) {
                                          return;
                                        }

                                        _configureApi();
                                      },
                                icon: _isChecking
                                    ? const SizedBox(
                                        width: 16,
                                        height: 16,
                                        child: CircularProgressIndicator(
                                          strokeWidth: 2,
                                        ),
                                      )
                                    : const Icon(Icons.settings),
                                label: Text(
                                  _isConfigured
                                      ? 'Konfiguriert'
                                      : _isChecking
                                      ? 'Wird konfiguriert...'
                                      : 'DOCUframe konfigurieren',
                                ),
                              ),
                            ),
                          ],
                        ),
                      ),
                      const SizedBox(height: 8),
                      Text(
                        'Actions',
                        style: Theme.of(context).textTheme.headlineSmall,
                      ),
                      const SizedBox(height: 8),
                      Wrap(
                        spacing: 8,
                        runSpacing: 8,
                        children: [
                          ElevatedButton(
                            onPressed: _isConfigured ? _checkService : null,
                            child: const Text('Check Service'),
                          ),
                          ElevatedButton(
                            onPressed: _isConfigured && !_isLoggedIn
                                ? _login
                                : null,
                            child: const Text('Login'),
                          ),
                          ElevatedButton(
                            onPressed: _isLoggedIn ? _getDocumentCount : null,
                            child: const Text('Get Document Count'),
                          ),
                          ElevatedButton(
                            onPressed: _isLoggedIn ? _createNote : null,
                            child: const Text('Create Note'),
                          ),
                          ElevatedButton(
                            onPressed: _isLoggedIn && _allowMultiRequest
                                ? _testMultiRequest
                                : null,
                            child: const Text('Test Multi-Request (10 Seiten)'),
                          ),
                          ElevatedButton(
                            onPressed: _isLoggedIn ? _checkSession : null,
                            child: const Text('Check Session'),
                          ),
                          ElevatedButton(
                            onPressed: _isLoggedIn ? _logout : null,
                            child: const Text('Logout'),
                          ),
                        ],
                      ),
                      if (_usesModernApi) ...[
                        const SizedBox(height: 24),
                        const Divider(),
                        const SizedBox(height: 8),
                        Text(
                          'Native V2 Endpoints',
                          style: Theme.of(context).textTheme.headlineSmall,
                        ),
                        const SizedBox(height: 8),
                        const Text(
                          'Diese Aufrufe verwenden direkt api.v2 und niemals '
                          'Legacy, RawApi oder v1/multi.',
                        ),
                        const SizedBox(height: 12),
                        Text(
                          'Bodylose Endpoints',
                          style: Theme.of(context).textTheme.titleMedium,
                        ),
                        const SizedBox(height: 8),
                        Wrap(
                          spacing: 8,
                          runSpacing: 8,
                          children: V2ExampleEndpoint.values
                              .where((endpoint) => !endpoint.acceptsBody)
                              .map(
                                (endpoint) => ElevatedButton(
                                  onPressed: _isLoggedIn && !_isChecking
                                      ? () => _executeV2Endpoint(endpoint)
                                      : null,
                                  child: Text('v2.${endpoint.label}'),
                                ),
                              )
                              .toList(growable: false),
                        ),
                        const SizedBox(height: 16),
                        Text(
                          'Endpoints mit JSON Body',
                          style: Theme.of(context).textTheme.titleMedium,
                        ),
                        const SizedBox(height: 4),
                        const Text(
                          'Bis die V2-DTOs stabil sind, wird der Request-Inhalt '
                          'dieser POST-Endpunkte als rohes JSON übergeben.',
                        ),
                        const SizedBox(height: 8),
                        const Text(
                          '1. Vorlage laden, 2. Beispielwerte ersetzen, '
                          '3. den passenden Endpoint-Button ausführen.',
                        ),
                        const SizedBox(height: 8),
                        Wrap(
                          spacing: 8,
                          runSpacing: 8,
                          children: V2ExampleEndpoint.values
                              .where((endpoint) => endpoint.acceptsBody)
                              .map(
                                (endpoint) => OutlinedButton.icon(
                                  key: ValueKey<String>(
                                    'v2-template-${endpoint.name}',
                                  ),
                                  onPressed: _isChecking
                                      ? null
                                      : () => _loadV2BodyTemplate(endpoint),
                                  icon: const Icon(Icons.article_outlined),
                                  label: Text('${endpoint.label} Vorlage'),
                                ),
                              )
                              .toList(growable: false),
                        ),
                        const SizedBox(height: 12),
                        TextField(
                          key: const ValueKey<String>('v2-body-field'),
                          controller: _v2BodyController,
                          enabled: !_isChecking,
                          minLines: 3,
                          maxLines: 8,
                          style: const TextStyle(fontFamily: 'monospace'),
                          decoration: const InputDecoration(
                            labelText: 'JSON Body (optional)',
                            helperText:
                                'Bei byId stehen classId für die Klasse und ids '
                                'für die numerischen Objekt-IDs.',
                            alignLabelWithHint: true,
                            border: OutlineInputBorder(),
                          ),
                        ),
                        const SizedBox(height: 12),
                        Wrap(
                          spacing: 8,
                          runSpacing: 8,
                          children: V2ExampleEndpoint.values
                              .where((endpoint) => endpoint.acceptsBody)
                              .map(
                                (endpoint) => ElevatedButton(
                                  onPressed: _isLoggedIn && !_isChecking
                                      ? () => _executeV2Endpoint(endpoint)
                                      : null,
                                  child: Text('v2.${endpoint.label}'),
                                ),
                              )
                              .toList(growable: false),
                        ),
                        const SizedBox(height: 12),
                        Text(
                          'Letzte V2 Response',
                          style: Theme.of(context).textTheme.titleMedium,
                        ),
                        const SizedBox(height: 4),
                        Container(
                          width: double.infinity,
                          constraints: const BoxConstraints(maxHeight: 280),
                          padding: const EdgeInsets.all(12),
                          decoration: BoxDecoration(
                            color: Colors.grey[100],
                            borderRadius: BorderRadius.circular(4),
                            border: Border.all(color: Colors.grey[300]!),
                          ),
                          child: SingleChildScrollView(
                            child: SelectionArea(
                              child: Text(
                                _v2Response,
                                style: const TextStyle(
                                  fontFamily: 'monospace',
                                  fontSize: 12,
                                ),
                              ),
                            ),
                          ),
                        ),
                      ],
                    ],
                  ),
                ),
              ),
              // DOCUframe-Konfiguration
              const SizedBox(height: 16),
              SizedBox(
                height: 500,
                child: Card(
                  child: Padding(
                    padding: const EdgeInsets.all(16.0),
                    child: Column(
                      crossAxisAlignment: CrossAxisAlignment.start,
                      children: [
                        Row(
                          children: [
                            Text(
                              'Activity Log',
                              style: Theme.of(context).textTheme.headlineSmall,
                            ),
                            Spacer(),
                            IconButton(
                              icon: const Icon(Icons.clear),
                              onPressed: () {
                                setState(() {
                                  _logs.clear();
                                });
                              },
                            ),
                          ],
                        ),
                        const SizedBox(height: 8),
                        Expanded(
                          child: Container(
                            padding: const EdgeInsets.all(8),
                            decoration: BoxDecoration(
                              color: Colors.grey[100],
                              borderRadius: BorderRadius.circular(4),
                            ),
                            child: SelectionArea(
                              child: ListView.builder(
                                itemCount: _logs.length,
                                itemBuilder: (context, index) {
                                  return Text(
                                    _logs[index],
                                    style: const TextStyle(
                                      fontFamily: 'monospace',
                                      fontSize: 12,
                                    ),
                                  );
                                },
                              ),
                            ),
                          ),
                        ),
                      ],
                    ),
                  ),
                ),
              ),
            ],
          ),
        ),
      ),
    );
  }
}
1
likes
160
points
391
downloads

Documentation

Documentation
API reference

Publisher

verified publishergsd-software.com

Weekly Downloads

DOCUframe REST API client for Flutter with encrypted login, session recovery, native V1/V2 endpoints, batching, uploads, and telemetry.

Homepage
Repository (GitHub)
View/report issues

Topics

#rest-api #http-client #session-management #encryption #authentication

License

MIT (license)

Dependencies

collection, crypto, encrypter_plus, flutter, gsd_encryption, http, iso8601_duration, meta, pointycastle

More

Packages that depend on gsd_restapi