Skip to content

Commit a8b1200

Browse files
authored
Update main.dart
1 parent 6f67f71 commit a8b1200

File tree

1 file changed

+274
-48
lines changed

1 file changed

+274
-48
lines changed

source-code/lib/main.dart

Lines changed: 274 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,8 @@ import 'package:flutter/material.dart';
22
import 'package:file_picker/file_picker.dart';
33
import 'dart:io';
44
import 'package:path/path.dart' as p;
5+
import 'dart:math';
6+
import 'dart:convert';
57

68
void main() {
79
runApp(const GamedeckApp());
@@ -29,8 +31,8 @@ class GamedeckApp extends StatelessWidget {
2931
style: ElevatedButton.styleFrom(
3032
backgroundColor: const Color(0xFF007BFF),
3133
foregroundColor: Colors.white,
32-
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
33-
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
34+
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(8)),
35+
padding: const EdgeInsets.symmetric(horizontal: 20, vertical: 12),
3436
),
3537
),
3638
),
@@ -42,8 +44,33 @@ class GamedeckApp extends StatelessWidget {
4244
class Game {
4345
final String name;
4446
final String path;
47+
String? coverPath;
48+
bool isFavorite;
4549

46-
Game({required this.name, required this.path});
50+
Game({
51+
required this.name,
52+
required this.path,
53+
this.coverPath,
54+
this.isFavorite = false,
55+
});
56+
57+
factory Game.fromJson(Map<String, dynamic> json) {
58+
return Game(
59+
name: json['name'],
60+
path: json['path'],
61+
coverPath: json['coverPath'],
62+
isFavorite: json['isFavorite'] ?? false,
63+
);
64+
}
65+
66+
Map<String, dynamic> toJson() {
67+
return {
68+
'name': name,
69+
'path': path,
70+
'coverPath': coverPath,
71+
'isFavorite': isFavorite,
72+
};
73+
}
4774
}
4875

4976
class GamedeckHomePage extends StatefulWidget {
@@ -55,6 +82,49 @@ class GamedeckHomePage extends StatefulWidget {
5582

5683
class _GamedeckHomePageState extends State<GamedeckHomePage> {
5784
List<Game> games = [];
85+
String? backgroundPath;
86+
87+
@override
88+
void initState() {
89+
super.initState();
90+
_loadData();
91+
}
92+
93+
Future<void> _loadData() async {
94+
try {
95+
final gamesFile = File('games.json');
96+
if (await gamesFile.exists()) {
97+
final jsonStr = await gamesFile.readAsString();
98+
final List<dynamic> jsonList = json.decode(jsonStr)['games'];
99+
setState(() {
100+
games = jsonList.map((json) => Game.fromJson(json)).toList();
101+
});
102+
}
103+
104+
final bgFile = File('background.txt');
105+
if (await bgFile.exists()) {
106+
backgroundPath = await bgFile.readAsString();
107+
setState(() {});
108+
}
109+
} catch (e) {
110+
print('Error loading data: $e');
111+
}
112+
}
113+
114+
Future<void> _saveData() async {
115+
try {
116+
final gamesFile = File('games.json');
117+
final map = {'games': games.map((g) => g.toJson()).toList()};
118+
await gamesFile.writeAsString(json.encode(map));
119+
120+
if (backgroundPath != null) {
121+
final bgFile = File('background.txt');
122+
await bgFile.writeAsString(backgroundPath!);
123+
}
124+
} catch (e) {
125+
print('Error saving data: $e');
126+
}
127+
}
58128

59129
Future<void> _addGame() async {
60130
FilePickerResult? result = await FilePicker.platform.pickFiles(
@@ -65,9 +135,17 @@ class _GamedeckHomePageState extends State<GamedeckHomePage> {
65135
String? filePath = file.path;
66136
if (filePath != null) {
67137
String gameName = p.basenameWithoutExtension(file.name).replaceAll('_', ' ').trim();
138+
139+
// Pick cover image (optional)
140+
FilePickerResult? coverResult = await FilePicker.platform.pickFiles(
141+
type: FileType.image,
142+
);
143+
String? coverPath = coverResult?.files.first.path;
144+
68145
setState(() {
69-
games.add(Game(name: gameName, path: filePath));
146+
games.add(Game(name: gameName, path: filePath, coverPath: coverPath));
70147
});
148+
await _saveData();
71149
ScaffoldMessenger.of(context).showSnackBar(
72150
SnackBar(content: Text('Dodano grę: $gameName')),
73151
);
@@ -89,47 +167,165 @@ class _GamedeckHomePageState extends State<GamedeckHomePage> {
89167
}
90168
}
91169

92-
@override
93-
Widget build(BuildContext context) {
94-
return Scaffold(
95-
appBar: AppBar(
96-
title: const Text('GAMEDECK PC', style: TextStyle(fontSize: 24)),
97-
centerTitle: false,
98-
actions: [
99-
Padding(
100-
padding: const EdgeInsets.only(right: 16.0),
101-
child: ElevatedButton.icon(
102-
onPressed: _addGame,
103-
icon: const Icon(Icons.add),
104-
label: const Text('Dodaj Grę'),
170+
Future<void> _launchRandom() async {
171+
if (games.isEmpty) {
172+
ScaffoldMessenger.of(context).showSnackBar(
173+
const SnackBar(content: Text('Brak gier do losowego uruchomienia')),
174+
);
175+
return;
176+
}
177+
final randomIndex = Random().nextInt(games.length);
178+
_launchGame(games[randomIndex].path);
179+
}
180+
181+
void _showOptions() {
182+
showDialog(
183+
context: context,
184+
builder: (context) => AlertDialog(
185+
title: const Text('Opcje'),
186+
content: Column(
187+
mainAxisSize: MainAxisSize.min,
188+
children: [
189+
ElevatedButton(
190+
onPressed: () async {
191+
Navigator.pop(context);
192+
FilePickerResult? res = await FilePicker.platform.pickFiles(
193+
type: FileType.image,
194+
);
195+
if (res != null) {
196+
setState(() {
197+
backgroundPath = res.files.first.path;
198+
});
199+
await _saveData();
200+
}
201+
},
202+
child: const Text('Zmień tapetę'),
105203
),
204+
],
205+
),
206+
actions: [
207+
TextButton(
208+
onPressed: () => Navigator.pop(context),
209+
child: const Text('Zamknij'),
106210
),
107211
],
108212
),
109-
body: games.isEmpty
110-
? const Center(
213+
);
214+
}
215+
216+
Widget _buildGameList(List<Game> gameList) {
217+
if (gameList.isEmpty) {
218+
return const Center(
111219
child: Text(
112-
'Brak gier w bibliotece.\nDodaj swoją pierwszą grę!',
113-
textAlign: TextAlign.center,
220+
'Brak gier w tej sekcji.',
114221
style: TextStyle(fontSize: 18, color: Colors.white70),
115222
),
116-
)
117-
: GridView.builder(
118-
padding: const EdgeInsets.all(20),
119-
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
120-
maxCrossAxisExtent: 300,
121-
childAspectRatio: 0.75,
122-
crossAxisSpacing: 20,
123-
mainAxisSpacing: 20,
223+
);
224+
}
225+
return GridView.builder(
226+
padding: const EdgeInsets.all(20),
227+
gridDelegate: const SliverGridDelegateWithMaxCrossAxisExtent(
228+
maxCrossAxisExtent: 300,
229+
childAspectRatio: 0.75,
230+
crossAxisSpacing: 20,
231+
mainAxisSpacing: 20,
232+
),
233+
itemCount: gameList.length,
234+
itemBuilder: (context, index) {
235+
final game = gameList[index];
236+
return GameCard(
237+
game: game,
238+
onPlay: () => _launchGame(game.path),
239+
onToggleFavorite: () {
240+
setState(() {
241+
game.isFavorite = !game.isFavorite;
242+
});
243+
_saveData();
244+
},
245+
);
246+
},
247+
);
248+
}
249+
250+
@override
251+
Widget build(BuildContext context) {
252+
return DefaultTabController(
253+
length: 2,
254+
child: Scaffold(
255+
appBar: AppBar(
256+
title: const Text('GAMEDECK PC', style: TextStyle(fontSize: 24)),
257+
centerTitle: false,
258+
actions: [
259+
Padding(
260+
padding: const EdgeInsets.only(right: 16.0),
261+
child: ElevatedButton.icon(
262+
onPressed: _addGame,
263+
icon: const Icon(Icons.add),
264+
label: const Text('Dodaj Grę'),
265+
),
266+
),
267+
],
268+
bottom: const TabBar(
269+
tabs: [
270+
Tab(text: 'Wszystkie'),
271+
Tab(text: 'Ulubione'),
272+
],
273+
),
274+
),
275+
body: Container(
276+
decoration: BoxDecoration(
277+
gradient: backgroundPath == null
278+
? const LinearGradient(
279+
begin: Alignment.topCenter,
280+
end: Alignment.bottomCenter,
281+
colors: [Color(0xFF1A1A1A), Color(0xFF001A4D)],
282+
)
283+
: null,
284+
image: backgroundPath != null
285+
? DecorationImage(
286+
image: FileImage(File(backgroundPath!)),
287+
fit: BoxFit.cover,
288+
)
289+
: null,
290+
),
291+
child: games.isEmpty
292+
? const Center(
293+
child: Text(
294+
'Brak gier w bibliotece.\nDodaj swoją pierwszą grę!',
295+
textAlign: TextAlign.center,
296+
style: TextStyle(fontSize: 18, color: Colors.white70),
297+
),
298+
)
299+
: TabBarView(
300+
children: [
301+
_buildGameList(games),
302+
_buildGameList(games.where((g) => g.isFavorite).toList()),
303+
],
304+
),
305+
),
306+
bottomNavigationBar: BottomAppBar(
307+
color: const Color(0xFF121212),
308+
child: Row(
309+
mainAxisAlignment: MainAxisAlignment.spaceEvenly,
310+
children: [
311+
ElevatedButton.icon(
312+
onPressed: () {},
313+
icon: const Icon(Icons.arrow_forward),
314+
label: const Text('Nawiguj'),
315+
),
316+
ElevatedButton.icon(
317+
onPressed: _showOptions,
318+
icon: const Icon(Icons.settings),
319+
label: const Text('Opcje'),
320+
),
321+
ElevatedButton.icon(
322+
onPressed: _launchRandom,
323+
icon: const Icon(Icons.shuffle),
324+
label: const Text('Losowa Gra'),
325+
),
326+
],
327+
),
124328
),
125-
itemCount: games.length,
126-
itemBuilder: (context, index) {
127-
final game = games[index];
128-
return GameCard(
129-
game: game,
130-
onPlay: () => _launchGame(game.path),
131-
);
132-
},
133329
),
134330
);
135331
}
@@ -138,11 +334,20 @@ class _GamedeckHomePageState extends State<GamedeckHomePage> {
138334
class GameCard extends StatelessWidget {
139335
final Game game;
140336
final VoidCallback onPlay;
337+
final VoidCallback onToggleFavorite;
141338

142-
const GameCard({super.key, required this.game, required this.onPlay});
339+
const GameCard({
340+
super.key,
341+
required this.game,
342+
required this.onPlay,
343+
required this.onToggleFavorite,
344+
});
143345

144346
@override
145347
Widget build(BuildContext context) {
348+
final placeholderUrl =
349+
'https://via.placeholder.com/200x260/2A2A2A/FFFFFF?text=${game.name.substring(0, 1)}';
350+
146351
return Card(
147352
elevation: 8,
148353
shape: RoundedRectangleBorder(borderRadius: BorderRadius.circular(16)),
@@ -153,23 +358,44 @@ class GameCard extends StatelessWidget {
153358
crossAxisAlignment: CrossAxisAlignment.stretch,
154359
children: [
155360
Expanded(
156-
child: Image.network(
157-
'https://via.placeholder.com/200x260/2A2A2A/FFFFFF?text=${game.name.substring(0, 1)}',
158-
fit: BoxFit.cover,
159-
),
361+
child: game.coverPath != null
362+
? Image.file(
363+
File(game.coverPath!),
364+
fit: BoxFit.cover,
365+
errorBuilder: (context, error, stackTrace) => Image.network(
366+
placeholderUrl,
367+
fit: BoxFit.cover,
368+
),
369+
)
370+
: Image.network(
371+
placeholderUrl,
372+
fit: BoxFit.cover,
373+
),
160374
),
161375
Padding(
162376
padding: const EdgeInsets.all(12.0),
163377
child: Column(
164378
crossAxisAlignment: CrossAxisAlignment.start,
165379
children: [
166-
Text(
167-
game.name,
168-
style: Theme.of(context).textTheme.titleLarge?.copyWith(fontSize: 18),
169-
maxLines: 1,
170-
overflow: TextOverflow.ellipsis,
380+
Row(
381+
children: [
382+
Expanded(
383+
child: Text(
384+
game.name,
385+
style: Theme.of(context).textTheme.titleLarge?.copyWith(fontSize: 18),
386+
maxLines: 1,
387+
overflow: TextOverflow.ellipsis,
388+
),
389+
),
390+
IconButton(
391+
icon: Icon(
392+
game.isFavorite ? Icons.favorite : Icons.favorite_border,
393+
color: game.isFavorite ? Colors.red : null,
394+
),
395+
onPressed: onToggleFavorite,
396+
),
397+
],
171398
),
172-
const SizedBox(height: 8),
173399
ElevatedButton.icon(
174400
onPressed: onPlay,
175401
icon: const Icon(Icons.play_arrow),

0 commit comments

Comments
 (0)