misskey_emoji 1.0.0 copy "misskey_emoji: ^1.0.0" to clipboard
misskey_emoji: ^1.0.0 copied to clipboard

A Flutter library for caching and resolving Misskey custom emoji metadata with persistent Isar storage, search, and efficient retrieval mechanisms.

misskey_emoji #

Pub package GitHub License

A Flutter library for caching and resolving Misskey MFM (Markup For Misskey) emoji metadata with persistent storage and efficient retrieval.

日本語

Features #

  • Emoji metadata caching with persistent storage using Isar database (names, URLs, attributes, etc.)
  • Efficient emoji resolution and retrieval by shortcode
  • In-memory and persistent catalog implementations
  • Search functionality for emojis by shortcode and keywords
  • Integration with Misskey API for emoji synchronization
  • Cross-platform support (iOS/Android)
  • Optimized for MFM (Markup For Misskey) emoji handling
  • Note: Image data caching should be implemented on the application side using libraries like cached_network_image

Installation #

Add this to your package's pubspec.yaml file:

dependencies:
  misskey_emoji: ^1.0.0

Quick Start #

Basic Usage #

import 'package:misskey_emoji/misskey_emoji.dart';
import 'package:misskey_api_core/misskey_api_core.dart';

// Create HTTP client for Misskey API
final httpClient = MisskeyHttpClient(
  config: MisskeyApiConfig(baseUrl: Uri.parse('https://misskey.io')),
);

// Create emoji API client
final emojiApi = MisskeyEmojiApi(httpClient);

// Create persistent catalog with Isar storage
final catalog = PersistentEmojiCatalog(
  api: emojiApi,
  store: IsarEmojiStore(),
);

// Sync emoji metadata from server
await catalog.sync();

// Get emoji metadata by shortcode
final emoji = await catalog.get(':custom_emoji:');
if (emoji != null) {
  print('Emoji URL: ${emoji.url}');
  print('Is animated: ${emoji.animated}');
}

// Search emojis
final searchResults = await EmojiSearch.search(
  catalog,
  query: 'smile',
  options: EmojiSearchOptions(limit: 10),
);

Using Emoji Resolver #

// Create resolver for emoji resolution
final resolver = MisskeyEmojiResolver(catalog: catalog);

// Resolve emoji metadata from shortcode
final emojiImage = await resolver.resolve(':custom_emoji:');
if (emojiImage != null) {
  print('Resolved emoji URL: ${emojiImage.url}');
  print('Is animated: ${emojiImage.animated}');
  print('Is sensitive: ${emojiImage.isSensitive}');
}

Displaying Emojis with Image Caching #

// For displaying emojis with image caching, implement on the application side
import 'package:cached_network_image/cached_network_image.dart';

Widget buildEmoji(String shortcode) {
  return FutureBuilder<EmojiImage?>(
    future: resolver.resolve(shortcode),
    builder: (context, snapshot) {
      if (snapshot.hasData && snapshot.data != null) {
        return CachedNetworkImage(
          imageUrl: snapshot.data!.url.toString(),
          placeholder: (context, url) => CircularProgressIndicator(),
          errorWidget: (context, url, error) => Icon(Icons.error),
        );
      }
      return Icon(Icons.emoji_emotions);
    },
  );
}

In-Memory Catalog (for temporary usage) #

// For cases where persistent storage is not needed
final inMemoryCatalog = InMemoryEmojiCatalog(
  api: emojiApi,
  host: 'misskey.io',
);

await inMemoryCatalog.sync();
final emoji = await inMemoryCatalog.get(':example:');

Resource Management #

When you finish using catalogs or stores, call dispose() to release resources.

Basic cleanup #

final isar = await openEmojiIsarForServer(
  Uri.parse('https://misskey.io'),
  directory: '/path/to/isar',
);
final store = IsarEmojiStore(isar);
final catalog = PersistentEmojiCatalog(api: emojiApi, store: store);

try {
  await catalog.sync();
  final emoji = catalog.get(':custom_emoji:');
} finally {
  await catalog.dispose(); // store.dispose() is called internally
  await isar.close(); // close Isar explicitly when you own it
}

Error handling #

final catalog = PersistentEmojiCatalog(
  api: emojiApi,
  store: store,
  onSyncError: (error, stackTrace) {
    // Log errors for debugging or monitoring
    print('Emoji sync failed: $error');
    // You can also send to error tracking service
  },
);

Riverpod integration examples #

Basic: Single provider with owned Isar

@riverpod
class EmojiCatalogNotifier extends _$EmojiCatalogNotifier {
  @override
  FutureOr<PersistentEmojiCatalog> build() async {
    // Create API client
    final httpClient = MisskeyHttpClient(
      config: MisskeyApiConfig(baseUrl: Uri.parse('https://misskey.io')),
    );
    final emojiApi = MisskeyEmojiApi(httpClient);
    
    // Create store with owned Isar instance
    final appDir = await getApplicationDocumentsDirectory();
    final isar = await openEmojiIsarForServer(
      Uri.parse('https://misskey.io'),
      directory: appDir.path,
    );
    final store = IsarEmojiStore(isar, ownsIsar: true);
    final catalog = PersistentEmojiCatalog(
      api: emojiApi,
      store: store,
      onSyncError: (error, stackTrace) {
        debugPrint('Emoji sync failed: $error');
      },
    );

    // Dispose resources when provider is disposed
    ref.onDispose(() async {
      await catalog.dispose(); // closes Isar because ownsIsar is true
    });

    await catalog.sync();
    return catalog;
  }
}
// Shared Isar instance provider (reusable across multiple catalogs)
@riverpod
Future<Isar> emojiIsar(Ref ref) async {
  final appDir = await getApplicationDocumentsDirectory();
  final isar = await openEmojiIsarForServer(
    Uri.parse('https://misskey.io'),
    directory: appDir.path,
  );
  
  // Close Isar when the app is disposed
  ref.onDispose(() async {
    await isar.close();
  });
  
  return isar;
}

// Emoji catalog provider using shared Isar
@riverpod
class EmojiCatalogNotifier extends _$EmojiCatalogNotifier {
  @override
  FutureOr<PersistentEmojiCatalog> build() async {
    final httpClient = MisskeyHttpClient(
      config: MisskeyApiConfig(baseUrl: Uri.parse('https://misskey.io')),
    );
    final emojiApi = MisskeyEmojiApi(httpClient);
    
    // Use shared Isar instance (ownsIsar: false is default)
    final isar = await ref.watch(emojiIsarProvider.future);
    final store = IsarEmojiStore(isar); // Isar lifecycle managed by emojiIsarProvider
    final catalog = PersistentEmojiCatalog(
      api: emojiApi,
      store: store,
      onSyncError: (error, stackTrace) {
        // Send to error tracking service (e.g., Sentry, Firebase Crashlytics)
        debugPrint('Emoji sync failed: $error');
      },
    );

    ref.onDispose(() async {
      await catalog.dispose(); // Only disposes catalog, Isar remains open
    });

    await catalog.sync();
    return catalog;
  }
}

// Usage in your widget
class EmojiPickerWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final catalogAsync = ref.watch(emojiCatalogNotifierProvider);
    
    return catalogAsync.when(
      data: (catalog) {
        final emoji = catalog.get(':custom_emoji:');
        return emoji != null ? Text('Found: ${emoji.name}') : Text('Not found');
      },
      loading: () => CircularProgressIndicator(),
      error: (error, stack) => Text('Error: $error'),
    );
  }
}

API Reference #

For detailed API documentation, please refer to the documentation on pub.dev.

License #

This project is published by 司書 (LibraryLibrarian) under the 3-Clause BSD License. For details, please see the LICENSE file.


Japanese #

Misskey MFM(Markup For Misskey)絵文字のメタデータのキャッシュと解決を行うFlutterライブラリ。永続化ストレージと効率的な取得機能を提供します。

概要 #

  • Isarデータベースを使用した絵文字メタデータの永続化キャッシュ(名前、URL、属性など)
  • 効率的な絵文字解決と取得機能
  • インメモリおよび永続化カタログの実装
  • ショートコードとキーワードによる絵文字検索機能
  • Misskey APIとの統合による絵文字同期
  • MFM(Markup For Misskey)絵文字処理の最適化
  • 注意: 画像データのキャッシュはcached_network_imageなどのライブラリを使用してアプリケーション側で実装してください

導入 #

pubspec.yamlファイルに以下を追加してください:

dependencies:
  misskey_emoji: ^1.0.0

利用方法 #

基本的な使用方法 #

import 'package:misskey_emoji/misskey_emoji.dart';
import 'package:misskey_api_core/misskey_api_core.dart';

// Misskey API用のHTTPクライアントを作成
final httpClient = MisskeyHttpClient(
  config: MisskeyApiConfig(baseUrl: Uri.parse('https://misskey.io')),
);

// 絵文字APIクライアントを作成
final emojiApi = MisskeyEmojiApi(httpClient);

// Isarストレージを使用した永続化カタログを作成
final catalog = PersistentEmojiCatalog(
  api: emojiApi,
  store: IsarEmojiStore(),
);

// サーバーから絵文字メタデータを同期
await catalog.sync();

// ショートコードで絵文字メタデータを取得
final emoji = await catalog.get(':custom_emoji:');
if (emoji != null) {
  print('絵文字URL: ${emoji.url}');
  print('アニメーション: ${emoji.animated}');
}

// 絵文字を検索
final searchResults = await EmojiSearch.search(
  catalog,
  query: 'smile',
  options: EmojiSearchOptions(limit: 10),
);

絵文字リゾルバーの使用 #

// 絵文字解決用のリゾルバーを作成
final resolver = MisskeyEmojiResolver(catalog: catalog);

// ショートコードから絵文字メタデータを解決
final emojiImage = await resolver.resolve(':custom_emoji:');
if (emojiImage != null) {
  print('解決された絵文字URL: ${emojiImage.url}');
  print('アニメーション: ${emojiImage.animated}');
  print('センシティブ: ${emojiImage.isSensitive}');
}

画像キャッシュ付きの絵文字表示 #

// 画像キャッシュ付きで絵文字を表示する場合は、アプリケーション側で実装
import 'package:cached_network_image/cached_network_image.dart';

Widget buildEmoji(String shortcode) {
  return FutureBuilder<EmojiImage?>(
    future: resolver.resolve(shortcode),
    builder: (context, snapshot) {
      if (snapshot.hasData && snapshot.data != null) {
        return CachedNetworkImage(
          imageUrl: snapshot.data!.url.toString(),
          placeholder: (context, url) => CircularProgressIndicator(),
          errorWidget: (context, url, error) => Icon(Icons.error),
        );
      }
      return Icon(Icons.emoji_emotions);
    },
  );
}

インメモリカタログ(一時的な使用) #

// 永続化ストレージが不要な場合
final inMemoryCatalog = InMemoryEmojiCatalog(
  api: emojiApi,
  host: 'misskey.io',
);

await inMemoryCatalog.sync();
final emoji = await inMemoryCatalog.get(':example:');

リソース管理 #

カタログやストアの利用後は、dispose()を呼び出してリソースを解放するようにしてください

基本的なクリーンアップ #

final isar = await openEmojiIsarForServer(
  Uri.parse('https://misskey.io'),
  directory: '/path/to/isar',
);
final store = IsarEmojiStore(isar);
final catalog = PersistentEmojiCatalog(api: emojiApi, store: store);

try {
  await catalog.sync();
  final emoji = catalog.get(':custom_emoji:');
} finally {
  await catalog.dispose(); // store.dispose() が内部で呼ばれる
  await isar.close(); // 所有している場合は明示的にクローズ
}

エラーハンドリング #

final catalog = PersistentEmojiCatalog(
  api: emojiApi,
  store: store,
  onSyncError: (error, stackTrace) {
    // デバッグや監視のためのエラーログ
    print('絵文字同期失敗: $error');
    // エラー追跡サービスに送信することも可能
  },
);

Riverpodとの統合 #

単一プロバイダーでIsarを所有

@riverpod
class EmojiCatalogNotifier extends _$EmojiCatalogNotifier {
  @override
  FutureOr<PersistentEmojiCatalog> build() async {
    // APIクライアントを作成
    final httpClient = MisskeyHttpClient(
      config: MisskeyApiConfig(baseUrl: Uri.parse('https://misskey.io')),
    );
    final emojiApi = MisskeyEmojiApi(httpClient);
    
    // 所有権を持つIsarインスタンスでストアを作成
    final appDir = await getApplicationDocumentsDirectory();
    final isar = await openEmojiIsarForServer(
      Uri.parse('https://misskey.io'),
      directory: appDir.path,
    );
    final store = IsarEmojiStore(isar, ownsIsar: true);
    final catalog = PersistentEmojiCatalog(
      api: emojiApi,
      store: store,
      onSyncError: (error, stackTrace) {
        debugPrint('絵文字同期失敗: $error');
      },
    );

    // プロバイダーがdisposeされた時にリソースを解放
    ref.onDispose(() async {
      await catalog.dispose(); // ownsIsarがtrueならIsarもクローズされる
    });

    await catalog.sync();
    return catalog;
  }
}

Isarインスタンスを共有してリソース管理を改善(基本的にこちらを推奨)

// 共有Isarインスタンスプロバイダー(複数のカタログで再利用可能)
@riverpod
Future<Isar> emojiIsar(Ref ref) async {
  final appDir = await getApplicationDocumentsDirectory();
  final isar = await openEmojiIsarForServer(
    Uri.parse('https://misskey.io'),
    directory: appDir.path,
  );
  
  // アプリ終了時にIsarをクローズ
  ref.onDispose(() async {
    await isar.close();
  });
  
  return isar;
}

// 共有Isarを使用する絵文字カタログプロバイダー
@riverpod
class EmojiCatalogNotifier extends _$EmojiCatalogNotifier {
  @override
  FutureOr<PersistentEmojiCatalog> build() async {
    final httpClient = MisskeyHttpClient(
      config: MisskeyApiConfig(baseUrl: Uri.parse('https://misskey.io')),
    );
    final emojiApi = MisskeyEmojiApi(httpClient);
    
    // 共有Isarインスタンスを使用(ownsIsar: falseがデフォルト)
    final isar = await ref.watch(emojiIsarProvider.future);
    final store = IsarEmojiStore(isar); // IsarのライフサイクルはemojiIsarProviderが管理
    final catalog = PersistentEmojiCatalog(
      api: emojiApi,
      store: store,
      onSyncError: (error, stackTrace) {
        // エラー追跡サービスに送信(例: Sentry、Firebase Crashlytics)
        debugPrint('絵文字同期失敗: $error');
      },
    );

    ref.onDispose(() async {
      await catalog.dispose(); // カタログのみをdispose、Isarは開いたまま
    });

    await catalog.sync();
    return catalog;
  }
}

// ウィジェットでの使用例
class EmojiPickerWidget extends ConsumerWidget {
  @override
  Widget build(BuildContext context, WidgetRef ref) {
    final catalogAsync = ref.watch(emojiCatalogNotifierProvider);
    
    return catalogAsync.when(
      data: (catalog) {
        final emoji = catalog.get(':custom_emoji:');
        return emoji != null ? Text('見つかりました: ${emoji.name}') : Text('見つかりません');
      },
      loading: () => CircularProgressIndicator(),
      error: (error, stack) => Text('エラー: $error'),
    );
  }
}

APIリファレンス #

詳細なAPIドキュメントについては、pub.devのドキュメントを参照してください。

ライセンス #

このプロジェクトは司書(LibraryLibrarian)によって、3-Clause BSD Licenseの下で公開されています。詳細はLICENSEファイルをご覧ください。

リンク #

0
likes
160
points
103
downloads

Publisher

verified publisherlibrarylibrarian.com

Weekly Downloads

A Flutter library for caching and resolving Misskey custom emoji metadata with persistent Isar storage, search, and efficient retrieval mechanisms.

Homepage
Repository (GitHub)
View/report issues

Topics

#misskey #emoji #cache #resolver #catalog

Documentation

API reference

License

BSD-3-Clause (license)

Dependencies

flutter, isar_community, isar_community_flutter_libs, json_annotation, misskey_api_core

More

Packages that depend on misskey_emoji