name27 / flutter

0 stars 0 forks source link

20개 quote 데이터 받아와서 tag 검색 앱 #98

Open name27 opened 1 year ago

name27 commented 1 year ago

image image image

image image image

main.dart

import 'package:flutter/material.dart';
import 'package:my_app/page/main_page.dart';

void main() {
  runApp(const MyApp());
}

class MyApp extends StatelessWidget {
  const MyApp({super.key});

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      theme: ThemeData.dark(),
      debugShowCheckedModeBanner: false,
      home: MainPage(),
    );
  }
}

main_page.dart

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:my_app/model/quote_data.dart';
import 'package:my_app/page/search_page.dart';
import 'package:my_app/widget/quoteCard.dart';

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

  @override
  State<MainPage> createState() => _MainPageState();
}

const int dataLength = 20;

class _MainPageState extends State<MainPage> {
  Dio dio = Dio();
  var url = 'https://favqs.com/api/qotd';
  List<QuoteData> quoteData = [];
  ValueNotifier<int> quoutelength = ValueNotifier<int>(0);
  Future<List<QuoteData>> readQuote() async {
    quoutelength.value = 0;
    quoteData.clear();
    for (int i = 0; i < dataLength; i++) {
      var res = await dio.get(url);
      if (res.statusCode == 200) {
        quoteData.add(QuoteData.fromMap(res.data));
      }
    }
    quoutelength.value = quoteData.length;
    return quoteData;
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      decoration: BoxDecoration(
          image: DecorationImage(
              image: NetworkImage(
                  'https://fastly.picsum.photos/id/197/400/600.jpg?hmac=mGEc8MdTwY4Ny3KkvCDqtNTF2SpHBxD8PwIGDm28BNY'),
              fit: BoxFit.cover,
              opacity: 0.5)),
      child: Scaffold(
          backgroundColor: Colors.transparent,
          extendBodyBehindAppBar: true,
          appBar: AppBar(
            backgroundColor: Colors.transparent,
            elevation: 0,
            actions: [
              ValueListenableBuilder(
                  valueListenable: quoutelength,
                  builder: (context, value, child) =>
                      quoutelength.value == dataLength
                          ? IconButton(
                              onPressed: () {
                                Navigator.push(
                                    context,
                                    MaterialPageRoute(
                                      builder: (context) =>
                                          SearchPage(quoteData: quoteData),
                                    ));
                              },
                              icon: FaIcon(FontAwesomeIcons.search))
                          : SizedBox())
            ],
          ),
          body: Center(
              child: FutureBuilder(
            future: readQuote(),
            builder: (context, snapshot) {
              if (snapshot.connectionState == ConnectionState.done) {
                return PageView.builder(
                    itemCount: dataLength,
                    itemBuilder: (context, index) =>
                        QuoteCard(quoteData: quoteData[index]));
              }
              return CircularProgressIndicator();
            },
          )),
          floatingActionButton: ValueListenableBuilder(
              valueListenable: quoutelength,
              builder: (context, value, child) =>
                  quoutelength.value == dataLength
                      ? FloatingActionButton(
                          onPressed: () {
                            setState(() {});
                          },
                          backgroundColor: Colors.black45,
                          child: Icon(
                            Icons.refresh,
                            color: Colors.white,
                          ),
                        )
                      : SizedBox())),
    );
  }
}

search_page.dart

import 'package:flutter/material.dart';
import 'package:font_awesome_flutter/font_awesome_flutter.dart';
import 'package:my_app/model/quote_data.dart';
import 'package:my_app/page/tag_page.dart';

class SearchPage extends StatefulWidget {
  const SearchPage({super.key, required this.quoteData});
  final List<QuoteData> quoteData;

  @override
  State<SearchPage> createState() => _SearchPageState();
}

var _inputController = TextEditingController();
List<String> searchList = [];
List<QuoteData> tagList = [];
bool _isSearch = false;

class _SearchPageState extends State<SearchPage> {
  @override
  void initState() {
    super.initState();
    _inputController.text = "";
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        backgroundColor: Colors.transparent,
        elevation: 0,
        title: TextField(
          onSubmitted: (value) {
            searchList.insert(0, value);

            searchList = searchList.toSet().toList();
            tagList.clear();
            for (var data in widget.quoteData) {
              for (var tag in data.quote.tags) {
                if (tag == value) {
                  tagList.add(data);
                }
              }
            }

            _isSearch = true;
            if (_isSearch) {
              Navigator.push(
                  context,
                  MaterialPageRoute(
                      builder: (context) => TagPage(
                            quoteData: tagList,
                            tag: value,
                          )));
            }

            setState(() {});
          },
          controller: _inputController,
          decoration: InputDecoration(
            hintText: 'Tag Search..',
            enabledBorder: OutlineInputBorder(
              borderSide: BorderSide(
                color: Colors.transparent,
                width: 0.3,
              ),
            ),
          ),
        ),
        actions: [
          IconButton(
              onPressed: () {
                _inputController.text = "";
                setState(() {});
              },
              icon: FaIcon(FontAwesomeIcons.close)),
          IconButton(
              onPressed: () {
                tagList.clear();
                searchList.insert(0, _inputController.text);
                searchList = searchList.toSet().toList();
                for (var data in widget.quoteData) {
                  for (var tag in data.quote.tags) {
                    if (tag == _inputController.text) {
                      tagList.add(data);
                    }
                  }
                }

                _isSearch = true;
                if (_isSearch) {
                  Navigator.push(
                      context,
                      MaterialPageRoute(
                          builder: (context) => TagPage(
                                quoteData: tagList,
                                tag: _inputController.text,
                              )));
                }
                setState(() {});
              },
              icon: FaIcon(FontAwesomeIcons.search)),
        ],
      ),
      body: searchList.isEmpty
          ? SizedBox()
          : Padding(
              padding: const EdgeInsets.all(8.0),
              child: Wrap(
                runSpacing: 3,
                spacing: 5,
                children: searchList
                    .map((e) => Chip(
                          label: GestureDetector(
                              onTap: () {
                                _inputController.text = e;
                              },
                              onDoubleTap: () {
                                tagList.clear();
                                for (var data in widget.quoteData) {
                                  for (var tag in data.quote.tags) {
                                    if (tag == e) {
                                      tagList.add(data);
                                    }
                                  }
                                }
                                _isSearch = true;
                                if (_isSearch) {
                                  Navigator.push(
                                      context,
                                      MaterialPageRoute(
                                          builder: (context) => TagPage(
                                                quoteData: tagList,
                                                tag: e,
                                              )));
                                }
                              },
                              child: Text(
                                e,
                                style: TextStyle(
                                    color: Colors.white, fontSize: 16),
                              )),
                          onDeleted: () {
                            searchList.remove(e);
                            setState(() {});
                          },
                        ))
                    .toList(),
              ),
            ),
    );
  }
}

tag_page.dart

import 'package:flutter/material.dart';
import 'package:my_app/model/quote_data.dart';
import 'package:my_app/widget/quoteCard.dart';

class TagPage extends StatelessWidget {
  const TagPage({
    super.key,
    required this.tag,
    required this.quoteData,
  });
  final List<QuoteData> quoteData;
  final String tag;

  @override
  Widget build(BuildContext context) {
    return Container(
        decoration: BoxDecoration(
            image: DecorationImage(
                image: NetworkImage(
                    'https://fastly.picsum.photos/id/338/400/600.jpg?hmac=jBPWPvkVzsfyawbDduGKmTO_lR0mq_j3R-2KngcnnPI'),
                fit: BoxFit.cover,
                opacity: 0.5)),
        child: Scaffold(
            extendBodyBehindAppBar: true,
            backgroundColor: Colors.transparent,
            appBar: AppBar(
              title: Text(tag),
              backgroundColor: Colors.transparent,
              elevation: 0,
              centerTitle: true,
            ),
            body: PageView.builder(
              itemCount: quoteData.length == 0 ? 1 : quoteData.length,
              itemBuilder: (context, index) => quoteData.isEmpty
                  ? Center(
                      child: Text('검색 결과가 없습니다'),
                    )
                  : QuoteCard(quoteData: quoteData[index]),
            ))
        );
  }
}

quoteCard.dart

import 'package:animate_do/animate_do.dart';
import 'package:flutter/material.dart';
import 'package:flutter/widgets.dart';
import 'package:my_app/model/quote_data.dart';

class QuoteCard extends StatelessWidget {
  const QuoteCard({super.key, required this.quoteData});
  final QuoteData quoteData;

  @override
  Widget build(BuildContext context) {
    return FadeIn(
      duration: Duration(milliseconds: 800),
      child: Center(
        child: Padding(
          padding: const EdgeInsets.all(12.0),
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            crossAxisAlignment: CrossAxisAlignment.center,
            children: [
              Text(
                quoteData.quote.body,
                textAlign: TextAlign.center,
                style: TextStyle(
                  fontWeight: FontWeight.w600,
                  fontSize: 21,
                  wordSpacing: 1.5,
                ),
              ),
              SizedBox(
                height: 8,
              ),
              Text(quoteData.quote.author),
              SizedBox(
                height: 12,
              ),
              Wrap(
                alignment: WrapAlignment.center,
                spacing: 5,
                runSpacing: 3,
                children: [
                  if (quoteData.quote.tags != null)
                    for (var tags in quoteData.quote.tags)
                      Chip(
                        label: Text('# ${tags}'),
                        backgroundColor: Colors.black26,
                      )
                ],
              )
            ],
          ),
        ),
      ),
    );
  }
}

quote_data.dart

import 'package:my_app/model/quote.dart';

class QuoteData {
  String? qotdDate;
  Quote quote;

  QuoteData({required this.qotdDate, required this.quote});

  factory QuoteData.fromMap(Map<String, dynamic> map){
    return QuoteData(qotdDate: map['qotdDate'], quote: Quote.fromMap(map['quote']));
  }
}

qoute.dart

class Quote {
  int id;
  bool dialogue;
  String? source;
  String? context;
  bool private;
  List<String> tags;
  String url;
  int? favoritesCount;
  int? upvotesCount;
  int? downvotesCount;
  String author;
  String? authorPermalink;
  String body;

  Quote(
      {required this.id,
      required this.dialogue,
      required this.source,
      required this.context,
      required this.private,
      required this.tags,
      required this.url,
      required this.favoritesCount,
      required this.upvotesCount,
      required this.downvotesCount,
      required this.author,
      required this.authorPermalink,
      required this.body});

  factory Quote.fromMap(Map<String, dynamic> map){
    return Quote(
    id: map['id'],
    dialogue: map['dialogue'],
    source: map['source'],
    context: map['context'],
    private: map['private'],
    tags: List<String>.from(map['tags']),
    url: map['url'],
    favoritesCount: map['favoritesCount'],
    upvotesCount: map['upvotesCount'],
    downvotesCount: map['downvotesCount'],
    author: map['author'], 
    authorPermalink: map['authorPermalink'],
    body: map['body']);
  }
}
name27 commented 1 year ago

헤맸던 내용

태그 페이지에서 페이지뷰 itemCount를 검색 페이지에서 받은 리스트길이로 했는데, 검색결과가 없을 시 ( 리스트가 비어있을 경우 ) 검색결과가 없다고 출력이 안됨

해결 과정

print도 찍어보고 dartpad도 해봤지만 해결이 안돼서 다시 해당 페이지 뷰를 자세히 보니 설마 itemCount가 0이면 페이지뷰가 안뜨나?해서 0으로 해봤더니 안뜸 quoteData.length == 0 ? 1 : quoteData.length 로 해결