Adaptando o UIKit do EaseMob para HarmonyOS com Flutter

Configuração do ambiente Flutter para HarmonyOS

Utilize uma versão do Flutter oficialmente compatível com HarmonyOS. A versão recomendada é 3.22.1-ohos-1.0.4, que inclui suporte nativo ao runtime OHOS. Atualize o pubspec.yaml:

environment:
  sdk: '3.4.0'
  flutter: "3.22.1-ohos-1.0.4"

Atualização de dependências

Substitua os pacotes padrão por versões adaptadas ao HarmonyOS, priorizando forks mantidos pela comunidade OpenHarmony SIG:

dependencies:
  em_chat_uikit:
    path: ../em_chat_uikit-2.2.0
  im_flutter_sdk_ohos:
    git:
      url: https://github.com/easemob/im_flutter_sdk_oh.git
      ref: 1.5.3

dependency_overrides:
  record:
    git:
      url: https://gitcode.com/openharmony-sig/fluttertpc_record.git
      path: record
      ref: d40e26bd4052362d505ef8c2c600ac69aa5a967a
  shared_preferences:
    git:
      url: https://gitcode.com/openharmony-sig/flutter_packages.git
      path: packages/shared_preferences/shared_preferences
  image_picker:
    git:
      url: https://gitcode.com/openharmony-sig/flutter_packages.git
      path: packages/image_picker/image_picker
  audioplayers:
    git:
      url: https://gitcode.com/openharmony-sig/flutter_audioplayers.git
      path: packages/audioplayers
  video_player:
    git:
      url: https://gitcode.com/openharmony-sig/flutter_packages.git
      path: packages/video_player/video_player
  flutter_localization:
    git:
      url: https://gitcode.com/openharmony-sig/flutter_localization.git
  sqflite:
    git:
      url: https://gitcode.com/OpenHarmony-SIG/flutter_sqflite.git
      ref: 'github.com/tekartik/sqflite.git/v2.3.3+1'
      path: sqflite

Otimizações de interface para HarmonyOS

O compoennte ChatUIKitPopupMenu foi reestruturado para evitar estouro visual em telas com densidade variável. As principais mudanças incluem:

  • Substituição de Container por SizedBox para eliminar padding vertical indesejado;
  • Redução do espaçamento entre ícone e rótulo de 4px para 2px;
  • Uso explícito de TextScaler.noScaling para garantir consistência tipográfica.

Exemplo revisado:

@override
Widget build(BuildContext context) {
  return Wrap(
    direction: Axis.horizontal,
    children: widget.actions.map((action) {
      return InkWell(
        onTap: () {
          widget.close?.call();
          action.onTap?.call();
        },
        child: SizedBox(
          width: 72,
          height: 64,
          child: Column(
            mainAxisAlignment: MainAxisAlignment.center,
            children: [
              if (action.icon != null)
                SizedBox(height: 28, width: 28, child: action.icon!),
              const SizedBox(height: 2),
              Text(
                action.label,
                maxLines: 1,
                overflow: TextOverflow.ellipsis,
                textScaler: TextScaler.noScaling,
                style: TextStyle(
                  color: widget.style.foregroundColor,
                  fontSize: 12,
                  fontWeight: FontWeight.w500,
                ),
              ),
            ],
          ),
        ),
      );
    }).toList(),
  );
}

Tratamento de funcionalidades não suportadas

Recursos como *threads*, tradução e denúncia não possuem implementação nativa no SDK HarmonyOS. Desative-os via configuração centralizada:

// Desativa threads de mensagens
ChatUIKitSettings.enableMessageThread = false;

// Remove opções de menu longo
ChatUIKitSettings.msgItemLongPressActions
  ..remove(ChatUIKitActionType.translate)
  ..remove(ChatUIKitActionType.report);

Gerenciamento de permissões de áudio

No arquivo ohos/entry/src/main/module.json5, adicione a permissão de microfone:

{
  "module": {
    "requestPermissions": [
      {
        "name": "ohos.permission.MICROPHONE",
        "reason": "$string:permission_microphone_reason",
        "usedScene": {
          "when": "always",
          "abilities": ["EntryAbility"]
        }
      }
    ]
  }
}

E defina a string localizada em ohos/AppScope/resources/base/element/string.json:

{
  "string": [
    {
      "name": "permission_microphone_reason",
      "value": "Permite gravar mensagens de voz e participar de chamadas de áudio/vídeo"
    }
  ]
}

Na lógica de gravação, substitua lançamentos de exceção por notificações amigáveis ao usuário:

Future<void> startRecording() async {
  if (!await record.hasPermission()) {
    ChatUIKit.instance.sendChatUIKitEvent(
      ChatUIKitEvent.noMicrophonePermission
    );
    _showPermissionAlert();
    return;
  }

  try {
    final timestamp = DateTime.now().millisecondsSinceEpoch;
    final fileName = '$timestamp.$extensionName';
    await record.start(recordConfig, path: '${_directory!.path}/$fileName');
    _state?.switchRecordType(RecordBarRecordType.recording);
  } on RecordException catch (e) {
    debugPrint('Gravação falhou: $e');
  }
}

void _showPermissionAlert() {
  final ctx = _state?.context;
  if (ctx == null || !ctx.mounted) return;

  showChatUIKitDialog(
    context: ctx,
    title: ChatUIKitLocal.microphonePermissionDeniedTitle.localString(ctx),
    content: ChatUIKitLocal.microphonePermissionDeniedContent.localString(ctx),
    actionItems: [
      ChatUIKitDialogAction.confirm(
        label: ChatUIKitLocal.confirm.localString(ctx),
        onTap: () => Navigator.of(ctx).pop(),
      ),
    ],
  );
}

Substituição de plugins incompatíveis

O plugin open_file não oferece suporte a HarmonyOS. Substitua-o por open_filex, aplicando codificação URI aos caminhos de arquivos para evitar falhas com caracteres especiais (como # em app keys):

final encodedPath = Uri.encodeComponent('/data/storage/el1/base/haps/entry/files/downloads/file.pdf');
final fileUri = Uri.parse('file://$encodedPath');
await OpenFilex.open(fileUri.toString());

Atualização dinâmica de reações

Para garantir que a lista de mensagens reflita alterações em reações em tempo real, implemente um mecanismo de atualização explícito no controlador de mensagens:

Future<void> updateReaction(
  String msgId,
  String emoji,
  bool isAdding,
) async {
  try {
    await ChatUIKit.instance.updateMessageReaction(
      messageId: msgId,
      reaction: emoji,
      isAdd: isAdding,
    );
    await refreshMessageReaction(msgId); // Atualiza UI imediatamente
  } catch (e) {
    chatPrint('Falha ao atualizar reação: $e');
  }
}

Future<void> refreshMessageReaction(String msgId) async {
  final idx = msgModelList.indexWhere((m) => m.message.msgId == msgId);
  if (idx == -1) return;

  final msg = await ChatUIKit.instance.loadMessage(messageId: msgId);
  if (msg == null) return;

  final reactions = await msg.reactionList();
  msgModelList[idx] = msgModelList[idx].copyWith(
    message: msg,
    reactions: reactions,
  );
  notifyListeners();
}

Integre esse comportamento ao componente de visualização de reações via callback onReactionChanged, disparado ao adicionar/remover uma reação — acionando assim a atualização da lista pai.

Tags: easemob HarmonyOS Flutter im-sdk uikit

Publicado em 8-29 08:16