Skip to content

Commit

Permalink
๐Ÿ”€ Feat/#4 UI,๊ธฐ๋Šฅ ๊ตฌํ˜„ ์™„๋ฃŒ
Browse files Browse the repository at this point in the history
  • Loading branch information
mic050r authored Jan 29, 2025
2 parents bc85e93 + 47611b9 commit c6405bd
Show file tree
Hide file tree
Showing 6 changed files with 264 additions and 2 deletions.
12 changes: 12 additions & 0 deletions lib/models/song.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ class Song {

Song({required this.title, required this.singer, required this.tjNumber, required this.kyNumber, required this.img});

// JSON์—์„œ Song ๊ฐ์ฒด๋กœ ๋ณ€ํ™˜
factory Song.fromJson(Map<String, dynamic> json) {
return Song(
title: json['title'] ?? '',
Expand All @@ -17,4 +18,15 @@ class Song {
img: json['img'] ?? '',
);
}

// Song ๊ฐ์ฒด๋ฅผ JSON์œผ๋กœ ๋ณ€ํ™˜
Map<String, dynamic> toJson() {
return {
'title': title,
'singer': singer,
'tj_number': tjNumber,
'ky_number': kyNumber,
'img': img,
};
}
}
8 changes: 7 additions & 1 deletion lib/pages/song_list_page.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import 'package:tj_musical_number_book/widgets/song_category_panel.dart';
import 'package:tj_musical_number_book/services/data_service.dart';
import 'package:tj_musical_number_book/models/song.dart';
import 'package:tj_musical_number_book/theme/colors.dart';
import 'package:tj_musical_number_book/pages/today_song_page.dart';

class SongListPage extends StatefulWidget {
const SongListPage({super.key});
Expand Down Expand Up @@ -39,7 +40,12 @@ class _SongListPageState extends State<SongListPage> {
actions: [
IconButton(
icon: const Icon(Icons.menu, color: AppColors.iconColor),
onPressed: () {},
onPressed: () {
Navigator.push(
context,
MaterialPageRoute(builder: (context) => const SavedSongsPage()),
);
},
),
],
),
Expand Down
93 changes: 93 additions & 0 deletions lib/pages/today_song_page.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,93 @@
import 'package:flutter/material.dart';
import 'package:tj_musical_number_book/models/song.dart';
import 'package:tj_musical_number_book/services/song_service.dart';
import 'package:tj_musical_number_book/theme/colors.dart';
import 'package:tj_musical_number_book/widgets/song_card.dart';

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

@override
_SavedSongsPageState createState() => _SavedSongsPageState();
}

class _SavedSongsPageState extends State<SavedSongsPage> {
late List<Song> savedSongs;

@override
void initState() {
super.initState();
loadSavedSongs();
}

Future<void> loadSavedSongs() async {
savedSongs = await getSavedSongs();
setState(() {});
}

void _onReorder(int oldIndex, int newIndex) {
setState(() {
if (newIndex > oldIndex) {
newIndex -= 1; // ์ธ๋ฑ์Šค๊ฐ€ ํ•˜๋‚˜์”ฉ ๋ฐ€๋ฆฌ๋Š” ๋ฌธ์ œ ํ•ด๊ฒฐ
}
final Song movedSong = savedSongs.removeAt(oldIndex);
savedSongs.insert(newIndex, movedSong);
});
updateSongs(savedSongs); // ์ƒˆ๋กœ์šด ์ˆœ์„œ ์ €์žฅ
}

@override
Widget build(BuildContext context) {
return Scaffold(
appBar: AppBar(
title: const Text('์˜ค๋Š˜ ๋ถ€๋ฅผ ๋„˜๋ฒ„',
style: TextStyle(color: AppColors.blackMain)),
backgroundColor: AppColors.white,
),
body: FutureBuilder<List<Song>>(
future: getSavedSongs(), // ์ €์žฅ๋œ ๋…ธ๋ž˜๋“ค ๋ถˆ๋Ÿฌ์˜ค๊ธฐ
builder: (context, snapshot) {
if (snapshot.connectionState == ConnectionState.waiting) {
return const Center(child: CircularProgressIndicator());
}

if (snapshot.hasError) {
return const Center(child: Text('์—๋Ÿฌ๊ฐ€ ๋ฐœ์ƒํ–ˆ์Šต๋‹ˆ๋‹ค.'));
}

if (!snapshot.hasData || snapshot.data!.isEmpty) {
return const Center(child: Text('์ €์žฅ๋œ ๋…ธ๋ž˜๊ฐ€ ์—†์Šต๋‹ˆ๋‹ค.'));
}

savedSongs = snapshot.data!;

return ReorderableListView(
onReorder: _onReorder,
children: savedSongs
.asMap()
.map((index, song) {
return MapEntry(
index,
SongCard(
key: ValueKey(index), // ์—ฌ๊ธฐ์— key๋ฅผ ์„ค์ •
song: song,
index: index,
onDelete: () async {
await deleteSong(song);
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('"${song.title}" ๋ฒˆํ˜ธ๊ฐ€ ์‚ญ์ œ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.')),
);
loadSavedSongs();
},
),
);
})
.values
.toList(),
);
},
),
);
}
}
62 changes: 62 additions & 0 deletions lib/services/song_service.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,62 @@
import 'dart:convert';
import 'package:shared_preferences/shared_preferences.dart';
import 'package:tj_musical_number_book/models/song.dart';

// Song ๊ฐ์ฒด๋ฅผ SharedPreferences์— ์ €์žฅํ•˜๋Š” ํ•จ์ˆ˜
Future<void> saveSong(Song song) async {
final prefs = await SharedPreferences.getInstance();
List<String> savedSongsJson = prefs.getStringList('saved_songs') ?? [];

// Song ๊ฐ์ฒด๋ฅผ JSON ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜
String songJson = json.encode(song.toJson());

savedSongsJson.add(songJson);
await prefs.setStringList('saved_songs', savedSongsJson);
}

// SharedPreferences์—์„œ ์ €์žฅ๋œ Song ๊ฐ์ฒด๋ฅผ ๋ถˆ๋Ÿฌ์˜ค๋Š” ํ•จ์ˆ˜
Future<List<Song>> getSavedSongs() async {
final prefs = await SharedPreferences.getInstance();

// SharedPreferences์—์„œ ์ €์žฅ๋œ ๋…ธ๋ž˜ ๋ชฉ๋ก์„ ๊ฐ€์ ธ์˜ด
List<String>? songsJson = prefs.getStringList('saved_songs');

if (songsJson != null) {
// JSON ๋ฌธ์ž์—ด์„ Song ๊ฐ์ฒด ๋ฆฌ์ŠคํŠธ๋กœ ๋ณ€ํ™˜
return songsJson.map((songJson) {
return Song.fromJson(jsonDecode(songJson));
}).toList();
}

return [];
}

// ๋…ธ๋ž˜ ์‚ญ์ œํ•˜๊ธฐ
Future<void> deleteSong(Song song) async {
SharedPreferences prefs = await SharedPreferences.getInstance();
List<String>? songsJson = prefs.getStringList('saved_songs');

if (songsJson != null) {
// ๋…ธ๋ž˜ ๋ฆฌ์ŠคํŠธ์—์„œ ์‚ญ์ œํ•  ๋…ธ๋ž˜๋ฅผ ์ œ๊ฑฐ
songsJson.removeWhere((songData) {
Song s = Song.fromJson(json.decode(songData));
return s.tjNumber == song.tjNumber; // tjNumber๋ฅผ ๊ธฐ์ค€์œผ๋กœ ์‚ญ์ œ
});

// ์‚ญ์ œ๋œ ๋ฆฌ์ŠคํŠธ๋ฅผ ๋‹ค์‹œ ์ €์žฅ
await prefs.setStringList('saved_songs', songsJson);
}
}

// ๋…ธ๋ž˜ ์ˆœ์„œ ์—…๋ฐ์ดํŠธ
Future<void> updateSongs(List<Song> songs) async {
// SharedPreferences ์ธ์Šคํ„ด์Šค ๊ฐ€์ ธ์˜ค๊ธฐ
final prefs = await SharedPreferences.getInstance();

// Song ๊ฐ์ฒด๋ฅผ JSON ๋ฌธ์ž์—ด๋กœ ๋ณ€ํ™˜
List<String> songsJson =
songs.map((song) => jsonEncode(song.toJson())).toList();

// SharedPreferences์— ์ €์žฅ
await prefs.setStringList('saved_songs', songsJson);
}
79 changes: 79 additions & 0 deletions lib/widgets/song_card.dart
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
import 'package:flutter/material.dart';
import 'package:tj_musical_number_book/models/song.dart';
import 'package:tj_musical_number_book/theme/colors.dart';

class SongCard extends StatelessWidget {
final Song song;
final int index;
final VoidCallback onDelete;

const SongCard({
Key? key,
required this.song,
required this.index,
required this.onDelete,
}) : super(key: key);

@override
Widget build(BuildContext context) {
return Card(
key: ValueKey(index.toString()),
margin: const EdgeInsets.symmetric(vertical: 4.0, horizontal: 8.0),
child: ListTile(
leading: Row(
mainAxisSize: MainAxisSize.min,
children: [
Text(
'${index + 1}',
style: const TextStyle(
color: AppColors.primary,
fontWeight: FontWeight.bold,
fontSize: 16.0,
),
),
const SizedBox(width: 8.0),
Container(
width: 50.0,
height: 50.0,
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(8.0),
),
child: Stack(
alignment: Alignment.center,
children: [
ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: Image.asset(
'./assets/imgs/logo/${song.img.isNotEmpty ? song.img : 'default.png'}',
fit: BoxFit.cover,
),
),
ClipRRect(
borderRadius: BorderRadius.circular(8.0),
child: Container(
color: Colors.black.withOpacity(0.43),
),
),
Text(
song.tjNumber,
style: const TextStyle(
color: AppColors.white,
fontWeight: FontWeight.bold,
fontSize: 11.0,
),
),
],
),
),
],
),
title: Text(song.title, style: const TextStyle(fontWeight: FontWeight.bold)),
subtitle: Text(song.singer),
trailing: IconButton(
icon: const Icon(Icons.remove_circle_outline, color: AppColors.circleBorder),
onPressed: onDelete,
),
),
);
}
}
12 changes: 11 additions & 1 deletion lib/widgets/song_category_panel.dart
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
import 'package:flutter/material.dart';
import 'package:tj_musical_number_book/theme/colors.dart';
import '../models/song.dart';
import '../services/song_service.dart';

class SongCategoryPanel extends StatelessWidget {
final String categoryTitle;
Expand Down Expand Up @@ -74,7 +76,15 @@ class SongCategoryPanel extends StatelessWidget {
subtitle: Text(song['singer']!),
trailing: IconButton(
icon: const Icon(Icons.add_circle_outline, color: AppColors.circleBorder),
onPressed: () {
onPressed: () async {
Song songToSave = Song(
title: song['title']!,
singer: song['singer']!,
tjNumber: song['tj_number']!,
kyNumber: song['ky_number']!,
img: song['img']!,
);
await saveSong(songToSave); // Song ๊ฐ์ฒด๋ฅผ ์ €์žฅ
ScaffoldMessenger.of(context).showSnackBar(
SnackBar(
content: Text('"${song['title']}" ๋ฒˆํ˜ธ๊ฐ€ ์˜ค๋Š˜ ๋ถ€๋ฅผ ๋„˜๋ฒ„์— ์ถ”๊ฐ€๋˜์—ˆ์Šต๋‹ˆ๋‹ค.'),
Expand Down

0 comments on commit c6405bd

Please sign in to comment.