📚 Módulo 11: Mobile - Integração Nativa (Geolocalização, Preferences) e Design Responsivo

Com a base móvel estabelecida no Módulo 10, expandiremos nossa aplicação híbrida TecLoja 02 integrando recursos de hardware do dispositivo Android através de Plugins do Capacitor.

Neste módulo, implementaremos:

  1. Armazenamento persistente de tokens JWT e sessões usando o plugin Preferences (substituto seguro do localStorage no ecossistema web móvel).
  2. Detecção da localização física do usuário via sensor de GPS (Geolocation) para cálculo dinâmico e simulado de frete na tela do carrinho.
  3. Otimização de design reativo com foco em Mobile First, garantindo usabilidade ergonômica para interações de toque.

🗺️ 1. Fluxo de Acesso ao Hardware do Dispositivo

Abaixo, descrevemos como o fluxo de permissões e as chamadas JavaScript do Vue interagem com o hardware físico do smartphone:

sequenceDiagram
    participant V as Vue Component (Composables)
    participant C as Capacitor Native Bridge
    participant A as Android OS (Permissions / Hardware)

    V->>C: requestPermissions() [Geolocation]
    C->>A: Solicitar acesso ao GPS do aparelho
    A-->>C: Permissão Concedida pelo usuário
    C-->>V: Retornar status "granted"
    
    V->>C: getCurrentPosition()
    C->>A: Consultar satélites/redes de localização
    A-->>C: Objeto com Lat/Long
    C-->>V: Retornar coordenadas geográficas
    Note over V: Atualizar estado do frete na tela do Carrinho

💾 2. Armazenamento Seguro de Tokens com @capacitor/preferences

No ecossistema móvel, o clássico localStorage pode ser deletado pelo sistema operacional caso o dispositivo fique sem espaço em disco. Para resolver isso, utilizaremos o @capacitor/preferences, que encapsula chaves-valores persistentes no Android via SharedPreferences.

Instalação

Na pasta raiz do seu repositório frontend, rode:

npm install @capacitor/preferences
npx cap sync

Implementação: Composable de Autenticação Atualizado

Abra seu arquivo de autenticação reativa (src/composables/useAuth.ts ou equivalente) e atualize a persistência para utilizar Promises assíncronas do Preferences:

import { ref } from 'vue';
import { Preferences } from '@capacitor/preferences';

const token = ref<string | null>(null);

export function useAuth() {
  
  // Salvar token de forma nativa e assíncrona
  const saveToken = async (newToken: string) => {
    token.value = newToken;
    await Preferences.set({
      key: 'auth_token',
      value: newToken
    });
  };

  // Carregar token no boot do aplicativo
  const loadToken = async () => {
    const { value } = await Preferences.get({ key: 'auth_token' });
    token.value = value;
    return value;
  };

  // Remover token na saída (logout)
  const clearToken = async () => {
    token.value = null;
    await Preferences.remove({ key: 'auth_token' });
  };

  return {
    token,
    saveToken,
    loadToken,
    clearToken
  };
}

📍 3. Coleta de Localização com @capacitor/geolocation

Para adicionar um calculador automático de frete na tela de checkout, consultaremos a geolocalização física do aluno.

Instalação

npm install @capacitor/geolocation
npx cap sync

Configurando Permissões do Android

Para ter acesso aos sensores do celular, precisamos declarar as permissões no arquivo manifest do Android. Abra o arquivo android/app/src/main/AndroidManifest.xml e certifique-se de adicionar as seguintes tags dentro do nó <manifest>:

<!-- Permissões de Localização Fina (GPS) e Coarse (Rede) -->
<uses-permission android:name="android.permission.ACCESS_COARSE_LOCATION" />
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" />
<uses-feature android:name="android.hardware.location.gps" />

Implementação na Tela do Carrinho (Carrinho.vue)

No script do seu componente Vue do carrinho, adicione a lógica de captura de coordenadas:

<script setup lang="ts">
import { ref } from 'vue';
import { Geolocation } from '@capacitor/geolocation';

const latitude = ref<number | null>(null);
const longitude = ref<number | null>(null);
const freteCalculado = ref<number | null>(null);
const carregandoLocalizacao = ref(false);

const obterLocalizacaoFrete = async () => {
  try {
    carregandoLocalizacao.value = true;
    
    // Solicitar permissão nativa antes de ler o hardware
    const statusPermissao = await Geolocation.requestPermissions();
    
    if (statusPermissao.location === 'granted') {
      const coordenadas = await Geolocation.getCurrentPosition();
      latitude.value = coordenadas.coords.latitude;
      longitude.value = coordenadas.coords.longitude;
      
      // Simulação didática de frete baseado na latitude do usuário
      freteCalculado.value = Math.abs(coordenadas.coords.latitude) * 1.5;
    } else {
      alert("Permissão de localização negada pelo usuário.");
    }
  } catch (error) {
    console.error("Erro ao obter geolocalização:", error);
  } finally {
    carregandoLocalizacao.value = false;
  }
};
</script>

<template>
  <div class="carrinho-container">
    <h2>Resumo da Compra</h2>
    
    <div class="frete-secao">
      <button @click="obterLocalizacaoFrete" :disabled="carregandoLocalizacao">
        {{ carregandoLocalizacao ? 'Buscando GPS...' : '📍 Calcular Frete via GPS' }}
      </button>
      
      <div v-if="latitude && longitude" class="coordenadas-preview">
        <p>Lat: {{ latitude.toFixed(4) }} | Lng: {{ longitude.toFixed(4) }}</p>
        <p class="frete-valor">Valor do Frete: R$ {{ freteCalculado?.toFixed(2) }}</p>
      </div>
    </div>
  </div>
</template>

🎨 4. Layout Otimizado para Mobile First (Ergonomia)

Para que o aplicativo pareça nativo, devemos ajustar nossos estilos no arquivo CSS global (styles.css ou index.css). Adicione regras específicas utilizando Media Queries e propriedades que eliminam comportamento de browser clássico:

/* Impedir zoom indesejado ao dar duplo clique no celular */
html, body {
  touch-action: manipulation;
  user-select: none;
  -webkit-user-select: none;
  background-color: #0f172a; /* Slate 900 */
}

/* Área de toque mínima recomendada pelo Guia de Acessibilidade (48px x 48px) */
button, a, input, select {
  min-height: 48px;
  min-width: 48px;
  padding: 12px 16px;
}

/* Ajustes Responsivos para Telas de Smartphones */
@media (max-width: 640px) {
  /* Grid do catálogo passa para 1 ou 2 colunas no celular */
  .grid-produtos {
    grid-template-columns: repeat(2, 1fr) !important;
    gap: 8px !important;
    padding: 8px;
  }

  /* Ajustes ergonômicos no menu inferior fixo estilo app de celular */
  .navbar {
    position: fixed;
    bottom: 0;
    top: auto;
    left: 0;
    width: 100%;
    z-index: 1000;
    box-shadow: 0 -4px 10px rgba(0,0,0,0.3);
    border-radius: 16px 16px 0 0;
  }

  /* Margem inferior para o conteúdo não ficar escondido pela Navbar na parte inferior */
  main {
    margin-bottom: 80px;
  }
}

✅ Pré-Requisitos deste Módulo

Antes de prosseguir, confirme se:


🤔 Por que fizemos assim?


🔍 Checkpoint

  1. Persistência do Token: Faça login na aplicação nativa pelo emulador. Feche o aplicativo (force close) e abra-o novamente. A sessão deve ser mantida utilizando o token recuperado do Preferences.
  2. Validação de Coordenadas: Clique em “Calcular Frete via GPS” e verifique se o emulador exibe o pop-up nativo do Android solicitando permissão de localização. Aceite a permissão e verifique o frete calculado.

⚠️ Erros Comuns

Erro Causa Solução
Location permission not granted no console do emulador As permissões de localização foram omitidas no arquivo AndroidManifest.xml ou o usuário clicou em “Negar”. Verifique se as tags <uses-permission> foram inseridas no manifesto e reinicie o emulador limpando os dados de permissão do app nas configurações do Android.
O emulador não consegue obter as coordenadas de GPS O emulador de Android nativo inicia com o sensor de geolocalização inativo ou sem pontos de coordenadas mockadas. Vá no painel lateral do emulador (três pontos ...), clique na guia Location e informe uma coordenada de latitude e longitude de teste, clicando em Send.
A barra de navegação superior encobre a barra de status do celular Ausência de tratamento para as chamadas “Safe Areas” dos celulares com notch. Instale o plugin @capacitor/status-bar para gerenciar a cor de fundo e sobreposição da status bar, ou adicione padding-top: env(safe-area-inset-top) no seu arquivo de layout principal do app.

Voltar para o Sumário