Avaliação Final – Função de Controle Parental para VLC-iOS

Quando comecei este projeto, o objetivo era claro: implementar um sistema de Controle Parental no VLC para iOS que pudesse proteger ações sensíveis, como excluir arquivos de mídia, acessar configurações e abrir streamings de rede, exigindo autenticação através de uma senha ou Face ID. O objetivo era fornecer uma experiência mais segura para famílias, particularmente pais que compartilham seus dispositivos com crianças pequenas.

Mas, desde o início, percebi que esta tarefa não seria simples. Na verdade, acabou sendo a experiência técnica mais desafiadora que já tive, muito mais complexa do que qualquer coisa que enfrentei na faculdade, projetos pessoais ou trabalho freelance. E não apenas por causa do código. Este também foi meu primeiro projeto colaborativo de código aberto, o que significava aprender a navegar por um enorme código compartilhado, respeitar as mudanças de outros contribuidores e manter um histórico Git limpo e revisável, algo que nunca havia feito antes.

Primeiros Passos e Desafios do Git

No início de junho, comecei projetando a estrutura de uma nova classe, ParentalControlCoordinator, que lidaria com todos os aspectos da autenticação. No entanto, rapidamente tive problemas ao trabalhar com Git. Após executar um git rebase master, acidentalmente introduzi dezenas de commits não relacionados na minha merge request. Demorei quase uma semana para entender como limpar o histórico de commits adequadamente usando git reset e git push --force, mas eventualmente consegui recuperar a branch e deixá-la pronta para desenvolvimento novamente.

Primeira Versão – Lógica Básica de Autenticação

Uma vez que os problemas do Git foram resolvidos, comecei a experimentar com a lógica real da funcionalidade. O primeiro método que criei foi:

func authenticate(completion: @escaping (Bool) -> Void)
Tela de Configurações com Toggles
Função Swift Authenticate

Ele verificava se o controle parental estava habilitado e, se sim, chamava:

keychain.validateSecret(allowBiometricAuthentication: true, isCancellable: true)
Tela de Entrada de Senha

Isso funcionava em alguns cenários, mas revelou uma grande falha arquitetural: a funcionalidade de controle parental estava vinculada ao Passcode Lock, uma funcionalidade que solicita ao usuário que digite um código de acesso ao abrir o aplicativo. Se o Passcode Lock estivesse desabilitado, o controle parental não funcionaria de forma alguma.

Independência Entre Funcionalidades

Para resolver isso, criei uma entrada separada no keychain dedicada ao controle parental. Em seguida, refatorei o ParentalControlCoordinator para remover todas as dependências do UserDefaults e reutilizei o KeychainCoordinator apenas para lógica relacionada à exibição da tela de código de acesso. Isso permitiu que o controle parental e o bloqueio por código funcionassem completamente independentes, como deveriam.

Implementação do Keychain

Protegendo Ações – Método por Método

Com a lógica de autenticação funcionando, passei a proteger funcionalidades individuais. Para evitar duplicação de código, criei um método centralizado:

func authorizeIfParentalControlIsEnabled(action: @escaping @convention(block) () -> Void, fail: (@convention(block) () -> Void)? = nil)
Função Swift Authorize

Este método era então chamado antes de cada ação restrita. Aqui está como foi integrado em todo o aplicativo:

  • Renomear um arquivo: Dentro de EditActions.rename(...)
  • Excluir um arquivo: Em EditActions.delete(...)
  • Abrir a tela de configurações: Via TabBarCoordinator usando tabBarController(_:shouldSelect:) e handleAppDidBecomeActive()
  • Abrir um stream de rede: Em _openURLStringAndDismiss()
  • Baixar arquivos: Também em _openURLStringAndDismiss()
  • Acessar arquivos locais: Em RemoteNetworkDataSource, via tableView(_:didSelectRowAt:)
  • Adicionar/remover arquivos de playlists/grupos de mídia: Em EditActions, envolvendo a lógica com authorizeIfParentalControlIsEnabled

Lógica de Sessão e Timeout

Para evitar sobrecarregar o usuário com solicitações constantes de código de acesso, implementei uma sessão de desbloqueio temporária. Após autenticação bem-sucedida, um timestamp era armazenado usando:

private var lastAuthenticationDate: Date?

Em seguida, verifiquei o tempo decorrido usando:

var isParentalControlUnlocked: Bool

Isso permitia que os usuários realizassem múltiplas ações protegidas dentro de uma janela de 30 segundos sem re-autenticar.

Autenticação Biométrica – O Último Obstáculo

O último e mais frustrante desafio foi a integração do Face ID. Se o usuário abrisse o aplicativo usando Face ID (via Passcode Lock), então tentasse autenticar novamente para controle parental, a tela do Face ID apareceria repetidamente e nunca seria dispensada.

Mesmo que a autenticação fosse tecnicamente bem-sucedida, o prompt biométrico permaneceria visível. Tentei de tudo, desde refatorar os métodos existentes até criar um novo showPasscodeController() dentro do ParentalControlCoordinator, e verificar LAContext, mas o problema persistia.

Tela de Autenticação Face ID

Status Atual e O Que Resta

A funcionalidade está atualmente em um estado avançado e testável. Ela protege com sucesso todas as principais ações e fornece uma boa experiência do usuário graças à lógica de sessão de desbloqueio.

No entanto, a merge request ainda não está pronta para ser mergeada devido ao conflito não resolvido com o Face ID, que parece requerer investigação mais profunda e colaboração.

Reflexões

Este projeto tem sido uma jornada incrível. Tive a liberdade de projetar a lógica e estruturar a arquitetura. Essa autonomia criativa é um dos aspectos mais gratificantes de trabalhar em código aberto.

Ao mesmo tempo, isso me levou aos meus limites. Tive que aprender Git da maneira difícil, mergulhar em uma base de código grande e corrigir bugs que nem sabia como descrever no início.

Embora ainda não tenha sido mesclado, acredito que este é o primeiro rascunho sólido de uma funcionalidade útil. Uma que pode ser refinada e eventualmente adicionada ao VLC para iOS para melhorar a experiência para famílias ao redor do mundo.