• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

wger-project / flutter / 30854254088

03 Aug 2026 09:21PM UTC coverage: 40.895% (-15.0%) from 55.93%
30854254088

Pull #1260

github

web-flow
Merge d93794de5 into e3affdd4f
Pull Request #1260: WIP: health sync

9980 of 24404 relevant lines covered (40.89%)

1.72 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

96.15
/lib/core/widgets/error.dart
1
/*
2
 * This file is part of wger Workout Manager <https://github.com/wger-project>.
3
 * Copyright (c) 2026 - 2026 wger Team
4
 *
5
 * wger Workout Manager is free software: you can redistribute it and/or modify
6
 * it under the terms of the GNU Affero General Public License as published by
7
 * the Free Software Foundation, either version 3 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU Affero General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU Affero General Public License
16
 * along with this program.  If not, see <http://www.gnu.org/licenses/>.
17
 */
18

19
import 'package:flutter/material.dart';
20
import 'package:wger/core/error_dialogs.dart' show ServerHtmlError;
21
import 'package:wger/core/exceptions/http_exception.dart';
22
import 'package:wger/l10n/generated/app_localizations.dart';
23

24
/// Maximum visible height for the inline error display. Long stack traces or
25
/// HTML pages scroll inside this box rather than blowing up the parent card.
26
const double _kMaxErrorHeight = 280;
27

28
/// Inline error indicator used by `AsyncValueWidget`
29
///
30
/// Accepts the raw error object so we can introspect it - if it's a
31
/// [WgerHttpException] with [ErrorType.html] (typical for Django's debug
32
/// pages and 500 errors served as HTML), the widget renders a Preview / Raw
33
/// toggle so the user can see the page as styled HTML or copy the source.
34
/// Anything else (plain strings, JSON, generic exceptions) renders as
35
/// scrollable monospace text.
36
class StreamErrorIndicator extends StatelessWidget {
37
  const StreamErrorIndicator(this.error, {this.stacktrace, super.key});
1✔
38

39
  final Object error;
40
  final StackTrace? stacktrace;
41

42
  @override
1✔
43
  Widget build(BuildContext context) {
44
    final theme = Theme.of(context);
1✔
45
    final i18n = AppLocalizations.of(context);
1✔
46

47
    return Padding(
1✔
48
      padding: const EdgeInsets.all(8),
49
      child: Column(
1✔
50
        crossAxisAlignment: CrossAxisAlignment.start,
51
        mainAxisSize: MainAxisSize.min,
52
        children: [
1✔
53
          Row(
1✔
54
            children: [
1✔
55
              Icon(Icons.error_outline, color: theme.colorScheme.error, size: 20),
3✔
56
              const SizedBox(width: 8),
57
              Expanded(
1✔
58
                child: Text(
1✔
59
                  i18n.anErrorOccurred,
1✔
60
                  style: theme.textTheme.bodyMedium?.copyWith(
3✔
61
                    color: theme.colorScheme.error,
2✔
62
                    fontWeight: FontWeight.w600,
63
                  ),
64
                ),
65
              ),
66
            ],
67
          ),
68
          const SizedBox(height: 8),
69
          ConstrainedBox(
1✔
70
            constraints: const BoxConstraints(maxHeight: _kMaxErrorHeight),
71
            child: _buildBody(context),
1✔
72
          ),
73
        ],
74
      ),
75
    );
76
  }
77

78
  Widget _buildBody(BuildContext context) {
1✔
79
    final error = this.error;
1✔
80
    if (error is WgerHttpException && error.type == ErrorType.html) {
3✔
81
      return _HtmlErrorBody(html: error.htmlError);
2✔
82
    }
83
    return _PlainErrorBody(text: error.toString(), stacktrace: stacktrace);
3✔
84
  }
85
}
86

87
/// Preview / Raw toggle for HTML errors. Mirrors the pattern used by the
88
/// markdown editor so the UI feels familiar.
89
class _HtmlErrorBody extends StatelessWidget {
90
  const _HtmlErrorBody({required this.html});
1✔
91

92
  final String html;
93

94
  @override
1✔
95
  Widget build(BuildContext context) {
96
    final i18n = AppLocalizations.of(context);
1✔
97
    final theme = Theme.of(context);
1✔
98

99
    return DefaultTabController(
1✔
100
      length: 2,
101
      child: Column(
1✔
102
        crossAxisAlignment: CrossAxisAlignment.stretch,
103
        mainAxisSize: MainAxisSize.min,
104
        children: [
1✔
105
          TabBar(
1✔
106
            labelColor: theme.colorScheme.primary,
2✔
107
            unselectedLabelColor: theme.textTheme.bodySmall?.color,
3✔
108
            tabs: [
1✔
109
              Tab(text: i18n.preview),
2✔
110
              Tab(text: i18n.errorRawSource),
2✔
111
            ],
112
          ),
113
          const SizedBox(height: 8),
114
          Expanded(
1✔
115
            child: TabBarView(
1✔
116
              children: [
1✔
117
                SingleChildScrollView(child: ServerHtmlError(data: html)),
3✔
118
                SingleChildScrollView(
1✔
119
                  child: SelectableText(
1✔
120
                    html,
1✔
121
                    style: const TextStyle(fontFamily: 'monospace', fontSize: 12),
122
                  ),
123
                ),
124
              ],
125
            ),
126
          ),
127
        ],
128
      ),
129
    );
130
  }
131
}
132

133
/// Plain-text error body (anything that's not HTML). Renders the message
134
/// and an optional stacktrace, both scrollable and selectable so the user
135
/// can copy them into a bug report.
136
class _PlainErrorBody extends StatelessWidget {
137
  const _PlainErrorBody({required this.text, this.stacktrace});
1✔
138

139
  final String text;
140
  final StackTrace? stacktrace;
141

142
  @override
1✔
143
  Widget build(BuildContext context) {
144
    return SingleChildScrollView(
1✔
145
      child: Column(
1✔
146
        crossAxisAlignment: CrossAxisAlignment.start,
147
        mainAxisSize: MainAxisSize.min,
148
        children: [
1✔
149
          SelectableText(
1✔
150
            text,
1✔
151
            style: const TextStyle(fontFamily: 'monospace', fontSize: 12),
152
          ),
153
          if (stacktrace != null) ...[
1✔
154
            const SizedBox(height: 8),
155
            SelectableText(
×
156
              stacktrace.toString(),
×
157
              style: const TextStyle(fontFamily: 'monospace', fontSize: 11, color: Colors.grey),
158
            ),
159
          ],
160
        ],
161
      ),
162
    );
163
  }
164
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE TRIAL · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc