Flutter 로고 퀴즈 게임
AndroidManifest.xml
인터넷 허가. 태그 위.
<!-- Required to fetch data from the internet. -->
<uses-permission android:name="android.permission.INTERNET" />
pubspec.yaml
http: ^0.13.4
flutter_dotenv: ^5.0.2
flutter_svg: ^1.0.3
shared_preferences: ^2.0.13
프로젝트 구조
메인 다트
import 'package:flutter/material.dart';
import 'pages/home/home.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
Future<void> main() async {
await dotenv.load(fileName: "assets/.env");
runApp(const MyApp());
}
class MyApp extends StatelessWidget {
const MyApp({Key? key}) : super(key: key);
@override
Widget build(BuildContext context) {
return MaterialApp(
debugShowCheckedModeBanner: false,
title: 'Flutter Logo Quiz',
theme: ThemeData(
primarySwatch: Colors.blue,
),
home: const HomePage(title: 'Flutter Logo Quiz'),
);
}
}
helpers.dart
String timeFormat(int second) {
final dur = Duration(seconds: second);
return dur.inMinutes.remainder(60).toString().padLeft(2, '0') +
':' +
dur.inSeconds.remainder(60).toString().padLeft(2, '0');
}
상수.다트
const brandDomainList = [
'netflix.com',
'airbnb.com',
'github.com',
'reddit.com',
'slack.com',
'wordpress.com',
'pepsi.com',
'starbucks.com',
'pinterest.com',
'walmart.com',
'toyota.com',
'subway.com',
'amazon.com',
'dominos.com',
'playstation.com',
'spotify.com',
'youtube.com',
'linkedin.com',
'paypal.com',
'stackoverflow.com',
];
/models/logo_format.dart
class LogoFormat {
final String src;
final String format;
const LogoFormat({required this.src, required this.format});
factory LogoFormat.fromJson(Map<String, dynamic> json) {
return LogoFormat(
src: json['src'] as String, format: json['format'] as String);
}
}
/models/logo.dart
import 'logo_format.dart';
class Logo {
final String type;
final List<LogoFormat> formats;
const Logo({required this.type, required this.formats});
factory Logo.fromJson(Map<String, dynamic> json) {
return Logo(
type: json['type'] as String,
formats: json['formats']
.map<LogoFormat>((f) => LogoFormat.fromJson(f))
.toList());
}
}
/models/brand.dart
import 'logo.dart';
class Brand {
final String name;
final String description;
final List<Logo> logos;
const Brand(
{required this.name, required this.description, required this.logos});
factory Brand.fromJson(Map<String, dynamic> json) {
return Brand(
name: json['name'] as String,
description: json['description'] as String,
logos: json['logos'].map<Logo>((l) => Logo.fromJson(l)).toList());
}
}
/models/game_brand.dart
class GameBrand {
final String name;
final String description;
final String icon;
final String logo;
const GameBrand(
{required this.name,
required this.description,
required this.icon,
required this.logo});
}
/서비스/logo_api.dart
import 'dart:convert';
import 'package:http/http.dart' as http;
import '../models/brand.dart';
import 'package:flutter_dotenv/flutter_dotenv.dart';
import '../models/game_brand.dart';
class LogoApi {
static Future<GameBrand> getBrand(String brandDomain) async {
// Get api key
final brandtoken = dotenv.env['brandtoken'];
final response = await http.get(
Uri.parse('https://api.brandfetch.io/v2/brands/$brandDomain'),
headers: {'Authorization': 'Bearer $brandtoken'});
if (response.statusCode == 200) {
final brand = Brand.fromJson(jsonDecode(response.body));
final gameBrand = GameBrand(
name: brand.name,
description: brand.description,
icon: brand.logos.firstWhere((l) => l.type == 'icon').formats[0].src,
logo: brand.logos
.firstWhere((l) => l.type == 'logo')
.formats
.firstWhere((f) => f.format == 'svg')
.src);
return gameBrand;
} else {
throw Exception('Failed to fetch brand');
}
}
}
/서비스/shared_prefs.dart
import 'package:shared_preferences/shared_preferences.dart';
class SharedPrefs {
static Future<SharedPreferences> _prefs() async {
return await SharedPreferences.getInstance();
}
static Future<int> getTotalScore() async {
final sharedPreferences = await _prefs();
return sharedPreferences.getInt('totalscore') ?? 0;
}
static Future<void> setTotalScore(int tscore) async {
final sharedPreferences = await _prefs();
await sharedPreferences.setInt('totalscore', tscore);
}
static Future<void> clearTotalScore() async {
final sharedPreferences = await _prefs();
await sharedPreferences.setInt('totalscore', 0);
}
}
/페이지/홈/home.dart
import 'package:flutter/material.dart';
import 'package:logo_quiz/constants.dart';
import 'package:logo_quiz/pages/game/game.dart';
import 'package:logo_quiz/services/logo_api.dart';
import '../../models/game_brand.dart';
class HomePage extends StatefulWidget {
const HomePage({Key? key, required this.title}) : super(key: key);
final String title;
@override
State<HomePage> createState() => _HomePageState();
}
class _HomePageState extends State<HomePage> {
final List<GameBrand> _brands = <GameBrand>[];
// for show loading indicator
bool _fetchingBrands = false;
// for show error
bool _fetchingBrandsError = false;
/*
** Get brands from api and turn them GameBrand
** object and store them _brands list
*/
Future<void> _initBrands() async {
setState(() {
_fetchingBrands = true;
});
for (var b in brandDomainList) {
try {
final brand = await LogoApi.getBrand(b);
_brands.add(brand);
} catch (ex) {
setState(() {
_fetchingBrandsError = true;
_fetchingBrands = false;
});
return;
}
}
setState(() {
_fetchingBrandsError = false;
_fetchingBrands = false;
});
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: Center(
child: Column(
mainAxisAlignment: MainAxisAlignment.center,
children: [
const Text(
'Flutter Logo Quiz',
style: TextStyle(
fontWeight: FontWeight.bold,
fontSize: 36,
),
),
const SizedBox(height: 25),
ElevatedButton(
child: _fetchingBrands
? const Text('Loading...')
: _fetchingBrandsError
? const Text('Error')
: const Text('Start'),
onPressed: () async {
// Send brands list to game page
if (!_fetchingBrands) {
await _initBrands();
if (!_fetchingBrandsError) {
Navigator.push(context,
MaterialPageRoute(builder: (context) {
return Game(brands: _brands);
}));
}
}
},
),
],
),
),
);
}
}
/페이지/게임/game.dart
import 'package:flutter/material.dart';
import '../../models/game_brand.dart';
import '../../services/shared_prefs.dart';
import 'components/game_level.dart';
class Game extends StatefulWidget {
final List<GameBrand> brands;
const Game({Key? key, required this.brands}) : super(key: key);
@override
State<Game> createState() => _GameState();
}
class _GameState extends State<Game> {
int _currentLevel = 0;
@override
void initState() {
super.initState();
SharedPrefs.clearTotalScore();
}
@override
Widget build(BuildContext context) {
return Scaffold(
body: _buildLevels(),
);
}
/*
** Builds game pages and send them
** brand, level number and current level number
** For stop timer in level page game controls
** the page number equal to current page number
*/
Widget _buildLevels() {
return SafeArea(
child: PageView.builder(
onPageChanged: (pageIndex) {
setState(() {
_currentLevel = pageIndex;
});
},
itemCount: widget.brands.length,
itemBuilder: (BuildContext context, int index) {
return GameLevel(
gameBrand: widget.brands[index],
levelNumber: index,
currentLevel: _currentLevel);
},
),
);
}
}
/game/components/game_level.dart
import 'dart:async';
import 'package:flutter/material.dart';
import 'package:logo_quiz/helpers.dart';
import 'package:logo_quiz/models/game_brand.dart';
import 'dart:math' as math;
import 'package:flutter_svg/flutter_svg.dart';
import 'package:logo_quiz/services/shared_prefs.dart';
class GameLevel extends StatefulWidget {
final GameBrand gameBrand;
final int levelNumber;
final int currentLevel;
const GameLevel(
{Key? key,
required this.gameBrand,
required this.levelNumber,
required this.currentLevel})
: super(key: key);
@override
State<GameLevel> createState() => _GameLevelState();
}
class _GameLevelState extends State<GameLevel>
with AutomaticKeepAliveClientMixin {
final List<String> _selectedLetters = [];
final ValueNotifier<int> _seconds = ValueNotifier<int>(300);
final ValueNotifier<int> _score = ValueNotifier<int>(0);
late final Timer _timer;
bool _levelComplete = false;
bool? _success;
bool _isLevelStarted = false;
int _letterOrder = 0;
int _totalScore = 0;
double _iconFlipValue = 0;
double _descriptionOpacity = 1.0;
String _iconOrLogo = 'icon';
List<String> _shuffledBrandNameCharList = [];
@override
bool get wantKeepAlive => true;
@override
void dispose() {
_timer.cancel();
super.dispose();
}
@override
void initState() {
// Shuffle brand name characters for game buttons
_shuffledBrandNameCharList = widget.gameBrand.name.characters.toList();
_shuffledBrandNameCharList.shuffle();
for (int i = 0; i < _shuffledBrandNameCharList.length; i++) {
_selectedLetters.add('_');
}
_timer = Timer.periodic(
const Duration(milliseconds: 1000),
(timer) {
if (_isLevelStarted &&
widget.currentLevel == widget.levelNumber &&
!_levelComplete) {
_seconds.value--;
if (_seconds.value <= 0) {
setState(() {
_score.value = 0;
_levelComplete = true;
_success = false;
_iconFlipValue = 1;
_descriptionOpacity = 0.0;
_printTotalScore();
});
}
}
},
);
super.initState();
}
@override
Widget build(BuildContext context) {
super.build(context);
return Column(
children: [
const Spacer(),
// Level number, complete indicator, time
Row(
mainAxisAlignment: MainAxisAlignment.spaceAround,
children: [
_levelNumber(),
_levelCompleteIndicator(),
_levelScore(),
if (_levelComplete) _levelTotalScore(),
_levelTime(),
],
),
const Spacer(),
// Icon
_levelIcon(),
const Spacer(),
// Brand letters or description
AnimatedOpacity(
onEnd: () {
setState(() {
_descriptionOpacity = 1.0;
});
},
duration: const Duration(milliseconds: 500),
opacity: _descriptionOpacity,
child: _brandNameOrDescription()),
const Spacer(),
// Brand name buttons
if (!_levelComplete) _brandNameButtons(),
const Spacer(),
],
);
}
Widget _levelCompleteIndicator() {
return Icon(Icons.verified,
color: _levelComplete && _success == false
? Colors.red
: _levelComplete && _success == true
? Colors.green
: Colors.grey,
size: 60);
}
Widget _levelNumber() {
return Container(
padding: const EdgeInsets.all(16.0),
decoration: BoxDecoration(
shape: BoxShape.circle,
border: Border.all(color: Colors.black12, width: 3),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(.3),
blurRadius: 10,
offset: const Offset(0, 0))
],
gradient: const LinearGradient(
colors: [Colors.blue, Color.fromARGB(255, 8, 114, 201)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter),
),
child: Text('${widget.levelNumber + 1}',
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white)));
}
Widget _levelTime() {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.black12, width: 3),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(.3),
blurRadius: 10,
offset: const Offset(0, 0))
],
gradient: const LinearGradient(
colors: [Colors.blue, Color.fromARGB(255, 8, 114, 201)],
begin: Alignment.topCenter,
end: Alignment.bottomCenter),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ValueListenableBuilder(
valueListenable: _seconds,
builder: (context, value, child) {
return Text(timeFormat(_seconds.value),
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white));
}),
),
);
}
Widget _levelScore() {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.black12, width: 3),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(.3),
blurRadius: 10,
offset: const Offset(0, 0))
],
gradient: const LinearGradient(
colors: [Colors.orange, Colors.deepOrange],
begin: Alignment.topCenter,
end: Alignment.bottomCenter),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ValueListenableBuilder(
valueListenable: _score,
builder: (context, value, child) {
return Text(_score.value.toString(),
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white));
}),
),
);
}
Widget _levelTotalScore() {
return Container(
decoration: BoxDecoration(
borderRadius: BorderRadius.circular(10),
border: Border.all(color: Colors.black12, width: 3),
boxShadow: [
BoxShadow(
color: Colors.black.withOpacity(.3),
blurRadius: 10,
offset: const Offset(0, 0))
],
gradient: const LinearGradient(
colors: [Colors.red, Colors.brown],
begin: Alignment.topCenter,
end: Alignment.bottomCenter),
),
child: Padding(
padding: const EdgeInsets.all(8.0),
child: ValueListenableBuilder(
valueListenable: _score,
builder: (context, value, child) {
return Text(_totalScore.toString(),
style: const TextStyle(
fontSize: 20,
fontWeight: FontWeight.bold,
color: Colors.white));
}),
),
);
}
Widget _levelIcon() {
return AnimatedContainer(
onEnd: () {
setState(() {
_iconOrLogo = 'logo';
_iconFlipValue = 2;
});
},
duration: const Duration(milliseconds: 1000),
transform: Matrix4.rotationY(_iconFlipValue * math.pi),
transformAlignment: Alignment.center,
child: Stack(
alignment: Alignment.center,
children: [
Container(color: Colors.black12, width: 175, height: 175),
Container(color: Colors.white, width: 150, height: 150),
SizedBox(
width: 125,
height: 125,
child: _iconOrLogo == 'icon'
? Image.network(
widget.gameBrand.icon,
fit: BoxFit.contain,
cacheWidth: 100,
cacheHeight: 100,
loadingBuilder: (BuildContext context, Widget child,
ImageChunkEvent? loadingProgress) {
if (loadingProgress == null) {
return child;
}
return Center(
child: CircularProgressIndicator(
value: loadingProgress.expectedTotalBytes != null
? loadingProgress.cumulativeBytesLoaded /
loadingProgress.expectedTotalBytes!
: null,
),
);
},
errorBuilder: (context, error, stackTrace) =>
const Icon(Icons.error),
)
: SvgPicture.network(
widget.gameBrand.logo,
fit: BoxFit.contain,
placeholderBuilder: (context) {
return const CircularProgressIndicator();
},
),
),
],
),
);
}
Widget _brandNameOrDescription() {
return _levelComplete
? Padding(
padding: const EdgeInsets.all(16.0),
child: Column(children: [
Text(
widget.gameBrand.name,
textAlign: TextAlign.center,
style:
const TextStyle(fontWeight: FontWeight.bold, fontSize: 28),
),
const SizedBox(
height: 5,
),
Text(
widget.gameBrand.description,
textAlign: TextAlign.center,
style: const TextStyle(fontSize: 18),
),
]),
)
: Padding(
padding: const EdgeInsets.all(8),
child: Wrap(
alignment: WrapAlignment.center,
spacing: 5,
runSpacing: 5,
children: _selectedLetters
.asMap()
.entries
.map<Widget>((e) => ElevatedButton(
onPressed: () {},
child: Text(e.value),
style: ElevatedButton.styleFrom(
primary: _levelComplete
? Colors.green
: _letterOrder == e.key
? Colors.red
: Colors.blue)))
.toList(),
),
);
}
Widget _brandNameButtons() {
List<Widget> wrapChildren = _shuffledBrandNameCharList
.map<Widget>((c) => ElevatedButton(
style: ElevatedButton.styleFrom(
shape: RoundedRectangleBorder(
borderRadius: BorderRadius.circular(30),
),
primary: Colors.blueGrey,
textStyle: const TextStyle(fontSize: 18)),
onPressed: () {
setState(() {
if (!_isLevelStarted) {
_isLevelStarted = true;
}
if (widget.gameBrand.name[_letterOrder] == c) {
_selectedLetters[_letterOrder] = c;
_letterOrder++;
_score.value += 10;
if (_letterOrder == widget.gameBrand.name.length) {
_levelComplete = true;
_success = true;
_iconFlipValue = 1;
_descriptionOpacity = 0.0;
_printTotalScore();
}
} else {
_score.value -= 1;
}
});
},
child: Text(c)))
.toList();
return Padding(
padding: const EdgeInsets.all(8.0),
child: Wrap(
alignment: WrapAlignment.center,
spacing: 5,
runSpacing: 5,
children: wrapChildren),
);
}
Future<void> _printTotalScore() async {
int tscore = await SharedPrefs.getTotalScore();
tscore += _score.value;
await SharedPrefs.setTotalScore(tscore);
setState(() {
_totalScore = tscore;
});
}
}
Reference
이 문제에 관하여(Flutter 로고 퀴즈 게임), 우리는 이곳에서 더 많은 자료를 발견하고 링크를 클릭하여 보았다 https://dev.to/canerdemirci/flutter-logo-quiz-game-265b텍스트를 자유롭게 공유하거나 복사할 수 있습니다.하지만 이 문서의 URL은 참조 URL로 남겨 두십시오.
우수한 개발자 콘텐츠 발견에 전념 (Collection and Share based on the CC Protocol.)