restqiita (item_list_screen.dart)


import 'package:flutter/material.dart';
import 'package:intl/intl.dart';
import 'package:restqiita/screens/sign_in_screen.dart';

import '../models/item.dart';
import '../models/user.dart';
import '../qiita_repository.dart';
import 'item_screen.dart';
import 'user_screen.dart';

class ItemListScreen extends StatefulWidget {
  @override
  _ItemListScreenState createState() => _ItemListScreenState();
}

class _ItemListScreenState extends State<ItemListScreen> {
  int _currentPage = 1;
  bool _isLoading = false;
  List<Item>? _itemList;
  Object? _error;

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

    _loadItemList(1);
  }

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      backgroundColor: Colors.white,
      appBar: AppBar(
        title: Text('Qiita App'),
        actions: [
          FutureBuilder<User>(
            future: QiitaRepository().getAuthenticatedUser(),
            builder: (context, snapshot) {
              final Widget icon = snapshot.hasData
                  ? _UserProfileIcon(
                size: 32,
                profileImageUrl: snapshot.data!.profileImageUrl,
              )
                  : Icon(Icons.person);
              return PopupMenuButton<String>(
                icon: icon,
                onSelected: (value) {
                  if (value == 'profile') {
                    _onProfileMenuIsSelected(snapshot.data!);
                  } else if (value == 'logout') {
                    _onLogoutMenuIsSelected();
                  }
                },
                itemBuilder: (context) {
                  return [
                    PopupMenuItem<String>(
                      value: 'profile',
                      child: Text('プロフィール'),
                    ),
                    PopupMenuItem(
                      value: 'logout',
                      child: Text('ログアウト'),
                    ),
                  ];
                },
              );
            },
          ),
        ],
      ),
      body: _buildBody(),
    );
  }

  Widget _buildBody() {
    if (_error != null) {
      return Center(
        child: Text(_error.toString()),
      );
    }

    if (_itemList != null) {
      return _ItemListView(
        itemList: _itemList!,
        onTapItem: (item) {
          Navigator.of(context).push(
            MaterialPageRoute(builder: (_) => ItemScreen(item: item)),
          );
        },
        onTapUser: (user) {
          Navigator.of(context).push(
            MaterialPageRoute(builder: (_) => UserScreen(user: user)),
          );
        },
        onScrollEnd: () {
          if (_isLoading == false) {
            _loadItemList(_currentPage + 1);
          }
        },
      );
    }

    return Center(
      child: CircularProgressIndicator(),
    );
  }

  void _loadItemList(int page) {
    QiitaRepository().getItemList(page: page).then((itemList) {
      setState(() {
        if (page == 1) {
          _itemList = itemList;
        } else {
          _itemList!.addAll(itemList);
        }
        _currentPage = page;
      });
    }).catchError((e) {
      setState(() {
        _error = e;
      });
    }).whenComplete(() {
      _isLoading = false;
    });
    _isLoading = true;
  }

  void _onProfileMenuIsSelected(User user) {
    Navigator.of(context).push(
      MaterialPageRoute(
        builder: (_) => UserScreen(user: user),
      ),
    );
  }

  void _onLogoutMenuIsSelected() async {
    await QiitaRepository().revokeSavedAccessToken();
    await QiitaRepository().deleteAccessToken();
    Navigator.of(context).pushReplacement(
      MaterialPageRoute(builder: (_) => SignInScreen()),
    );
  }
}

class _ItemListView extends StatelessWidget {
  final List<Item> itemList;
  final Function(Item item) onTapItem;
  final Function(User user) onTapUser;
  final Function() onScrollEnd;

  const _ItemListView({
    Key? key,
    required this.itemList,
    required this.onTapItem,
    required this.onTapUser,
    required this.onScrollEnd,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return NotificationListener<ScrollNotification>(
      onNotification: (ScrollNotification notification) {
        if (notification.metrics.extentAfter == 0.0) {
          onScrollEnd();
        }
        return true;
      },
      child: ListView.separated(
        itemCount: itemList.length,
        itemBuilder: (context, index) {
          final Item item = itemList[index];
          return InkWell(
            onTap: () => onTapItem(item),
            child: Padding(
              padding: EdgeInsets.symmetric(vertical: 4),
              child: ListTile(
                key: ValueKey<String>(item.id),
                leading: GestureDetector(
                  onTap: () => onTapUser(item.user),
                  child: _UserProfileIcon(
                    size: 48,
                    profileImageUrl: item.user.profileImageUrl,
                  ),
                ),
                title: Text(item.title),
                subtitle: Row(
                  mainAxisAlignment: MainAxisAlignment.spaceBetween,
                  mainAxisSize: MainAxisSize.min,
                  children: [
                    Container(
                      width: 120,
                      child: Text(
                        item.user.id,
                        overflow: TextOverflow.ellipsis,
                      ),
                    ),
                    SizedBox(width: 8),
                    Text(DateFormat.Md().add_jm().format(item.createdAt)),
                  ],
                ),
                trailing: _LikeCountBadge(likesCount: item.likesCount),
              ),
            ),
          );
        },
        separatorBuilder: (context, index) {
          return Divider(height: 1);
        },
      ),
    );
  }
}

class _UserProfileIcon extends StatelessWidget {
  final double size;
  final String profileImageUrl;

  const _UserProfileIcon({
    Key? key,
    required this.size,
    required this.profileImageUrl,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ClipRRect(
      borderRadius: BorderRadius.circular(4),
      child: Image.network(
        profileImageUrl,
        width: size,
        height: size,
        errorBuilder: (context, error, stackTrace) {
          return Container(
            width: size,
            height: size,
            color: Colors.grey,
          );
        },
      ),
    );
  }
}

class _LikeCountBadge extends StatelessWidget {
  final int likesCount;

  const _LikeCountBadge({
    Key? key,
    required this.likesCount,
  }) : super(key: key);

  @override
  Widget build(BuildContext context) {
    return ClipOval(
      child: Container(
        width: 20,
        height: 20,
        color: (likesCount > 0) ? Theme.of(context).primaryColor : Colors.grey,
        child: Center(
          child: Text(
            '$likesCount',
            style: TextStyle(color: Colors.white, fontSize: 12),
          ),
        ),
      ),
    );
  }
}