Flutter漫画阅读插件mangadex_library的使用

发布于 1周前 作者 nodeper 来自 Flutter

Flutter漫画阅读插件mangadex_library的使用

警告!正在进行中的库

该库目前处于开发中和逐步更改的状态,因此不包含许多功能,并且经常添加新功能和修复bug。当前状态下,该库能够实现以下功能:

  • 获取登录、刷新和会话令牌
  • 注销用户
  • 搜索漫画
  • 获取漫画缩略图/封面
  • 获取漫画章节
  • 获取漫画页面
  • 获取已登录用户数据
  • 获取用户详细信息
  • 获取作者详细信息
  • 管理自定义列表
  • 关注/取消关注漫画/组/用户
  • 设置/获取已登录用户的漫画阅读状态
  • 获取、创建和删除扫描组

2.0.0版本的重大变更

mangadex_library 之前只是一组静态函数,虽然工作正常,但存在一些问题:

  • 单个库文件变成了大量静态函数的堆栈,难以理解和维护。
  • 刷新令牌需要用户手动处理,库只提供了刷新函数。

为了解决这些问题,引入了以下重大变更:

  • 库现在更依赖于客户端实例而不是静态函数。你可以创建一个 MangadexClient 实例,使用 login 函数后,它会自动每14分钟刷新一次令牌(可以根据需要更改)。还有一个 onrefresh 回调函数,在成功刷新令牌时执行自定义任务。
  • 所有函数都放在各自的仓库中,以便未来更容易添加更多功能。

toJson()模型方法不稳定

所有JSON模型类的 toJson() 方法不稳定,因为MangaDex在对象为空时返回的是空列表 [] 而不是空对象 {}。例如,如果描述为空,则返回一个空列表而不是空对象。

快速入门

以下是一个快速演示API的示例代码:

import 'package:mangadex_library/mangadex_client.dart';
import 'package:mangadex_library/mangadex_server_exception.dart';
import 'package:mangadex_library/src/client_types/personal_client.dart';

void main() {
  printFilenames();
}

void printFilenames() async {
  const clientId = 'YOUR_CLIENT_ID'; // 替换为你的Client ID
  const clientSecret = 'YOUR_CLIENT_SECRET'; // 替换为你的Client Secret
  // 请参考MangaDex官方文档获取Client ID和Client Secret
  final client = MangadexPersonalClient(clientId: clientId, clientSecret: clientSecret);
  
  var username = 'USERNAME'; // 替换为你的用户名
  var password = 'PASSWORD'; // 替换为你的密码

  try {
    // 登录并设置登录令牌
    await client.login(username, password);

    // 搜索漫画,这里以 "oregairu" 为例
    var searchData = await client.search(query: 'oregairu');
    
    // 获取第一个搜索结果的漫画ID
    var mangaID = searchData.data![0].id;
    
    // 获取该漫画的所有章节
    var chapterData = await client.getChapters(mangaID!);
    
    // 获取第一个章节的ID
    var chapterID = chapterData.data![0].id;
    
    // 获取章节的基础URL
    var baseUrl = await client.getBaseUrl(chapterID!);
    
    // 打印该章节所有页面的URL
    baseUrl.chapter!.dataSaver!.forEach((filename) {
      print(client.constructPageUrl(baseUrl.baseUrl!, true, baseUrl.chapter!.hash!, filename));
    });
  } on MangadexServerException catch (e) {
    e.info.errors!.forEach((error) {
      print(error.title); // 打印错误标题
      print(error.detail); // 打印错误详情
    });
  }
  
  // 释放客户端资源,因为刷新定时器仍在运行
  client.dispose();
}

文档

文档正在使用wiki编写,计划提供详细的文档,因此需要一些时间。目前,生成的HTML文档可以在这里找到:doc/api/ 文件夹。

完整示例Demo

为了帮助你更好地理解如何使用 mangadex_library,以下是一个完整的Flutter应用示例,展示了如何集成该库来搜索漫画并显示其章节页面。

import 'package:flutter/material.dart';
import 'package:mangadex_library/mangadex_client.dart';
import 'package:mangadex_library/mangadex_server_exception.dart';
import 'package:mangadex_library/src/client_types/personal_client.dart';

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

class MyApp extends StatelessWidget {
  [@override](/user/override)
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'MangaDex Reader',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: MangaSearchPage(),
    );
  }
}

class MangaSearchPage extends StatefulWidget {
  [@override](/user/override)
  _MangaSearchPageState createState() => _MangaSearchPageState();
}

class _MangaSearchPageState extends State<MangaSearchPage> {
  final TextEditingController _searchController = TextEditingController();
  List<String> _mangaTitles = [];
  String? _selectedMangaId;

  final client = MangadexPersonalClient(
    clientId: 'YOUR_CLIENT_ID', // 替换为你的Client ID
    clientSecret: 'YOUR_CLIENT_SECRET', // 替换为你的Client Secret
  );

  Future<void> _searchManga(String query) async {
    try {
      var searchData = await client.search(query: query);
      setState(() {
        _mangaTitles = searchData.data?.map((e) => e.attributes.title).toList() ?? [];
        _selectedMangaId = null;
      });
    } on MangadexServerException catch (e) {
      print('Error: ${e.info.errors?.first.title}');
    }
  }

  Future<void> _selectManga(String mangaId) async {
    try {
      var chapterData = await client.getChapters(mangaId);
      var chapterID = chapterData.data?[0].id;
      var baseUrl = await client.getBaseUrl(chapterID!);

      Navigator.push(
        context,
        MaterialPageRoute(
          builder: (context) => ChapterPagesPage(
            baseUrl: baseUrl,
            chapterHash: baseUrl.chapter!.hash!,
            pageUrls: baseUrl.chapter!.dataSaver!.map((filename) {
              return client.constructPageUrl(baseUrl.baseUrl!, true, baseUrl.chapter!.hash!, filename);
            }).toList(),
          ),
        ),
      );
    } on MangadexServerException catch (e) {
      print('Error: ${e.info.errors?.first.title}');
    }
  }

  [@override](/user/override)
  void dispose() {
    client.dispose();
    super.dispose();
  }

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('MangaDex Reader'),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          children: [
            TextField(
              controller: _searchController,
              decoration: InputDecoration(
                labelText: 'Search for a manga',
                suffixIcon: IconButton(
                  icon: Icon(Icons.search),
                  onPressed: () {
                    _searchManga(_searchController.text);
                  },
                ),
              ),
            ),
            SizedBox(height: 16),
            Expanded(
              child: ListView.builder(
                itemCount: _mangaTitles.length,
                itemBuilder: (context, index) {
                  return ListTile(
                    title: Text(_mangaTitles[index]),
                    onTap: () {
                      _selectManga(searchData.data![index].id!);
                    },
                  );
                },
              ),
            ),
          ],
        ),
      ),
    );
  }
}

class ChapterPagesPage extends StatelessWidget {
  final BaseUrl baseUrl;
  final String chapterHash;
  final List<String> pageUrls;

  ChapterPagesPage({
    required this.baseUrl,
    required this.chapterHash,
    required this.pageUrls,
  });

  [@override](/user/override)
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text('Chapter Pages'),
      ),
      body: PageView.builder(
        itemCount: pageUrls.length,
        itemBuilder: (context, index) {
          return Image.network(pageUrls[index]);
        },
      ),
    );
  }
}

更多关于Flutter漫画阅读插件mangadex_library的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html

1 回复

更多关于Flutter漫画阅读插件mangadex_library的使用的实战系列教程也可以访问 https://www.itying.com/category-92-b0.html


当然,以下是如何在Flutter项目中集成并使用mangadex_library插件的一个基本示例。这个示例将展示如何初始化插件、获取漫画列表以及显示漫画详情。请注意,mangadex_library插件是一个假设的插件名称,实际上可能需要根据真实的插件名称和API进行调整。

首先,确保你的Flutter项目中已经添加了mangadex_library插件。在pubspec.yaml文件中添加以下依赖项(假设插件的真实名称就是mangadex_library):

dependencies:
  flutter:
    sdk: flutter
  mangadex_library: ^latest_version  # 替换为实际的最新版本号

然后,运行flutter pub get来安装依赖项。

接下来,在你的Flutter应用中,你可以按照以下步骤使用mangadex_library插件:

  1. 初始化插件并获取漫画列表
import 'package:flutter/material.dart';
import 'package:mangadex_library/mangadex_library.dart';

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

class MyApp extends StatefulWidget {
  @override
  _MyAppState createState() => _MyAppState();
}

class _MyAppState extends State<MyApp> {
  List<Manga> mangaList = [];
  MangaDexClient? client;

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

  void initMangaDexClient() async {
    // 初始化MangaDexClient,这里假设需要一些初始化参数,如API密钥等
    client = MangaDexClient(apiKey: 'your_api_key_here');

    // 获取漫画列表
    try {
      var response = await client!.getLatestManga();
      if (response.success) {
        setState(() {
          mangaList = response.data;
        });
      } else {
        print('Failed to fetch manga list: ${response.message}');
      }
    } catch (e) {
      print('Error initializing MangaDexClient: $e');
    }
  }

  @override
  Widget build(BuildContext context) {
    return MaterialApp(
      title: 'MangaDex Reader',
      theme: ThemeData(
        primarySwatch: Colors.blue,
      ),
      home: Scaffold(
        appBar: AppBar(
          title: Text('MangaDex Reader'),
        ),
        body: mangaList.isEmpty
            ? Center(child: CircularProgressIndicator())
            : ListView.builder(
                itemCount: mangaList.length,
                itemBuilder: (context, index) {
                  var manga = mangaList[index];
                  return ListTile(
                    title: Text(manga.title),
                    subtitle: Text(manga.author),
                    onTap: () {
                      // 导航到漫画详情页面
                      Navigator.push(
                        context,
                        MaterialPageRoute(builder: (context) => MangaDetailPage(manga: manga)),
                      );
                    },
                  );
                }),
      ),
    );
  }
}

// 假设MangaDexClient和相关的数据模型如下
class MangaDexClient {
  String apiKey;

  MangaDexClient({required this.apiKey});

  Future<MangaResponse> getLatestManga() async {
    // 这里应该是实际的网络请求代码,使用Dart的HttpClient或其他网络库
    // 由于这是一个示例,我们直接返回一个模拟的响应
    return MangaResponse(
      success: true,
      data: [
        Manga(title: 'Comic 1', author: 'Author 1'),
        Manga(title: 'Comic 2', author: 'Author 2'),
        // ...更多漫画
      ],
    );
  }
}

class MangaResponse {
  bool success;
  List<Manga> data;

  MangaResponse({required this.success, required this.data});
}

class Manga {
  String title;
  String author;

  Manga({required this.title, required this.author});
}

// 漫画详情页面
class MangaDetailPage extends StatelessWidget {
  final Manga manga;

  MangaDetailPage({required this.manga});

  @override
  Widget build(BuildContext context) {
    return Scaffold(
      appBar: AppBar(
        title: Text(manga.title),
      ),
      body: Padding(
        padding: const EdgeInsets.all(16.0),
        child: Column(
          crossAxisAlignment: CrossAxisAlignment.start,
          children: [
            Text('Title: ${manga.title}'),
            Text('Author: ${manga.author}'),
            // ...更多详情
          ],
        ),
      ),
    );
  }
}

这个示例展示了如何初始化MangaDexClient,获取最新的漫画列表,并在列表项被点击时导航到漫画详情页面。请注意,MangaDexClient和相关的数据模型(如MangaResponseManga)是假设的,你需要根据实际的插件API和返回的数据结构进行调整。

此外,实际的网络请求部分需要使用Dart的HttpClient或其他网络库来实现,这里为了简化示例而直接返回了一个模拟的响应。你需要根据插件提供的API文档来实现实际的网络请求和数据处理逻辑。

回到顶部