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

Oda2 / boardpocket / 22607572890

03 Mar 2026 03:56AM UTC coverage: 52.493% (+1.2%) from 51.278%
22607572890

push

github

Oda2
test: Add more testing

3 of 8 new or added lines in 2 files covered. (37.5%)

2 existing lines in 1 file now uncovered.

1832 of 3490 relevant lines covered (52.49%)

0.87 hits per line

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

5.16
/lib/data/database/database_helper.dart
1
import 'dart:convert';
2
import 'package:flutter/foundation.dart';
3
import 'package:shared_preferences/shared_preferences.dart';
4
import 'package:sqflite/sqflite.dart';
5
import 'package:sqflite_common_ffi_web/sqflite_ffi_web.dart';
6
import 'package:path/path.dart';
7

8
class DatabaseHelper {
9
  static final DatabaseHelper instance = DatabaseHelper._init();
6✔
10
  static Database? _database;
11
  static bool _initialized = false;
12

13
  // Web fallback storage
14
  SharedPreferences? _prefs;
15
  final Map<String, List<Map<String, dynamic>>> _memoryStorage = {
16
    'games': [],
17
    'wishlist': [],
18
    'players': [],
19
    'settings': [],
20
  };
21

22
  DatabaseHelper._init();
2✔
23

24
  Future<void> _ensureInitialized() async {
1✔
25
    if (_initialized) return;
26

27
    if (kIsWeb) {
28
      try {
29
        // Try to initialize for web
30
        databaseFactory = databaseFactoryFfiWeb;
×
31
      } catch (e) {
32
        print(
×
33
          'Warning: sqflite ffi web initialization failed, using memory fallback: $e',
×
34
        );
35
      }
36
      // Always use shared preferences for web storage fallback
37
      _prefs = await SharedPreferences.getInstance();
×
38
      _loadFromPrefs();
×
39
    }
40

41
    _initialized = true;
42
  }
43

44
  void _loadFromPrefs() {
×
45
    if (_prefs == null) return;
×
46

47
    final gamesJson = _prefs!.getString('games_data');
×
48
    final wishlistJson = _prefs!.getString('wishlist_data');
×
49
    final playersJson = _prefs!.getString('players_data');
×
50

51
    if (gamesJson != null) {
52
      _memoryStorage['games'] = List<Map<String, dynamic>>.from(
×
53
        json.decode(gamesJson),
×
54
      );
55
    }
56
    if (wishlistJson != null) {
57
      _memoryStorage['wishlist'] = List<Map<String, dynamic>>.from(
×
58
        json.decode(wishlistJson),
×
59
      );
60
    }
61
    if (playersJson != null) {
62
      _memoryStorage['players'] = List<Map<String, dynamic>>.from(
×
63
        json.decode(playersJson),
×
64
      );
65
    }
66
  }
67

68
  Future<void> _saveToPrefs() async {
×
69
    if (_prefs == null) return;
×
70

71
    await _prefs!.setString('games_data', json.encode(_memoryStorage['games']));
×
72
    await _prefs!.setString(
×
73
      'wishlist_data',
74
      json.encode(_memoryStorage['wishlist']),
×
75
    );
76
    await _prefs!.setString(
×
77
      'players_data',
78
      json.encode(_memoryStorage['players']),
×
79
    );
80
  }
81

82
  bool get _isWebMode => kIsWeb;
1✔
83

NEW
84
  Future<String> getDatabasePath() async {
×
NEW
85
    if (_isWebMode) {
×
NEW
86
      throw Exception('Use memory storage on web');
×
87
    }
NEW
88
    final db = await database;
×
NEW
89
    return db.path;
×
90
  }
91

92
  Future<Database> get database async {
1✔
93
    if (_isWebMode) {
1✔
94
      throw Exception('Use memory storage on web');
×
95
    }
96

97
    if (_database != null) return _database!;
98
    await _ensureInitialized();
1✔
99
    _database = await _initDB('boardpocket.db');
1✔
100
    return _database!;
101
  }
102

103
  Future<Database> _initDB(String filePath) async {
1✔
104
    final dbPath = await getDatabasesPath();
1✔
105
    final path = join(dbPath, filePath);
×
106

107
    return await openDatabase(
×
108
      path,
109
      version: 2,
110
      onCreate: _createDB,
×
111
      onUpgrade: _upgradeDB,
×
112
    );
113
  }
114

115
  Future _upgradeDB(Database db, int oldVersion, int newVersion) async {
×
116
    if (oldVersion < 2) {
×
117
      // Add wins and losses columns to games table
118
      await db.execute('ALTER TABLE games ADD COLUMN wins INTEGER DEFAULT 0');
×
119
      await db.execute('ALTER TABLE games ADD COLUMN losses INTEGER DEFAULT 0');
×
120
    }
121
  }
122

123
  Future _createDB(Database db, int version) async {
×
124
    const idType = 'TEXT PRIMARY KEY';
125
    const textType = 'TEXT';
126
    const intType = 'INTEGER';
127
    const realType = 'REAL';
128

129
    // Games table
130
    await db.execute('''
×
131
      CREATE TABLE games (
132
        id $idType,
133
        title $textType NOT NULL,
134
        image_path $textType,
135
        players $textType NOT NULL,
136
        time $textType NOT NULL,
137
        category $textType NOT NULL,
138
        min_players $intType,
139
        max_players $intType,
140
        play_time $intType,
141
        complexity $realType,
142
        total_plays $intType DEFAULT 0,
143
        wins $intType DEFAULT 0,
144
        losses $intType DEFAULT 0,
145
        win_rate $realType DEFAULT 0.0,
146
        last_played $intType,
147
        created_at $intType NOT NULL,
148
        updated_at $intType NOT NULL
149
      )
150
    ''');
151

152
    // Wishlist table
153
    await db.execute('''
×
154
      CREATE TABLE wishlist (
155
        id $idType,
156
        title $textType NOT NULL,
157
        image_path $textType,
158
        image_url $textType,
159
        price $textType,
160
        purchase_url $textType,
161
        players $textType NOT NULL,
162
        time $textType NOT NULL,
163
        tag $textType,
164
        category $textType,
165
        created_at $intType NOT NULL
166
      )
167
    ''');
168

169
    // Players table (for Name Draw)
170
    await db.execute('''
×
171
      CREATE TABLE players (
172
        id $idType,
173
        name $textType NOT NULL,
174
        created_at $intType NOT NULL
175
      )
176
    ''');
177

178
    // Settings table
179
    await db.execute('''
×
180
      CREATE TABLE settings (
181
        key $textType PRIMARY KEY,
182
        value $textType NOT NULL
183
      )
184
    ''');
185

186
    // Insert default settings
187
    await db.insert('settings', {'key': 'dark_mode', 'value': 'true'});
×
188
    await db.insert('settings', {'key': 'language', 'value': 'en'});
×
189
    await db.insert('settings', {'key': 'initialized', 'value': 'true'});
×
190
  }
191

192
  // Games CRUD - Web compatible
193
  Future<String> insertGame(Map<String, dynamic> game) async {
×
194
    if (_isWebMode) {
×
195
      await _ensureInitialized();
×
196
      _memoryStorage['games']!.add(game);
×
197
      await _saveToPrefs();
×
198
      return game['id'] as String;
×
199
    }
200

201
    final db = await database;
×
202
    await db.insert('games', game);
×
203
    return game['id'] as String;
×
204
  }
205

206
  Future<List<Map<String, dynamic>>> getAllGames() async {
1✔
207
    if (_isWebMode) {
1✔
208
      await _ensureInitialized();
×
209
      return List<Map<String, dynamic>>.from(_memoryStorage['games']!)..sort(
×
210
        (a, b) => (b['created_at'] as int).compareTo(a['created_at'] as int),
×
211
      );
212
    }
213

214
    final db = await database;
1✔
215
    return await db.query('games', orderBy: 'created_at DESC');
×
216
  }
217

218
  Future<List<Map<String, dynamic>>> searchGames(String query) async {
×
219
    if (_isWebMode) {
×
220
      await _ensureInitialized();
×
221
      return _memoryStorage['games']!
×
222
          .where(
×
223
            (g) => (g['title'] as String).toLowerCase().contains(
×
224
              query.toLowerCase(),
×
225
            ),
226
          )
227
          .toList()
×
228
        ..sort(
×
229
          (a, b) => (b['created_at'] as int).compareTo(a['created_at'] as int),
×
230
        );
231
    }
232

233
    final db = await database;
×
234
    return await db.query(
×
235
      'games',
236
      where: 'title LIKE ?',
237
      whereArgs: ['%$query%'],
×
238
      orderBy: 'created_at DESC',
239
    );
240
  }
241

242
  Future<List<Map<String, dynamic>>> getGamesByCategory(String category) async {
×
243
    if (_isWebMode) {
×
244
      await _ensureInitialized();
×
245
      return _memoryStorage['games']!
×
246
          .where((g) => g['category'] == category)
×
247
          .toList()
×
248
        ..sort(
×
249
          (a, b) => (b['created_at'] as int).compareTo(a['created_at'] as int),
×
250
        );
251
    }
252

253
    final db = await database;
×
254
    return await db.query(
×
255
      'games',
256
      where: 'category = ?',
257
      whereArgs: [category],
×
258
      orderBy: 'created_at DESC',
259
    );
260
  }
261

262
  Future<List<String>> getDistinctCategories() async {
×
263
    if (_isWebMode) {
×
264
      await _ensureInitialized();
×
265
      final categories = _memoryStorage['games']!
×
266
          .map((g) => g['category'] as String)
×
267
          .toSet()
×
268
          .toList();
×
269
      categories.sort();
×
270
      return categories;
271
    }
272

273
    final db = await database;
×
274
    final result = await db.rawQuery(
×
275
      'SELECT DISTINCT category FROM games ORDER BY category ASC',
276
    );
277
    return result.map((e) => e['category'] as String).toList();
×
278
  }
279

280
  Future<Map<String, dynamic>?> getGameById(String id) async {
×
281
    if (_isWebMode) {
×
282
      await _ensureInitialized();
×
283
      try {
284
        return _memoryStorage['games']!.firstWhere((g) => g['id'] == id);
×
285
      } catch (e) {
286
        return null;
287
      }
288
    }
289

290
    final db = await database;
×
291
    final results = await db.query('games', where: 'id = ?', whereArgs: [id]);
×
292
    return results.isNotEmpty ? results.first : null;
×
293
  }
294

295
  Future<int> updateGame(String id, Map<String, dynamic> game) async {
×
296
    if (_isWebMode) {
×
297
      await _ensureInitialized();
×
298
      final index = _memoryStorage['games']!.indexWhere((g) => g['id'] == id);
×
299
      if (index >= 0) {
×
300
        _memoryStorage['games']![index] = {
×
301
          ..._memoryStorage['games']![index],
×
302
          ...game,
×
303
        };
304
        await _saveToPrefs();
×
305
        return 1;
306
      }
307
      return 0;
308
    }
309

310
    final db = await database;
×
311
    return await db.update('games', game, where: 'id = ?', whereArgs: [id]);
×
312
  }
313

314
  Future<int> deleteGame(String id) async {
×
315
    if (_isWebMode) {
×
316
      await _ensureInitialized();
×
317
      final initialLength = _memoryStorage['games']!.length;
×
318
      _memoryStorage['games']!.removeWhere((g) => g['id'] == id);
×
319
      final removed = initialLength - _memoryStorage['games']!.length;
×
320
      await _saveToPrefs();
×
321
      return removed > 0 ? 1 : 0;
×
322
    }
323

324
    final db = await database;
×
325
    return await db.delete('games', where: 'id = ?', whereArgs: [id]);
×
326
  }
327

328
  // Wishlist CRUD - Web compatible
329
  Future<String> insertWishlistItem(Map<String, dynamic> item) async {
×
330
    if (_isWebMode) {
×
331
      await _ensureInitialized();
×
332
      _memoryStorage['wishlist']!.add(item);
×
333
      await _saveToPrefs();
×
334
      return item['id'] as String;
×
335
    }
336

337
    final db = await database;
×
338
    await db.insert('wishlist', item);
×
339
    return item['id'] as String;
×
340
  }
341

342
  Future<List<Map<String, dynamic>>> getAllWishlistItems() async {
×
343
    if (_isWebMode) {
×
344
      await _ensureInitialized();
×
345
      return List<Map<String, dynamic>>.from(_memoryStorage['wishlist']!)..sort(
×
346
        (a, b) => (b['created_at'] as int).compareTo(a['created_at'] as int),
×
347
      );
348
    }
349

350
    final db = await database;
×
351
    return await db.query('wishlist', orderBy: 'created_at DESC');
×
352
  }
353

354
  Future<int> updateWishlistItem(String id, Map<String, dynamic> item) async {
×
355
    if (_isWebMode) {
×
356
      await _ensureInitialized();
×
357
      final index = _memoryStorage['wishlist']!.indexWhere(
×
358
        (w) => w['id'] == id,
×
359
      );
360
      if (index >= 0) {
×
361
        _memoryStorage['wishlist']![index] = {
×
362
          ..._memoryStorage['wishlist']![index],
×
363
          ...item,
×
364
        };
365
        await _saveToPrefs();
×
366
        return 1;
367
      }
368
      return 0;
369
    }
370

371
    final db = await database;
×
372
    return await db.update('wishlist', item, where: 'id = ?', whereArgs: [id]);
×
373
  }
374

375
  Future<int> deleteWishlistItem(String id) async {
×
376
    if (_isWebMode) {
×
377
      await _ensureInitialized();
×
378
      final initialLength = _memoryStorage['wishlist']!.length;
×
379
      _memoryStorage['wishlist']!.removeWhere((w) => w['id'] == id);
×
380
      final removed = initialLength - _memoryStorage['wishlist']!.length;
×
381
      await _saveToPrefs();
×
382
      return removed > 0 ? 1 : 0;
×
383
    }
384

385
    final db = await database;
×
386
    return await db.delete('wishlist', where: 'id = ?', whereArgs: [id]);
×
387
  }
388

389
  // Players CRUD - Web compatible
390
  Future<String> insertPlayer(Map<String, dynamic> player) async {
×
391
    if (_isWebMode) {
×
392
      await _ensureInitialized();
×
393
      _memoryStorage['players']!.add(player);
×
394
      await _saveToPrefs();
×
395
      return player['id'] as String;
×
396
    }
397

398
    final db = await database;
×
399
    await db.insert('players', player);
×
400
    return player['id'] as String;
×
401
  }
402

403
  Future<List<Map<String, dynamic>>> getAllPlayers() async {
×
404
    if (_isWebMode) {
×
405
      await _ensureInitialized();
×
406
      return List<Map<String, dynamic>>.from(_memoryStorage['players']!)..sort(
×
407
        (a, b) => (b['created_at'] as int).compareTo(a['created_at'] as int),
×
408
      );
409
    }
410

411
    final db = await database;
×
412
    return await db.query('players', orderBy: 'created_at DESC');
×
413
  }
414

415
  Future<int> deletePlayer(String id) async {
×
416
    if (_isWebMode) {
×
417
      await _ensureInitialized();
×
418
      final initialLength = _memoryStorage['players']!.length;
×
419
      _memoryStorage['players']!.removeWhere((p) => p['id'] == id);
×
420
      final removed = initialLength - _memoryStorage['players']!.length;
×
421
      await _saveToPrefs();
×
422
      return removed > 0 ? 1 : 0;
×
423
    }
424

425
    final db = await database;
×
426
    return await db.delete('players', where: 'id = ?', whereArgs: [id]);
×
427
  }
428

429
  // Settings - handled by SettingsRepository with SharedPreferences now
430
  Future<String?> getSetting(String key) async {
×
431
    // This is now handled by SettingsRepository, keeping for compatibility
432
    return null;
433
  }
434

435
  Future<int> setSetting(String key, String value) async {
×
436
    // This is now handled by SettingsRepository, keeping for compatibility
437
    return 0;
438
  }
439

440
  // Backup/Restore - Web compatible
441
  Future<Map<String, dynamic>> exportAllData() async {
×
442
    if (_isWebMode) {
×
443
      await _ensureInitialized();
×
444
      return {
×
445
        'games': _memoryStorage['games'],
×
446
        'wishlist': _memoryStorage['wishlist'],
×
447
        'players': _memoryStorage['players'],
×
448
        'settings': [],
×
449
        'export_date': DateTime.now().toIso8601String(),
×
450
        'version': 1,
451
      };
452
    }
453

454
    final db = await database;
×
455

456
    final games = await db.query('games');
×
457
    final wishlist = await db.query('wishlist');
×
458
    final players = await db.query('players');
×
459
    final settings = await db.query('settings');
×
460

461
    return {
×
462
      'games': games,
463
      'wishlist': wishlist,
464
      'players': players,
465
      'settings': settings,
466
      'export_date': DateTime.now().toIso8601String(),
×
467
      'version': 1,
468
    };
469
  }
470

471
  Future<void> importAllData(Map<String, dynamic> data) async {
×
472
    if (_isWebMode) {
×
473
      await _ensureInitialized();
×
474
      if (data['games'] != null) {
×
475
        _memoryStorage['games'] = List<Map<String, dynamic>>.from(
×
476
          data['games'] as List,
×
477
        );
478
      }
479
      if (data['wishlist'] != null) {
×
480
        _memoryStorage['wishlist'] = List<Map<String, dynamic>>.from(
×
481
          data['wishlist'] as List,
×
482
        );
483
      }
484
      if (data['players'] != null) {
×
485
        _memoryStorage['players'] = List<Map<String, dynamic>>.from(
×
486
          data['players'] as List,
×
487
        );
488
      }
489
      await _saveToPrefs();
×
490
      return;
491
    }
492

493
    final db = await database;
×
494

495
    await db.transaction((txn) async {
×
496
      // Clear existing data
497
      await txn.delete('games');
×
498
      await txn.delete('wishlist');
×
499
      await txn.delete('players');
×
500
      await txn.delete('settings');
×
501

502
      // Insert imported data
503
      if (data['games'] != null) {
×
504
        for (final game in data['games'] as List) {
×
505
          await txn.insert('games', game as Map<String, dynamic>);
×
506
        }
507
      }
508

509
      if (data['wishlist'] != null) {
×
510
        for (final item in data['wishlist'] as List) {
×
511
          await txn.insert('wishlist', item as Map<String, dynamic>);
×
512
        }
513
      }
514

515
      if (data['players'] != null) {
×
516
        for (final player in data['players'] as List) {
×
517
          await txn.insert('players', player as Map<String, dynamic>);
×
518
        }
519
      }
520

521
      if (data['settings'] != null) {
×
522
        for (final setting in data['settings'] as List) {
×
523
          await txn.insert('settings', setting as Map<String, dynamic>);
×
524
        }
525
      }
526
    });
527
  }
528

529
  Future close() async {
×
530
    if (_isWebMode) {
×
531
      await _saveToPrefs();
×
532
      return;
533
    }
534

535
    final db = await database;
×
536
    db.close();
×
537
  }
538
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc