25일차 과제 2 - rlatkddbs99/Flutter GitHub Wiki

자꾸 오류가 떠서 보니 소켓통신에서 오류가 있었고 매니페스트에 인터넷 권한도 주고 해봐도 안되었었다. 보니까 자꾸 애뮬레이터를 오프라인 애뮬레이터로 띄워서 그런거였다;

Android Emulator - flutter_emulator_5554 2023-03-02 23-12-16

main.dart

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:todo/page/mainPage.dart';

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

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

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      home: mainPage(),
    );
  }
}

model/todo.dart

class Todo {
  int userId;
  int id;
  String title;
  bool completed;

  Todo(
      {required this.userId,
      required this.id,
      required this.title,
      required this.completed});

  factory Todo.fromMap(Map<String, dynamic> map) {
    return Todo(
        userId: map['userId'],
        id: map['id'],
        title: map['title'],
        completed: map['completed']);
  }
}

page/mainPage.dart

import 'dart:ffi';
import 'dart:ui';

import 'package:dio/dio.dart';
import 'package:flutter/material.dart';
import 'package:flutter/src/widgets/container.dart';
import 'package:flutter/src/widgets/framework.dart';
import 'package:todo/model/todo.dart';
import 'package:todo/widget/filter_bottom_sheet.dart';

import '../widget/todo_card.dart';

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

  @override
  State<mainPage> createState() => _mainPageState();
}

class _mainPageState extends State<mainPage> {
//   Future<List<Todo>> readData() async {  //오늘 강의는 퓨쳐 안쓰고 네트워크 데이터 요청하기
//     Dio dio = Dio();
//     var url = "https://jsonplaceholder.typicode.com/todos"; //url원하는 번호로 고치기
//     var res = await dio.get(url);
// //  print(res.data); //결과: 데이터 다 묶여서 뜸
// //  print(res.data.runtimeType);  //List<dynamic> , 변환과정 필요
//     if (res.statusCode == 200) {
//       var data = List<Map<String, dynamic>>.from(
//           res.data); //List<Map<String,dynamic>>으로 바꿔줘야돼
//       data.map((e) => Todo.fromMap(e)).toList();
//     }
//     return [];
//   }
  TodoFilter todoFilter = TodoFilter.all;
  Dio dio = Dio();
  var url = "https://jsonplaceholder.typicode.com/todos";
  List<Todo> todos = []; //todo가 저장될 리스트
  readTodos() async {
    var res = await dio.get(url); //요청
    if (res.statusCode == 200) {
      //잘 받아오면
      var data = List<Map<String, dynamic>>.from(res
          .data); //데이터 형태 리스트로 바꾸기(list<dynamic>이 List<Map<String,dynamic>>으로)
      setState(() {
        todos = data.map((e) => Todo.fromMap(e)).toList();
      });
      //새로운값 대치, 새로고침 할 수 있도록 setState설정. 한번만 받는게 아니라 리프레쉬 누를때마다하려고
      //futurebuilder가 아닌 이렇게
    }
  }

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

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      extendBodyBehindAppBar: true,
      appBar: AppBar(
        backgroundColor: Colors.transparent,
        foregroundColor: Colors.black,
        elevation: 0,
        flexibleSpace: ClipRRect(
          //넘어가는 부분 자동으로 blur 처리
          child: BackdropFilter(
            //appBar뒤에 위젯넣기
            filter: ImageFilter.blur(sigmaX: 10, sigmaY: 10),
          ),
        ),
        title: Text("Todo App"),
        actions: [
          IconButton(
              onPressed: () {
                showModalBottomSheet(
                    context: context,
                    builder: (context) => FilterBottomSheet(
                        filter: todoFilter, //일단 지금 설정한게 없기 때문에 기본값. all에 체크되어있음
                        onApply: (value) {
                          setState(() {
                            todoFilter = value; //내가 선택한 항목으로 화면 바꿀거야
                          });
                        })); //새로운값이 왔을 때 호출
              },
              icon: Icon(Icons.filter_alt)),
          IconButton(
              onPressed: () {
                readTodos(); //새로고침해서 네트워크 데이터 다시가져와
              },
              icon: Icon(Icons.refresh_sharp))
        ],
      ),
      body: ListView.builder(
        //TodoCard랑 연결
        itemBuilder: (context, index) =>
            TodoCard(todo: filterMaker(todos)[index]), //해당하는 인덱스의 todo를 생성할거야
        itemCount: filterMaker(todos)
            .length, //네트워크에 모든 데이터가 있는데 이제 filterMaker를 호출해서 해당하는 인덱스만 출력하게된다
      ),
    );
  }

  //todoFilter바꾸기
  List<Todo> filterMaker(List<Todo> value) {
    // todoFilter
    switch (todoFilter) {
      case TodoFilter.all:
        return value;
      case TodoFilter.completed:
        return value
            .where(
              (element) => element.completed == true,
            )
            .toList();
      case TodoFilter.incompleted:
        return value
            .where(
              (element) => element.completed == false,
            )
            .toList();
    }
  }
}

widget/todo_card.dart

import 'package:flutter/material.dart';

import '../model/todo.dart';

class TodoCard extends StatelessWidget {
  const TodoCard({super.key, required this.todo});
  final Todo todo; //todo넘겨받기

  @override
  Widget build(BuildContext context) {
    return Dismissible(
      //swipe했을 때 해당내용 지움, 지워주긴하는데 눈에서만 보이지 않음
      //onDisMissed속성을 이용해서 메모리에서도 같이 지워주는게 좋음
      key: Key(todo.id.toString()),
      child: Container(
        margin: const EdgeInsets.all(8),
        decoration: BoxDecoration(
          color: todo.completed ? Colors.green.shade100 : null, //성공이 된 todo의 색깔
          border: todo.completed
              ? Border.all(
                  color: Colors.green,
                )
              : null,
          borderRadius: BorderRadius.circular(8),
        ),
        child: ListTile(
          title: Text(
            todo.title,
            style: TextStyle(
              color: todo.completed ? Colors.green : null, //이미끝난거면 초록색으로
              fontWeight: FontWeight.bold,
            ),
          ),
          trailing: todo.completed //성공한거면 아이콘 넣음
              ? const Icon(
                  Icons.check_circle,
                  color: Colors.green,
                )
              : null,
        ),
      ),
    );
  }
}

widget/filter_bottom_sheet.dart

import 'package:flutter/material.dart';

enum TodoFilter { all, completed, incompleted } //어떠한 값을 만들어 줄 때 설정해주는 상수값

class FilterBottomSheet extends StatefulWidget {
  const FilterBottomSheet(
      {Key? key, required this.filter, required this.onApply})
      : super(key: key);
  final TodoFilter filter; //enum으로 정의한 3개중에서만 가능
  final Function(TodoFilter) onApply; //전달받는 함수

  @override
  State<FilterBottomSheet> createState() => _FilterBottomSheetState();
}

class _FilterBottomSheetState extends State<FilterBottomSheet> {
  onApply(TodoFilter filter) {
    ScaffoldMessenger.of(context).showSnackBar(
      SnackBar(
        content: Text('Filter applied: $filter'),
      ),
    );
    widget.onApply(filter);
    Navigator.pop(context);
  }

  @override
  Widget build(BuildContext context) {
    return Container(
      padding: const EdgeInsets.all(16),
      child: Column(
        mainAxisSize: MainAxisSize.min,
        children: [
          ListTile(
            title: const Text('All'),
            trailing: Checkbox(
              value: widget.filter == TodoFilter.all,
              onChanged: (value) {
                //체크박스가 체크 되었을 떄
                if (value == true) onApply(TodoFilter.all);
              },
            ),
          ),
          ListTile(
            title: const Text('Completed'),
            trailing: Checkbox(
              value: widget.filter == TodoFilter.completed,
              onChanged: (value) {
                if (value == true) onApply(TodoFilter.completed);
              },
            ),
          ),
          ListTile(
            title: const Text('InCompleted'),
            trailing: Checkbox(
              value: widget.filter == TodoFilter.incompleted,
              onChanged: (value) {
                if (value == true) onApply(TodoFilter.incompleted);
              },
            ),
          ),
        ],
      ),
    );
  }
}

⚠️ **GitHub.com Fallback** ⚠️