Como Melhorar a Experiência do Usuário Utilizando API de Mapa

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes! Você receberá um email de confirmação. Somente depois de confirma-lo é que eu poderei lhe enviar os conteúdos semanais exclusivos. Os artigos em PDF são entregues somente para os inscritos na lista.

Email inválido.
Blog /Android /Como Melhorar a Experiência do Usuário Utilizando API de Mapa

Como Melhorar a Experiência do Usuário Utilizando API de Mapa

Vinícius Thiengo
(2839)
Go-ahead
"O método consciente de tentativa e erro é mais bem-sucedido que o planejamento de um gênio isolado."
Peter Skillman
Prototipagem Android
Capa do curso Prototipagem Profissional de Aplicativos
TítuloAndroid: Prototipagem Profissional de Aplicativos
CategoriasAndroid, Design, Protótipo
AutorVinícius Thiengo
Vídeo aulas186
Tempo15 horas
ExercíciosSim
CertificadoSim
Acessar Curso
Quer aprender a programar para Android? Acesse abaixo o curso gratuito no Blog.
Lendo
TítuloAprenda Domain-driven Design: Alinhando Arquitetura de Software e Estratégia de Negócios
CategoriaEngenharia de Software
Autor(es)Vlad Khononov
EditoraAlta Books
Edição
Ano2024
Páginas320
Conteúdo Exclusivo
Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba gratuitamente conteúdos Android sem precedentes!
Email inválido

Tudo bem?

Neste artigo nós vamos a uma nova versão de nosso Android "app framework" para YouTubers.

Quando digo "nova versão", na verdade estou dizendo:

Vamos configurar um novo canal no aplicativo.

Canal que vai exigir mudanças "severas" ao menos na interface com o usuário.

Vamos também adicionar ao app uma experiência de usuário mais tangível.

Inserindo nos códigos do projeto parte das APIs de Mapas Android de Alta Qualidade:

Animação do app android YouTube com a abertura de um circuito da Formula 1

Se você já está ciente sobre este projeto de aplicativo framework e já tem planos com ele. Ideias de monetização ou apenas de ampliação de portfólio.

Então é importante que você leia este artigo até o final.

Pois iremos discutir alguns pontos que acabei não levando em consideração quando desenvolvemos por completo todo esse projeto de app framework.

Pontos que afetam diretamente a produção. Produção na criação de varias versões do aplicativo.

É isso.

Antes de continuar, saiba que a versão PDF deste artigo está disponível somente aos inscritos da lista de e-mails do Blog.

Se você ainda não faz parte desta lista. Então não deixe de se inscrever para ter acesso a este e a outros conteúdos completos do Blog.

A seguir os tópicos que estaremos abordando neste artigo:

Cheguei somente agora no projeto

Se você ainda não conhece o projeto Android de app framework que desenvolvemos aqui no Blog.

Então é possível que você fique um pouco perdido no conteúdo deste artigo.

Sendo assim eu recomendo que você acesse primeiro este projeto a partir do artigo a seguir:

Lembrando que o projeto está todo finalizado.

Então, dependendo de sua motivação, é possível termina-lo em pouco tempo.

Toda a série deste projeto está bem dividida:

  • Primeiro nós vamos ter uma visão Estratégica de toda a ideia de aplicativo;
  • Logo depois nós vamos à parte Tática, aos códigos.

É isso.

A partir daqui vamos já criar mais uma versão de app para canais no YouTube... partindo de nosso aplicativo framework.

Já conheço tudo. Quero o novo!

Se você já está ok com este nosso projeto de app framework. Já o conhece "tudo" e não tem dificuldades em criar uma nova versão dele com um novo canal.

Neste caso é bem provável que você queira ir direto para as partes inéditas deste conteúdo, certo?

Sendo assim, siga o roteiro:

Parte inicial:

Leia tudo até o fim da seção Não banalize o valor (R$) de seu trabalho e esforço.

Parte intermediária:

Leia todo o conteúdo sobre "background drawable em SVG" presente na seção Background.

Parte final:

Então volte a ler somente a partir da seção Circuitos.

Com isso você estará acessando os trechos inéditos sobre este app framework.

Se quiser ir direto aos comentários da implementação da API de Mapas de Alta Qualidade, então acesse a seção Experiência realista.

Portfólio de apresentação

Se você já conhece o projeto e está prestes a atender a algum canal ou até mesmo já enviando contatos.

Então eu acho completamente aceitável que você utilize qualquer um dos três projetos Android que temos aqui no Blog e que já fazem uso deste framework que desenvolvemos juntos:

Apps Android construídos com o framework android para canais do YouTube

Mas espero que sua narrativa seja honesta com o cliente.

Ou seja:

Informe que são três exemplos de canais YouTube que estão configurados com o framework que você está oferecendo ao YouTuber.

Com o paragrafo acima você é 100% honesto com o possível cliente.

Pois em nenhum momento você informou que são clientes reais (não são) e que você foi o desenvolvedor(a) dos projetos. Apesar de ser completamente possível você fazer até melhor.

Coloquei está seção, pois pode ser que você já queira monetizar esse projeto de aplicativo framework. Porém ainda não tem em mãos algum já finalizado.

Então esses três projetos já prontos vão certamente facilitar a negociação com possíveis YouTubers clientes. Pois com o um portfólio disponível tudo fica mais claro na mesa de negociação.

Canal que será utilizado

Desta vez optei por um canal que é de uma marca muito popular e é também um canal extremamente ativo no YouTube.

Ou seja, libera inúmeros vídeos por dia.

Vamos seguir com o canal da Formula 1.

Canal que na verdade tem conteúdos de:

  • Formula 1;
  • Formula 2 (está daqui é realmente divertida, alta competitividade);
  • e Formula 3.

Home page do canal YouTube da Formula 1

Todos os dias têm vídeos novos e em alguns dias têm até seis novos vídeos. Isso mesmo, seis vídeos em um dia.

Muitos desses vídeos batem mais de 100k de visualizações em menos de 24 horas. Ou seja, o público do canal é bem ativo

A configuração prudente aqui de acesso aos servidores do YouTube (invocada em WorkManager) seria pelo menos de 4 em 4 horas.

Este é um canal de mais de 5 milhões de seguidores e que está realmente preocupado em aumentar ainda mais o número de subscribers.

Isso é um aspecto muito importante caso você queira monetizar esse projeto de app com algum canal YouTube...

...seguindo a terceira estratégia que mostro no último artigo deste aplicativo framework.

Não banalize o valor (R$) de seu trabalho e esforço

Quando eu iniciei mais está versão de projeto com o canal da Formula 1...

... eu queria mesmo era poder mostrar as possibilidades de modificações em app ainda maiores do que as que realizamos na versão de aplicativo para o canal Emagrecer de Vez (artigo 14 do projeto framework).

Queria também mostrar como é fácil adicionar uma funcionalidade de "realidade mais tangível". Isso com poucas linhas de código quando utilizando as APIs Android de Mapas de Alta Qualidade.

Imaginei que seria algo que rapidamente ficaria pronto, até porque eu estaria em uma versão de app com menos telas do que as versões anteriores.

São apenas seis telas aqui.

Voilà la.

Me enganei. 😅

Algo que eu não tinha percebido é que a parte gráfica, imagens, pode ser bem crítica de conseguir com facilidade se o domínio de problema do canal YouTube do app for bem "exótico".

Ok, Thiengo. Mas automobilismo não é nem de perto algo exótico.

Correto.

Porém até o momento que você precisa dos gráficos dos circuitos (as pistas de corrida) da temporada 2020 da Formula 1...

... e descobre que a única fonte que fornece o desenho de todos os circuitos com um design padrão, fornece em formato rasterizado quando na verdade você precisa de formato vetorial (SVG).

Então você descobre que para cada circuito, em imagem rasterizada, você terá que aplicar um bocado de trabalho manual que inclui até mesmo otimização de arquivo SVG.

Em resumo:

Um único e simples projeto, que deveria ficar pronto em poucas horas... devido ao uso de um framework já testado.

Esse projeto levou mais de um dia por causa da parte gráfica.

Pois o preenchimento de informações (textos) especificas de canal em app é algo trivial.

Mas trabalhar imagens complexas quando você não tem muita expertise no assunto ou não tem um designer do seu lado... ao menos essa parte não é nada simples.

Então, não banalize seu trabalho e esforço somente porque você tem um framework em mãos.

Aqui eu não vou lhe dar valores (R$) específicos, pois ainda não fiz testes de vendas do app em mercado. Pretendo fazer um artigo somente sobre isso.

Mas achei prudente adicionar essa parte aqui no conteúdo, principalmente para aumentar a sua confiança e assim você cobrar o que entender ser justo.

Como será a partir daqui

Como eu sei que você pode sim querer fazer tudo passo a passo.

Então vamos praticamente seguir o mesmo roteiro utilizado no penúltimo artigo desta série de app framework.

Ou seja, vamos em artigo apresentar cada um dos passos que são necessários para o aplicativo final ser um aplicativo de notificações YouTube. Notificações de novos vídeos do canal da Formula 1.

E diferente do que fizemos no artigo 14 deste projeto. Aqui teremos algumas modificações a mais no framework. Deixando claro ao desenvolvedor que as possibilidades são infinitas.

É isso.

Vamos ao projeto.

Download e inicialização do projeto

Vamos iniciar baixando o .zip do projeto no GitHub e depois abri-lo no Android Studio.

Baixando do GitHub

Acesse o repositório GitHub do projeto Android em:

https://github.com/viniciusthiengo/aplicativo-android-para-canais-do-youtube.

Logo depois:

  • Clique em "Code";
  • Então clique em "Download ZIP".

Agora, na área de aplicativos em seu ambiente de desenvolvimento, descompacte o ZIP.

Abrindo no Android Studio

Com o Android Studio aberto, na tela inicial:

  • Clique em "Open an existing Android Studio project";
  • Selecione o projeto que você acabou de baixar e descompactar. Projeto de nome "aplicativo-android-para-canais-do-youtube-master".

Aguarde até o final do carregamento em IDE.

Erro com o Git

Assim que o projeto for carregado por completo, provavelmente aparecerá um alerta de erro como a seguir:

Alerta de erro Git

Não se preocupe com erro de Git. Pois este é um inteiro novo projeto que vai partir de nosso app framework.

Sendo assim, siga os passos:

  • No pop-up de alerta de erro clique em "Configure";
  • Na caixa de diálogo que se abrir selecione (clique) a única opção na lista "Version Control";
  • Com a opção selecionada, agora clique no sinal de menos (remover) que há no fundo da lista;

Botão de remoção do vinculo com o Git

  • Agora clique em "Apply";
  • Por fim clique em "Ok".

Pronto.

Problema de configuração Git resolvido.

Renomeando todo o projeto

Antes de entrar nos trechos mais delicados de renomeação de projeto. Vamos primeiro renomear a pasta do projeto na área de aplicativos de seu sistema operacional.

Primeiro feche o projeto e remova ele da lista de apps do Android Studio IDE. 

Removendo app da lista de apps do Android Studio IDE

Logo depois coloque como rótulo da pasta do projeto o nome "CanalFormula1":

Pasta CanalFormula1

Por fim, realize todo o processo de migração de projeto no Android Studio. Acessando ele via "Open an existing Android Studio project".

Ignore os erros que aparecerem.

Pois iremos agora iniciar as renomeações dentro de projeto e aos poucos, de acordo com as atualizações, eles vão sendo resolvidos.

Root name

Com o aplicativo aberto no IDE e com a visualização de pacotes em modo "Android".

Neste contexto acesse o arquivo settings.gradle:

  • Em rootproject.name coloque o valor "Canal Formula 1";
  • Por fim clique em "Sync Now" e aguarde a sincronização do projeto.

Rótulos de pacotes

Para os pacotes, primeiro:

  • Coloque a visualização de pacotes em modo "Project";
  • Expanda tudo até chegar ao primeiro filho direto do pacote "java";
  • Selecione esse pacote raiz (o primeiro filho direto do pacote "java");
  • Clique em "Show Options Menu" - aquela engrenagem ⚙ que fica ao topo direito da estrutura de pacotes;

Botão Show Options Menu

  • Então clique (desmarque) a opção "Compact Middle Packages".

Você vai perceber que a estrutura de pacotes será dividida por completo.

Assim podemos partir para os pacotes raizes:

  • Clique com o botão direito do mouse no pacote "canalviniciusthiengo";
  • Acesse "Refactor" e clique em "Rename";
  • No pop-up que se abrir clique em "Rename package";
  • No novo pop-up que se abrir coloque em "Rename package (...)" o valor "youtubechannel";
  • Então clique em "Refactor" e aguarde o processamento.

Muito bom.

Agora o pacote "thiengo":

  • Clique com o botão direito do mouse no pacote "thiengo";
  • Acesse "Refactor" e clique em "Rename";
  • No pop-up que se abrir clique em "Rename package";
  • No novo pop-up que se abrir coloque em "Rename package (...)" o valor "formula1";
  • Então clique em "Refactor" e aguarde o processamento.

Excelente.

Ainda temos um pacote "br" perdido em projeto. Pois o site oficial do canal não tem o ".br". O domínio é apenas ".com": Formula1.com.

Sendo assim:

  • Miniminize o pacote "youtubechannel";
  • Clique neste pacote e arraste-o para dentro do pacote "com". Ele ficará como um pacote irmão de "br". O pacote "br" ficará vazio;
  • No pop-up que se abrir, mantenha a opção "Move package (...)" marcada e clique em "Ok";
  • No novo pop-up de "Warning" clique em "Yes";
  • Agora, na caixa de diálogo de configuração de migração de pacote, clique em "Refactor";
  • A caixa "Find" será acionada. Então clique em "Do Refactor" e aguarde o processamento;
  • Por fim, clique com o botão direito do mouse no pacote "br" (agora vazio) e clique em "Delete";
  • No pop-up que aparecer, apenas clique em "Delete".

Quase tudo finalizado!

Atualizando o Gradle

Abra o Gradle Nível de Aplicativo, ou build.gradle (Module: app).

No bloco defaultConfig modifique o valor de applicationId para formula1.com.youtubechannel como a seguir:

...
android {
...

defaultConfig {
applicationId "formula1.com.youtubechannel"
...
}
...
}
...

 

Sincronize o projeto.

Reconstrua o projeto

Com o projeto ainda aberto em IDE:

  • Acesse o menu de topo e clique em "Build";
  • Logo depois clique em "Clean Project";
  • Ao final do processamento do clean acesse novamente "Build" e clique em "Rebuild Project".

Ainda no menu de topo:

  • Clique em "File";
  • Logo depois clique em "Invalidate Caches / Restart";
  • No pop-up que se abrir clique em "Invalidate and Restart".

Ao final do processamento feche o projeto e remova ele da lista de apps da tela inicial do Android Studio.

Deletando arquivo

Entre na pasta do projeto e delete o folder oculto .idea.

Folder oculto .idea

É possível que dentro de CanalFormula1 ainda tenha arquivos e folders com o nome antigo do projeto.

Isso depende da versão do Android Studio que você estiver usando.

Se houverem arquivos e folders com o nome antigo do projeto, já neste ponto de renomeação de tudo. Então delete-os.

Agora acesse o Android Studio e abra o projeto novamente via "Open an existing Android Studio project".

Nova estrutura física

Agora temos um novo projeto com um nome de pacote exclusivo, exatamente como solicita a Google Play Store.

Nossa configuração final de pacotes renomeados ficou:

Estrutura física do projeto sem compactação de pacotes

Vamos compactar os pacotes em IDE:

  • Clicando em "Show Options Menus";
  • Depois clicando em "Compact Middle Packages".

Temos:

Estrutura física do projeto com compactação de pacotes

Neste ponto o projeto não mais deverá estar apresentando erros.

Gerando chaves e IDs de API

Agora vamos às chaves de APIs e IDs que teremos que configurar em projeto para que seja possível:

  • Obter dados do canal direto dos servidores do YouTube;
  • e Enviar notificações push.

Dica importante:

Crie um novo Gmail para cada nova versão do app framework que você estiver desenvolvendo.

Mesmo que o YouTuber cliente já tenha um Gmail.

Crie um exclusivo para gerar todas as chaves e IDs do projeto e então entregue esse e-mail e dados de acesso a ele.

Identificador do canal

A URL do canal no YouTube é a seguinte: https://www.youtube.com/user/Formula1.

Assim:

  • Entre em YouTube Channel ID;
  • No campo "Youtube Channel URL, Video URL or username" coloque a URL do canal (que foi apresentada anteriormente);
  • Clique em "Get YouTube Channel ID";
  • Em "YOUTUBE CHANNEL ID, INFO & STATISTICS" você terá também o ID do canal:
  • Copie o ID "UCB_qr75-ydFVKSF9Dmo6izg".

Classe de configuração

Em projeto, no Android Studio, acesse a classe de configuração YouTubeConfig.

Então atualize a constante CHANNEL_ID como a seguir:

...
abstract class Channel {
companion object {
...
const val CHANNEL_ID = "UCB_qr75-ydFVKSF9Dmo6izg"

...
}
}
...

OneSignal

Para gerar a chave e o ID do Google Firebase e também o ID do OneSignal.

Para isso siga o passo a passo em Dados que são pré-requisito da parte 10 do projeto.

Siga o passo a passo até ao fim desta seção da Parte 10.

Porque ao final dela você terá em mãos:

Classe de configuração

Agora acesse a classe de configuração OneSignalConfig.

Atualize as constantes SERVER_KEY e SENDER_ID da classe interna Firebase e também a constante ID da classe interna App.

Atualize todas essas constantes com a chave e os IDs gerados na seção anterior:

...
abstract class Firebase {
companion object CloudMessage {
...
const val SERVER_KEY = "SDHsdNA..."
const val SENDER_ID = "okjuJIS..."
}
}

abstract class App {
companion object {
...
const val ID = "IgthhS..."
}
}
...

Gradle

Ainda é preciso atualizar o Gradle Nível de Aplicativo, ou build.gradle (Module: app), com o ID OneSignal.

Sendo assim, em manifestPlaceholders, coloque o ID de projeto OneSignal em onesignal_app_id como a seguir:

...
android {
...

defaultConfig {
...

manifestPlaceholders = [
onesignal_app_id: 'IgthhS...',
...
]
}
...
}
...

 

Sincronize o projeto.

YouTube Data API

Aqui nós vamos precisar somente da chave de desenvolvedor Google.

Para isso siga o passo a passo em Geração de chave no Google Console da parte 11 do projeto.

Ao final desse passo a passo você terá em mãos uma Google Dev Key.

Classe de configuração

Agora acesse a classe de configuração YouTubeConfig.

Então atualize a constante GOOGLE_DEV com a chave de desenvolvedor Google que você gerou na seção anterior:

...
abstract class Key {
companion object {
...
const val GOOGLE_DEV = "GtHYtydfO..."
}
}
...

 

Assim, com as configurações de chaves e IDs finalizadas, nós estamos prontos para atualizarmos os dados de canal, e parte da estrutura de código, no projeto de aplicativo Android.

Estrutura global

Nesta seção estaremos atualizando os pontos de projeto que atendem à todas as telas ou que não têm vinculo direto com somente uma tela.

Um ponto importante a ressaltar aqui é:

As propriedades que não forem citadas nas posteriores atualizações do app neste conteúdo...

... essas propriedades continuarão com os exatos mesmos valores que já tinham quando o projeto foi baixado do GitHub.

Quando for possível remover qualquer entidade do projeto, eu vou deixar explícito em texto.

Então é isso.

Vamos às atualizações.

Ícones de abertura

Os nossos ícones de abertura de aplicativo serão os seguintes:

Ícone retangular:

Ícone retangular de abertura de app

Ícone circular:

Ícone circular de abertura de app

Realize o download de cada uma das versões rasterizadas de ícone e coloque-as em seus respectivos folders mipmap em projeto.

Splash Screen

Para a tela de abertura:

Splash Screen do app framework Android

Vamos mudar as imagens e a cor de fundo.

Imagens

Imagens vetoriais não funcionam no modelo de tela de abertura que estamos utilizando. Então vamos manter o uso de imagens rasterizadas neste ponto do projeto.

Segue:

Imagem central: 

Imagem central Splash Screen

Imagem de Powered by (desenvolvido por):

Imagem de Powered by da Splash Screen

Realize o download das imagens e coloque-as em seus respectivos folders drawable.

Cor de fundo

Vamos utilizar para cor de background a mesma cor da StatusBar (preto, #000000).

Assim, em /res/launcher_screen.xml, atualize a cor de background como a seguir:

...
<item android:drawable="@color/colorStatusBar" />
...

 

Atualizações de Splash Screen finalizadas.

Nome do app

No arquivo /res/values/strings.xml atualize o nome do aplicativo como a seguir:

...
<string name="app_name">
Canal Formula 1
</string>
...

Primary e accent colors

No arquivo /res/values/colors.xml atualize as cores das propriedades a seguir:

...
<color name="colorPrimary">#E20600</color>
<color name="colorPrimaryDark">#E20600</color>
<color name="colorAccent">#FFFFFF</color>
...

Background de conteúdo

Ainda em /res/values/colors.xml atualize a propriedade de cor de background de conteúdo, atualize como a seguir:

...
<color name="colorScreenBackground">#E20600</color>
...

Família de fontes

No projeto original nós temos, para títulos, uma família de fontes que é diferente da família de fontes padrão Android, Roboto.

A fonte "Quicksand Variable":

Título com a família de fonte Quicksand Variable

Para o canal Formula 1 vamos utilizar uma fonte que "casa" mais com um conteúdo de automobilismo:

Título com a família de fonte Racing Sans One

Vamos utilizar a fonte "Racing Sans One".

Faça o download dela:

Depois coloque-a no folder /res/font do projeto.

Essa fonte foi baixada no Google Fonts.

Tema de título

Agora precisamos atualizar o tema de títulos de telas para utilizar a nova família de fontes.

Em /res/values/styles.xml atualize o valor de android:fontFamily no tema AppTheme.Title como a seguir:

...
<style name="AppTheme.Title">
...
<item name="android:fontFamily">@font/racing_sans_one_regular</item>
...
</style>
...

Topo

Para o topo:

Design de topo do app

Temos novas cores, rótulos, background e imagem.

Cores

No arquivo estático /res/values/colors.xml atualize as cores das propriedades a seguir:

...
<!-- Topo -->
<color name="colorStatusBar">#000000</color>
<color name="colorTopIcon">#FFFFFF</color>
<color name="colorTopText">#FFFFFF</color>
...

Você vai perceber que estaremos trabalhando em maioria com tons de vermelho.

Isso, pois o vermelho é uma das principais cores do site oficial da Formula 1. Vermelho, preto e branco.

Rótulos

No arquivo XML /res/values/strings.xml atualize as propriedades a seguir com os valores definidos:

...
<string name="channel_name">
FORMULA 1
</string>
<string name="channel_desc">
Mantenha-se atualizado com as últimas notícias e
conteúdos sobre a Fórmula 1
</string>
<string name="channel_thumb_content_desc">
Thumb do canal YouTube Formula 1.
</string>
<string name="channel_toast_alert">
É preciso ter o aplicativo do YouTube instalado
para acessar o canal da Formula 1.
</string>
...

Família de fontes

Com a nova família de fontes para títulos já definida em projeto. Podemos então atualizar o TextView tv_channel_name do layout de topo.

Em /res/layout/top_signature.xml atualize a família de fontes do TextView de título como a seguir:

...
<TextView
android:id="@+id/tv_channel_name"
...
android:fontFamily="@font/racing_sans_one_regular"
... />
...

Logo

Agora temos a logo do canal:

Logo do canal

Imagem que deverá ter as suas cinco versões respeitando os 65dp x 65dp:

Realize o download das imagens e coloque-as em seus respectivos folders drawable.

Ainda sobre a "imagem logo" de topo. Precisamos adicionar duas configurações ao CardView de top_signature.xml para que a logo fique como esperado em tela:

Imagem logo de topo

Em /res/layout/top_signature.xml atualize o único CardView do layout como a seguir:

...
<androidx.cardview.widget.CardView
android:id="@+id/cv_channel_logo"
...
android:background="@android:color/transparent"
android:backgroundTint="@android:color/transparent"
app:cardElevation="0dp">
...

 

Somente os atributos backgroundbackgroundTint e cardElevation devem ser atualizados no CardView.

Background

Você já deve ter notado que nosso background de topo também está bem diferente do então comum background de waves, ondas.

Agora temos listras:

Background de topo do app

Sendo assim, nosso /res/drawable/bg_top.xml tem então o seguinte novo código fonte:

<vector
xmlns:android="http://schemas.android.com/apk/res/android"
android:width="200dp"
android:height="100dp"
android:viewportWidth="1600"
android:viewportHeight="800">

<path
android:fillColor="#000000"
android:pathData="M0,0h1600v800h-1600z" />
<path
android:fillColor="#2d0100"
android:pathData="M1600,160l-1600,300l0,-110l1600,-300z" />
<path
android:fillColor="#5a0200"
android:pathData="M1600,260l-1600,300l0,-110l1600,-300z" />
<path
android:fillColor="#880400"
android:pathData="M1600,360l-1600,300l0,-110l1600,-300z" />
<path
android:fillColor="#b50500"
android:pathData="M1600,460l-1600,300l0,-110l1600,-300z" />
<path
android:fillColor="#e20600"
android:pathData="M1600,800l-1600,0l0,-50l1600,-300z" />
</vector>

Sim. Diferente do arquivo de waves, onde as ondas eram externas e referenciadas uma a uma. Aqui temos toda uma estrutura vetorial desenvolvida diretamente em arquivo.

Acredite: não precisei criar essa nova estrutura. Digo, não na "unha".

O background de topo anterior foi gerado de maneira trivial no seguinte site de criação de backgrounds em SVG:

SVG Background.

Há inúmeros outros sites para a geração não somente de backgrounds em SVG, mas também de qualquer tipo de drawable.

Um outro também popular é o:

Hero Patterns - A collection of repeatable SVG background patterns for you to use on your web projects.

Uma opinião sincera:

Caso você ainda não tenha bons backgrounds de topo para seus aplicativos.

Então não deixe de salvar em sua caixa de ferramentas Android ao menos os dois sites anteriores de background SVG.

Pois esses backgrounds dão mais modernidade ao design de qualquer aplicativo... com triviais linhas de código que basicamente temos que "copiar e colar".

Thiengo, ok. E o problema de imagens vetoriais com mais de 200dp x 200dp?

Excelente colocação.

Confesso que foi a primeira coisa que pensei assim que trabalhei com backgrounds em formato vetorial.

Como resultado obtive:

Nenhum problema de sobrecarga de memória ou demora na renderização do layout em tela.

Tendo em mente que todos os arquivos vetoriais de background utilizados até o momento em projeto. Todos esses são arquivos pequenos em bytes.

Sendo assim... mesmo com a ressalva na documentação oficial sobre vetoriais acima dos 200dp x 200dp...

... mesmo com isso, vamos prosseguir com nossa configuração atual em app, pois está funcionando "redondo".

Por fim, você pode seguramente deletar de projeto todos os arquivos vetoriais de waves que não são mais utilizados.

Para o bottom menu principal:

Bottom menu principal do app

Temos novas cores, rótulos e ícones vetoriais.

Cores

No arquivo estático /res/values/colors.xml atualize as cores das propriedades a seguir:

...
<!-- Bottom Menu -->
<color name="colorMenuSeparatorLine">#E20600</color>
<color name="colorMenuBackground">@android:color/transparent</color>
<color name="colorMenuItemBackground">@android:color/transparent</color>
<color name="colorMenuItemSelected">#FFFFFF</color>
<color name="colorMenuItemNotSelected">#A00000</color>
...

 

É isso mesmo que você está vendo, @android:color/transparent.

Quando for necessário para conseguirmos ao final do projeto uma versão de app framework que melhor representa o canal YouTube cliente...

... vamos sim utilizar a "cor" transparente.

Dessa forma não precisaremos, por exemplo, ter que atualizar:

  • Ou código dinâmico de lógica (Kotlin);
  • Ou o código estático de layout (XML).

Basta colocar a visualização com a "cor" transparente que assim vai aparentar que o componente visual "não esta presente em layout".

Rótulos

Em /res/values/strings.xml atualize as propriedades a seguir com os respectivos valores definidos:

...
<!-- Menu -->
<string name="item_menu_last_video">
Vídeo
</string>
<string name="item_menu_play_lists">
PlayLists
</string>
<string name="item_menu_social_networks">
Redes
</string>
<string name="item_menu_circuits">
Circuitos
</string>
<string name="item_menu_partners">
Parceiros
</string>
<string name="item_menu_store">
Loja
</string>
...

 

Todas as propriedades estáticas de rótulos de menu que não estão na definição anterior. Essas propriedades podem ser removidas do arquivo de Strings.

Lembrando que quando alguma entidade puder ser removida, eu vou de maneira explícita lhe informar.

Ícones

Agora os ícones vetoriais de menu. Já lhe adianto que todos foram alterados.

Assim temos:

Para a opção Vídeo, ic_helmet.xml:

Ícone vetorial da opção Vídeo

Para a opção Redes, ic_social_networks.xml:

Ícone vetorial da opção Redes

Para a opção PlayLists, ic_play_lists.xml:

Ícone vetorial da opção PlayLists

Para a opção Circuitos (pistas de corrida), ic_circuits.xml:

Ícone vetorial da opção Circuitos

Para a opção Parceiros, ic_partners.xml:

Ícone vetorial da opção Parceiros

Para a opção Loja, ic_store.xml:

Ícone vetorial da opção Loja

Realize o download de cada um dos ícones e coloque-os no folder /res/drawable.

Outro ponto onde também é possível remover recursos:

Os ícones de menu não mais utilizados. Todos esses podem ser removidos.

Identificadores de opções

Outro arquivo estático que estará passando por atualizações é o arquivo de IDs.

Mais precisamente o arquivo /res/values/ids.xml.

Este ficará exatamente como a seguir:

<?xml version="1.0" encoding="utf-8"?>
<resources>
<item type="id" name="last_video" />
<item type="id" name="play_lists" />
<item type="id" name="social_networks" />
<item type="id" name="circuits" />
<item type="id" name="partners" />
<item type="id" name="store" />
</resources>

 

Nós poderíamos ter mantido a configuração anterior de IDs. O problema é que os rótulos anteriores, alguns deles, não representavam mais a responsabilidade da entidade ID utilizada em código.

Exemplo:

Ou o ID antigo courses seria utilizado no lugar do ID partners, somente para reaproveitarmos a definição de ID e não ser necessário criar um novo.

Ou o ID courses ficaria em código sem ser utilizado. Ou seja, mesmo assim criaríamos o ID partners.

O que foi informado acima é, nada mais nada menos, que o princípio de um código mal escrito.

Se preocupe, mesmo que seja um detalhe pequeno, em utilizar rótulos que indicam o que a entidade faz. Mesmo sendo algo simples: um ID.

Dados fixos

Por fim os dados fixos que preencherão a lista de itens que esta vinculada ao RecyclerView do menu.

Acesse a classe persistência MenuItemsData e coloque no método getItems() a seguinte nova configuração de dados:

...
fun getItems( res: Resources )
= listOf(
MenuItem(
id = R.id.last_video,
label = res.getString( R.string.item_menu_last_video ),
icon = R.drawable.ic_helmet,
isSelected = MenuItemStatus.SELECTED
),
MenuItem(
id = R.id.play_lists,
label = res.getString( R.string.item_menu_play_lists ),
icon = R.drawable.ic_play_lists
),
MenuItem(
id = R.id.social_networks,
label = res.getString( R.string.item_menu_social_networks ),
icon = R.drawable.ic_social_networks
),
MenuItem(
id = R.id.circuits,
label = res.getString( R.string.item_menu_circuits ),
icon = R.drawable.ic_circuits
),
MenuItem(
id = R.id.partners,
label = res.getString( R.string.item_menu_partners ),
icon = R.drawable.ic_partners
),
MenuItem(
id = R.id.store,
label = res.getString( R.string.item_menu_store ),
icon = R.drawable.ic_store
)
)
...

 

Pronto.

Podemos ir às telas de projeto.

Atualizações por tela

Aqui vamos às atualizações específicas de cada uma das telas do canal que teremos em app.

Desta vez não temos tanta "coincidência" como tivemos em Nós Temos Um Framework Em Mãos - YouTuber Android App - Parte 14 quando colocamos em projeto de app um canal que tinha as exatas mesmas telas que a versão inicial do framework.

Desta vez estaremos adicionando algumas telas e todos os recursos necessários a elas.

Então, vamos aos códigos.

Último vídeo

A tela de "último vídeo" disponibilizado em canal terá cores, rótulo de título, ícones e dados fixos diferentes da versão inicial de framework:

Tela de último vídeo

Cores

No arquivo XML /res/values/colors.xml atualize as propriedades a seguir com os valores definidos:

...
<color name="colorContentTitle">#FFFFFF</color>
<color name="colorContentText">#FFFFFF</color>

<!-- Video player -->
<color name="colorVideoPlayerBackground">#000000</color>
<color name="colorVideoPlayerForeground">#BB000000</color>
<color name="colorVideoPlayerIcon">#FFFFFF</color>

<!-- YouTube block -->
<color name="colorVideoPlayerYBBackground">#D20000</color>
<color name="colorVideoPlayerYBText">#FFFFFF</color>
<color name="colorVideoPlayerYBIcon">#940000</color>

<!-- Comment block -->
<color name="colorVideoPlayerCBBackground">#D20000</color>
<color name="colorVideoPlayerCBText">#FFFFFF</color>
<color name="colorVideoPlayerCBIcon">#940000</color>
...

Rótulo de título

Para os rótulos da tela de último vídeo teremos atualização somente no rótulo de título.

Assim, em /res/values/strings.xml, atualize a propriedade a seguir:

...
<string name="last_video_content_title">
Assista agora
</string>
...

Ícones

Se você reparar bem nós temos dois novos ícones nas pequenas caixas abaixo do player de vídeo:

Barras abaixo do player do vídeo

Na verdade um desses dois ícones já está em projeto. É o mesmo ícone YouTube à direita no topo do app:

Ícone da logo do YouTube, ic_youtube_logo.xml:

Ícone vetorial da logo do YouTube

E tem o novo ícone de comentário:

Ícone de "deixe o seu comentário", ic_comment_color.xml:

Ícone vetorial de Comentário

Este último ícone ainda não está presente em projeto.

Sendo assim, por segurança, realize o download de ambos os ícones e coloque-os no folder drawable.

Dados fixos

Na classe persistência LastVideoData coloque a seguinte nova definição de dados iniciais:

...
fun getInitialVideo()
= LastVideo(
uid = "yWfUg5ja90k",
title = "Top 10 Moments of Pit Lane Drama",
description = "The quest to shave off precious " +
"milliseconds in the pits is always a " +
"dangerous game. Get it right, and you " +
"might prosper. Get it wrong, and, well, " +
"here are ten examples..."
)
.apply {
thumbUrl = ""
}
...

 

É isso mesmo que você notou: os dados são todos em inglês.

Aparentemente ao menos no canal YouTube da Formula 1 é sempre assim, tudo em inglês.

Agora podemos ir para a configuração de outra tela.

Redes

A tela de Redes sociais e sites disponibilizados em canal terá cores, rótulo e dados fixos diferentes da versão inicial de framework:

Tela de Redes sociais e sites

Cores

No arquivo estático /res/values/colors.xml atualize as propriedades a seguir com os valores definidos:

...
<!-- Item de lista -->
<color name="colorListItemBackground_RippleStart">#D20000</color>
<color name="colorListItemBackground_RippleEnd">@android:color/transparent</color>
<color name="colorListItemBackgroundInfoSide">#940000</color>
<color name="colorListItemStroke">#E20600</color>
<color name="colorListItemText">#FFFFFF</color>
<color name="colorListItemLinealIcon">#940000</color>
<color name="colorListItemNoDataIcon">#940000</color>
...

 

Note que adicionamos duas novas propriedades de cor:

  • colorListItemBackgroundInfoSide;
  • colorListItemLinealIcon.

A propriedade colorListItemBackgroundInfoSide é para uma parte do layout de itens de circuito, pista de corrida, que vamos desenvolver.

Esse layout será diferente do layout comum para itens já disponível em framework:

Layout de item de lista da tela de circuitos

A propriedade colorListItemLinealIcon é para manter os ícones de itens de listas exatamente com a mesma cor:

Ícones de item de lista

Sendo assim, já para a propriedade colorListItemLinealIcon, ainda falta atualizar o estilo de ícones de lista para que todos tenham a mesma cor.

Em /res/values/styles.xml atualize o tema AppTheme.ListItemIcon adicionando o atributo android:tint exatamente como a seguir:

...
<style name="AppTheme.ListItemIcon">
...
<item name="android:tint">@color/colorListItemLinealIcon</item>
</style>
...

Rótulo de título

Aqui também teremos um novo rótulo de título.

Em /res/values/strings.xml atualize a propriedade a seguir com o valor definido:

...
<string name="social_networks_content_title">
Acompanhe a Formula 1 em todas as redes
</string>
...

Ícones

Aqui vamos utilizar ícones que realmente são a logo da rede ou site em lista.

Para o Blog, ic_f1_blog_color.xml:

Ícone da opção Blog

Para a conta Facebook, ic_facebook_color.xml:

Ícone da opção Facebook

Para a conta Twitter, ic_twitter_color.xml:

Ícone da opção Twitter

Para a conta Instagram, ic_instagram_color.xml:

Ícone da opção Instagram

Para a conta YouTube, ic_youtube_color.xml:

Ícone da opção YouTube

Realize o download dos ícones e coloque-os no folder /res/drawable.

Dados fixos

Na classe persistência SocialNetworksData coloque a seguinte nova definição de dados no método getNetworks():

...
fun getNetworks()
= listOf(
SocialNetwork(
network = "Site oficial",
accountName = "Formula1.com",
webUri = "https://www.formula1.com/",
logo = R.drawable.ic_f1_blog_color
),
SocialNetwork(
network = "Facebook",
accountName = "/Formula1",
webUri = "https://www.facebook.com/Formula1",
logo = R.drawable.ic_facebook_color
),
SocialNetwork(
network = "Twitter",
accountName = "/F1",
webUri = "https://twitter.com/f1",
logo = R.drawable.ic_twitter_color
),
SocialNetwork(
network = "Instagram",
accountName = "/F1",
webUri = "https://www.instagram.com/f1/",
logo = R.drawable.ic_instagram_color
),
SocialNetwork(
network = "YouTube",
accountName = "/F1",
webUri = "https://www.youtube.com/F1",
logo = R.drawable.ic_youtube_color
)
)
...

Classe de domínio

Como não mais estaremos utilizando a propriedade appUri da classe SocialNetwork. Então podemos remover essa propriedade e o método getAppUri() desta classe.

Ela ficará como a seguir:

...
class SocialNetwork(
val network: String,
private val accountName: String,
private val webUri: String,
private val logo: Int ) : ListItem {

override fun getMainText()
= String.format(
"%s: %s",
network,
accountName
)

override fun getWebUri()
= Uri.parse( webUri )

override fun getIcon()
= logo
}

 

Vamos então à próxima tela.

PlayLists

Agora a tela de PlayLists. Aliás com muitas PlayLists. Pois o canal alvo tem inúmeras delas:

Tela de PlayLists

Rótulo de título

Em /res/values/strings.xml atualize a propriedade de título a seguir:

...
<string name="playlists_content_title">
PlayLists
</string>
...

Ícone

O ícone vetorial de cada PlayList será exatamente o mesmo para todas:

Ícone de cada PlayList do canal, ic_playlist_color.xml:

Ícone vetorial de itens de PlayList

Realize o download do ícone e coloque-o no folder /res/drawable de sua versão de projeto.

Dados fixos

Coloque no método getInitialPlayLists(), da classe persistência PlayListsData, cada uma das PlayLists já disponíveis em canal.

Como a seguir:

...
fun getInitialPlayLists()
= mutableListOf(
PlayList(
title = "F1 Esports 2020",
uid = "PLfoNZDHitwjXAEbWFSL4hJsUnkF14y_rw"
),
PlayList(
title = "2020 F1 Pole Positions | Onboard Hot Laps",
uid = "PLfoNZDHitwjUG6Nq8W0XLC_ke3s90wb3M"
),
PlayList(
title = "Fernando Alonso Returns to F1!",
uid = "PLfoNZDHitwjVHvU4_yRzxojvLKbsKWDxZ"
),
PlayList(
title = "Best Team Radio 2020",
uid = "PLfoNZDHitwjWck2ndC4PdIN3-c9ccbIgB"
),
PlayList(
title = "Formula 1 Rolex Belgian Grand Prix 2020",
uid = "PLfoNZDHitwjV0jtnJr3410xKMT8It8_HZ"
),
PlayList(
title = "Formula 1 Aramco Gran Premio De España 2020",
uid = "PLfoNZDHitwjWFvaGTH2tZLHlUfqN8t90k"
),
PlayList(
title = "Emirates Formula 1 70th Anniversary Grand Prix 2020",
uid = "PLfoNZDHitwjX6EY2Sfdn6tHy9zFbnCnrd"
),
PlayList(
title = "What F1 Feels Like",
uid = "PLfoNZDHitwjXvx_7bXfxatL8vE7dfJMRN"
),
PlayList(
title = "The Legends",
uid = "PLfoNZDHitwjWnlZdS3mk5ttOZlqkLr9Nz"
),
PlayList(
title = "F1 70th Anniversary",
uid = "PLfoNZDHitwjUynZnW8XMmbhvnK1Qi2nR7"
),
PlayList(
title = "2021 Driver Moves",
uid = "PLfoNZDHitwjUnV6lmfFRHUOztgyXrVbd1"
),
PlayList(
title = "F1 Challenge Series 2020",
uid = "PLfoNZDHitwjVgUCeBbSOaLihLI3Jvh-B7"
),
PlayList(
title = "F1 Classics",
uid = "PLfoNZDHitwjUlydEe_RAZ2IgaKCIDxoUd"
),
PlayList(
title = "F1 Virtual GPs 2020",
uid = "PLfoNZDHitwjU1moRPhV163T1QHTpkui89"
),
PlayList(
title = "F1 Rewind!",
uid = "PLfoNZDHitwjXSniIQHvZPchn056vJq_Dn"
),
PlayList(
title = "2020 FIA Formula 3 Championship",
uid = "PLfoNZDHitwjXjQ9cvNSinbW6ezSKjxM8y"
),
PlayList(
title = "2020 FIA Formula 2 Championship",
uid = "PLfoNZDHitwjVGnDvKlYNZUIK4f7UK1iLC"
),
PlayList(
title = "Formula 1 Rolex Grosser Preis Von Österreich 2020",
uid = "PLfoNZDHitwjXMBeShm_2iOq34Q7dlXmBA"
),
PlayList(
title = "Formula 1 Pirelli Grosser Preis Der Steiermark 2020",
uid = "PLfoNZDHitwjWvN5xRm8-xTyrScl2ybuAZ"
),
PlayList(
title = "Formula 1 Rolex Australian Grand Prix 2020",
uid = "PLfoNZDHitwjW_JfXMrZUDlp8lwrjPkcWa"
),
PlayList(
title = "F1 2020 Race Highlights",
uid = "PLfoNZDHitwjXRANMnqmL0BzNGianZ2eX_"
),
PlayList(
title = "2020 F1 Season Preview",
uid = "PLfoNZDHitwjWx_vptWVzoyuL8GVGglV0h"
),
PlayList(
title = "Team Guides",
uid = "PLfoNZDHitwjVoXLlvVcfG1MbZn80BOGj6"
),
PlayList(
title = "F1 Testing 2020",
uid = "PLfoNZDHitwjXh_17yB_bI-hHXt3t1lgQb"
),
PlayList(
title = "F1 2020 Car Launches!",
uid = "PLfoNZDHitwjXbQQB7Rh43PX8kVoueMkcR"
),
PlayList(
title = "Best of 2019!",
uid = "PLfoNZDHitwjXID63aL08SfW3TqunE649p"
),
PlayList(
title = "F1 x Complex",
uid = "PLfoNZDHitwjXWM6U-UTowz2gO2m7Oi4oS"
),
PlayList(
title = "Grill The Grid 2019",
uid = "PLfoNZDHitwjUAibL7oIg4TCX1QkWdTcak"
),
PlayList(
title = "F1 Esports 2019",
uid = "PLfoNZDHitwjXw9KWL4nIoTGhogXtztEhe"
),
PlayList(
title = "The Best Of Team Radio 2019!",
uid = "PLfoNZDHitwjU7xYE4FdrweH8aGaT2z7IT"
),
PlayList(
title = "Niki Lauda, 1949-2019",
uid = "PLfoNZDHitwjVeh3xLJtXqup7iTAwdV4e3"
),
PlayList(
title = "FIA Formula 3 Championship",
uid = "PLfoNZDHitwjUzp1jZH8p8PMXpVVC-Aj-K"
),
PlayList(
title = "2019 F1 Pole Positions | Onboard Hot Laps",
uid = "PLfoNZDHitwjUA9aqbPGKw1l4SIz2bACi_"
),
PlayList(
title = "Formula 1 Etihad Airways Abu Dhabi Grand Prix 2019",
uid = "PLfoNZDHitwjUjlVjVvtXTtIhct2_1AhZy"
),
PlayList(
title = "Formula 1 Grande Premio Heineken do Brasil 2019",
uid = "PLfoNZDHitwjVf0YMtgh1es9I4oDORsmS5"
),
PlayList(
title = "Formula 1 United States Grand Prix 2019",
uid = "PLfoNZDHitwjWhH_FA5GArjph8k992Sxrt"
),
PlayList(
title = "Formula 1 Gran Premio de Mexico 2019",
uid = "PLfoNZDHitwjXI1gchbZgf-hlrKXHdXvlw"
),
PlayList(
title = "Formula 1 Japanese Grand Prix 2019",
uid = "PLfoNZDHitwjXlgY0rzRl-UomhV8vz6glb"
)
)
...

 

São muitas, certo?

Em caso de cliente, faça a mesma coisa:

Não economize, coloque todas as PlayLists já disponíveis em canal.

Assim podemos ir à próxima tela.

Circuitos

Para está tela teremos todo um combo de novas entidades, até mesmo o fragmento será um novo.

Tela de circuitos

Não somente está tela, mas também as telas de Parceiros e Loja terão todo um combo de novas entidades.

Uma outra opção é renomear as entidades que não mais serão utilizadas.

Aqui a nossa abordagem será como se estivéssemos literalmente adicionando novas telas por completo em projeto.

Então vamos aos códigos... começando pelos estáticos.

Estáticos de interface

Rótulos

Em /res/values/strings.xml adicione as seguintes novas Strings:

...
<!-- CircuitsFragment -->
<string name="circuits_content_title">
Circuitos
</string>
<string name="circuits_desc">
Toque no circuito para ter acesso a ele com
realidade de mapa (incluindo Street View).
</string>
<string name="circuits_toast_alert">
É preciso o aplicativo do \"%s\" para ter uma
realidade física do circuito \"%s\".
</string>

<!-- Circuitos -->
<string name="grand_prix_spain_circuit_name">
Circuito de Barcelona (Espanha)
</string>
<string name="grand_prix_spain_circuit_km_length">
Percurso: 4.655 km
</string>
<string name="grand_prix_spain_circuit_laps">
Voltas: 64
</string>

<string name="grand_prix_british_circuit_name">
Circuito de Silverstone (Inglaterra)
</string>
<string name="grand_prix_british_circuit_km_length">
Percurso: 5.891 km
</string>
<string name="grand_prix_british_circuit_laps">
Voltas: 52
</string>

<string name="grand_prix_austria_circuit_name">
Circuito Red Bull Ring (Áustria)
</string>
<string name="grand_prix_austria_circuit_km_length">
Percurso: 4.318 km
</string>
<string name="grand_prix_austria_circuit_laps">
Voltas: 71
</string>

<string name="grand_prix_hungary_circuit_name">
Circuito Hungaroring (Hungria)
</string>
<string name="grand_prix_hungary_circuit_km_length">
Percurso: 4.381 km
</string>
<string name="grand_prix_hungary_circuit_laps">
Voltas: 70
</string>

<string name="grand_prix_belgium_circuit_name">
Circuito Spa-Francorchamps (Bélgica)
</string>
<string name="grand_prix_belgium_circuit_km_length">
Percurso: 7.004 km
</string>
<string name="grand_prix_belgium_circuit_laps">
Voltas: 44
</string>

<string name="grand_prix_italy_circuit_name">
Circuito de Monza (Itália)
</string>
<string name="grand_prix_italy_circuit_km_length">
Percurso: 5.793 km
</string>
<string name="grand_prix_italy_circuit_laps">
Voltas: 53
</string>

<string name="grand_prix_tuscan_circuit_name">
Circuito Internacional de Mugello (Itália)
</string>
<string name="grand_prix_tuscan_circuit_km_length">
Percurso: 5.245 km
</string>
<string name="grand_prix_tuscan_circuit_laps">
Voltas: 59
</string>

<string name="grand_prix_russian_circuit_name">
Circuito de Sochi (Rússia)
</string>
<string name="grand_prix_russian_circuit_km_length">
Percurso: 5.848 km
</string>
<string name="grand_prix_russian_circuit_laps">
Voltas: 53
</string>

<string name="grand_prix_germany_circuit_name">
Circuito de Nurburgring (Alemanha)
</string>
<string name="grand_prix_germany_circuit_km_length">
Percurso: 5.148 km
</string>
<string name="grand_prix_germany_circuit_laps">
Voltas: 60
</string>

<string name="grand_prix_portugal_circuit_name">
Circuito Internacional do Algarve (Portugal)
</string>
<string name="grand_prix_portugal_circuit_km_length">
Percurso: 4.684 km
</string>
<string name="grand_prix_portugal_circuit_laps">
Voltas: 66
</string>

<string name="grand_prix_emilia_romagna_circuit_name">
Circuito Enzo e Dino Ferrari (Itália)
</string>
<string name="grand_prix_emilia_romagna_circuit_km_length">
Percurso: 4.959 km
</string>
<string name="grand_prix_emilia_romagna_circuit_laps">
Voltas: 63
</string>

<string name="grand_prix_turkey_circuit_name">
Circuito Intercity Istanbul Park (Turquia)
</string>
<string name="grand_prix_turkey_circuit_km_length">
Percurso: 5.338 km
</string>
<string name="grand_prix_turkey_circuit_laps">
Voltas: 58
</string>

<string name="grand_prix_bahrain_circuit_name">
Circuito Internacional em Sakhir (Bahrain)
</string>
<string name="grand_prix_bahrain_circuit_km_length">
Percurso: 5.412 km
</string>
<string name="grand_prix_bahrain_circuit_laps">
Voltas: 57
</string>

<string name="grand_prix_bahrain_rolex_circuit_name">
Circuito Internacional em Sakhir - Rolex (Bahrain)
</string>
<string name="grand_prix_bahrain_rolex_circuit_km_length">
Percurso: 3.543 km
</string>
<string name="grand_prix_bahrain_rolex_circuit_laps">
Voltas: 87
</string>

<string name="grand_prix_abu_dhabi_circuit_name">
Circuito Yas Marina (Abu Dhabi)
</string>
<string name="grand_prix_abu_dhabi_circuit_km_length">
Percurso: 5.554 km
</string>
<string name="grand_prix_abu_dhabi_circuit_laps">
Voltas: 55
</string>
...

 

Todos os circuitos da temporada de 2020 foram colocados em projeto.

Incluindo os circuitos de pré-temporada e os circuitos que não foram utilizados em 2020 devido ao problema de pandemia.

Imagens das pistas

Este é o ponto crítico para está versão de app framework.

Digo crítico, pois não é nada fácil encontrar a imagem de cada circuito em formato SVG. Não até o momento em que este artigo é construído.

Sendo assim a melhor escolha é:

Sim, é isso mesmo.

Quando você optar por trabalhar com versões SVG de imagens que não têm suas versões SVG com fácil acesso...

... então muito provavelmente o roteiro de conversão será como na lista anterior.

É isso.

Segue cada ícone dos circuitos da temporada 2020 da Formula 1:

Circuito de Barcelona (Espanha), ic_grand_prix_spain.xml:

Circuito de Barcelona (Espanha)

Circuito de Silverstone (Inglaterra), ic_grand_prix_british.xml:

Circuito de Silverstone (Inglaterra)

Circuito Red Bull Ring (Áustria), ic_grand_prix_austria.xml:

Circuito Red Bull Ring (Áustria)

Circuito Hungaroring (Hungria), ic_grand_prix_hungary.xml:

Circuito Hungaroring (Hungria)

Circuito Spa-Francorchamps (Bélgica), ic_grand_prix_belgium.xml:

Circuito Spa-Francorchamps (Bélgica)

Circuito de Monza (Itália), ic_grand_prix_italy.xml:

Circuito de Monza (Itália)

Circuito Internacional de Mugello (Itália), ic_grand_prix_tuscan.xml:

Circuito Internacional de Mugello (Itália)

Circuito de Sochi (Rússia), ic_grand_prix_russian.xml:

Circuito de Sochi (Rússia)

Circuito de Nurburgring (Alemanha), ic_grand_prix_germany.xml:

Circuito de Nurburgring (Alemanha)

Circuito Internacional do Algarve (Portugal), ic_grand_prix_portugal.xml:

Circuito Internacional do Algarve (Portugal)

Circuito Enzo e Dino Ferrari (Itália), ic_grand_prix_emilia_romagna.xml:

Circuito Enzo e Dino Ferrari (Itália)

Circuito Intercity Istanbul Park (Turquia), ic_grand_prix_turkey.xml:

Circuito Intercity Istanbul Park (Turquia)

Circuito Internacional em Sakhir (Bahrain), ic_grand_prix_bahrain.xml:

Circuito Internacional em Sakhir (Bahrain)

Circuito Internacional em Sakhir - Rolex (Bahrain), ic_grand_prix_bahrain_rolex.xml:

Circuito Internacional em Sakhir - Rolex (Bahrain)

Circuito Yas Marina (Abu Dhabi), ic_grand_prix_abu_dhabi.xml:

Circuito Yas Marina (Abu Dhabi)

Assim, realize o download das imagens vetoriais de cada circuito e coloque-as no folder /res/drawable.

Ícones de itens

Ainda temos dois ícones para carregar:

Ícones de informação do circuito

São os ícones de "Percurso" e de "Voltas":

Ícone de percurso (tamanho da pista), ic_extension.xml:

Ícone de percurso

Ícone de número de voltas em circuito, ic_lap.xml:

Ícone de número de voltas em circuito

Novamente, realize o download dos ícones e coloque-os no folder /res/drawable.

Bloco de informações de circuito

Note novamente no design de item de lista da tela de circuitos que há um conjunto de informações ao lado de cada circuito:

Conjunto de informações de circuito

E para está área também temos um arquivo de background específico.

Justamente devido à cor e às bordas arredondadas (com radius aplicado).

Sendo assim, em /res/drawable coloque o novo arquivo XML bg_item_list_info_side.xml com a seguinte definição:

<?xml version="1.0" encoding="utf-8"?>
<shape
xmlns:android="http://schemas.android.com/apk/res/android"
android:shape="rectangle">

<solid
android:color="@color/colorListItemBackgroundInfoSide" />

<corners
android:bottomLeftRadius="0dp"
android:radius="8dp" />
</shape>

 

Enfim a nossa nova propriedade de cor colorListItemBackgroundInfoSide sendo utilizada.

Layout de item

Agora o novo layout para os itens de lista desta tela de circuitos.

Primeiro o diagrama deste novo layout:

Diagrama do layout list_item_big_image.xml

Agora, em /res/layout, adicione o layout list_item_big_image.xml com a configuração de código a seguir:

<?xml version="1.0" encoding="utf-8"?>
<RelativeLayout
xmlns:android="http://schemas.android.com/apk/res/android"
style="@style/AppTheme.ListItemContainer"
android:background="@drawable/bg_item_list">

<ImageView
android:id="@+id/iv_icon"
style="@style/AppTheme.ListItemIcon"
android:layout_width="126dp"
android:layout_height="105dp"
android:layout_above="@+id/vv_vertical_line"
android:layout_alignParentStart="true"
android:layout_alignParentTop="false"
android:adjustViewBounds="false"
android:padding="5dp"
android:scaleType="centerInside" />

<View
android:id="@+id/vv_vertical_line"
android:layout_width="match_parent"
android:layout_height="3dp"
android:layout_alignParentBottom="true"
android:layout_toStartOf="@+id/ll_text_container"
android:background="@color/colorListItemBackgroundInfoSide" />

<LinearLayout
android:id="@+id/ll_text_container"
android:layout_width="wrap_content"
android:layout_height="match_parent"
android:layout_alignParentTop="true"
android:layout_alignParentEnd="true"
android:layout_toEndOf="@+id/iv_icon"
android:background="@drawable/bg_item_list_info_side"
android:orientation="vertical"
android:paddingStart="8dp"
android:paddingTop="1dp"
android:paddingEnd="8dp"
android:paddingBottom="1dp">

<TextView
android:id="@+id/tv_main_text"
style="@style/AppTheme.ListItemMainText"
android:layout_marginTop="8dp"
android:layout_marginBottom="14dp"
android:ellipsize="end"
android:maxLines="2"
android:textStyle="bold" />

<TextView
android:id="@+id/tv_first_aux_text"
style="@style/AppTheme.ListItemAuxText"
android:layout_marginBottom="0dp"
android:drawableStart="@drawable/ic_extension"
android:drawablePadding="7dp"
android:ellipsize="end"
android:maxLines="1"
android:textSize="14sp"
android:textStyle="normal" />

<TextView
android:id="@+id/tv_second_aux_text"
style="@style/AppTheme.ListItemAuxText"
android:drawableStart="@drawable/ic_lap"
android:drawablePadding="7dp"
android:ellipsize="end"
android:maxLines="1"
android:textSize="14sp"
android:textStyle="normal" />
</LinearLayout>
</RelativeLayout>

 

Somente está tela terá um novo layout de item de lista.

Vamos aos códigos dinâmicos (Kotlin).

Códigos dinâmicos

Atualizando adapter de lista

Ok, Thiengo. Temos um novo layout de item de lista. Sendo assim teremos que ter também novas classes Adapter e ViewHolder?

Não.

Porém será preciso uma pequena atualização na classe ListItemAdapter.

É sério: é uma pequena atualização apenas.

Acesse a classe ListItemAdapter e na assinatura dela adicione a propriedade layout como a seguir:

...
class ListItemAdapter(
val context: Context,
val items: List<ListItem>,
private val callExternalAppCallback: (ListItem)->Unit,
private val layout: Int = R.layout.list_item
) : RecyclerView.Adapter<ListItemViewHolder>() {
...

 

E por fim, ainda em ListItemAdapter, porém no método onCreateViewHolder(). Atualize a invocação de inflate() para passar a referenciar também a nova propriedade layout.

Exatamente como a seguir:

...
override fun onCreateViewHolder(...) : ListItemViewHolder {

val layout = LayoutInflater
.from( context )
.inflate(
layout,
parent,
false
)

...
}
...

 

Pronto.

Assim nosso novo fragmento de circuitos também poderá aproveitar toda a configuração do fragmento ancestral FrameworkListFragment.

Classe de domínio

Sim, teremos um nova classe de domínio.

No pacote /model adicione a classe Circuit com o código a seguir:

package formula1.com.youtubechannel.model

import android.content.res.Resources
import android.net.Uri

/**
* Um circuito da temporada da Fórmula 1.
*
* O objetivo desta classe (objetos desta classe)
* é manter os principais dados do circuito para que
* o seguidor tenha mais entretenimento no app.
*
* @property place app que abrirá o circuito em tela.
* @property name nome do circuito.
* @property kmLength tamanho, km, do circuito.
* @property laps número de voltas no circuito.
* @property appUri maps URI API para abrir o mapa
* ou o StreetView.
* @property logo imagem do circuito.
* @constructor cria um objeto completo do tipo
* [Circuit].
*/
class Circuit(
val place: String = "Google Maps",
val name: String,
val kmLength: String,
val laps: String,
private val appUri: String,
private val logo: Int ) : ListItem {

override fun getMainText()
= name

override fun getFirstAuxText()
= kmLength

override fun getSecondAuxText( resource: Resources )
= laps

override fun getAppUri()
= Uri.parse( appUri )

override fun getIcon()
= logo
}

 

Podemos ir então à persistência estática.

Persistência estática

No pacote /data/fixed crie a classe CircuitsData com os códigos a seguir:

package formula1.com.youtubechannel.data.fixed

import android.content.res.Resources
import formula1.com.youtubechannel.R
import formula1.com.youtubechannel.model.Circuit

/**
* Contém os dados dos circuitos da temporada
* da Formula 1.
*
* O objetivo desta classe é trabalhar como uma
* persistência local estática, fixa, que contém
* os dados dos circuitos.
*
* Como esses dados tendem a sofrer poucas
* alterações (incluindo a inserção de novos grupos)
* e com espaços de tempo longos entre as alterações,
* então a melhor escolha foi o trabalho deles em
* uma classe estática (companion object) que
* trabalha como se fosse uma persistência de dados
* estáticos.
*/
abstract class CircuitsData {

companion object{
/**
* Retorna os circuitos Formula 1.
*
* @return lista não mutável de objetos
* [Circuit].
*/
fun getCircuits( res: Resources )
= listOf(
Circuit(
name = res.getString( R.string.grand_prix_spain_circuit_name ),
kmLength = res.getString( R.string.grand_prix_spain_circuit_km_length ),
laps = res.getString( R.string.grand_prix_spain_circuit_laps ),
appUri = "geo:41.568386,2.257148?z=14",
logo = R.drawable.ic_grand_prix_spain
),
Circuit(
name = res.getString( R.string.grand_prix_british_circuit_name ),
kmLength = res.getString( R.string.grand_prix_british_circuit_km_length ),
laps = res.getString( R.string.grand_prix_british_circuit_laps ),
appUri = "google.streetview:panoid=uuyC1meP9ZmbqQd2Ja_pdQ",
logo = R.drawable.ic_grand_prix_british
),
Circuit(
name = res.getString( R.string.grand_prix_austria_circuit_name ),
kmLength = res.getString( R.string.grand_prix_austria_circuit_km_length ),
laps = res.getString( R.string.grand_prix_austria_circuit_laps ),
appUri = "google.streetview:panoid=8iy5DWuMERRI4GLOHzPrAQ",
logo = R.drawable.ic_grand_prix_austria
),
Circuit(
name = res.getString( R.string.grand_prix_hungary_circuit_name ),
kmLength = res.getString( R.string.grand_prix_hungary_circuit_km_length ),
laps = res.getString( R.string.grand_prix_hungary_circuit_laps ),
appUri = "geo:47.582093,19.250562?z=14",
logo = R.drawable.ic_grand_prix_hungary
),
Circuit(
name = res.getString( R.string.grand_prix_belgium_circuit_name ),
kmLength = res.getString( R.string.grand_prix_belgium_circuit_km_length ),
laps = res.getString( R.string.grand_prix_belgium_circuit_laps ),
appUri = "geo:50.437067,5.972030?z=14",
logo = R.drawable.ic_grand_prix_belgium
),
Circuit(
name = res.getString( R.string.grand_prix_italy_circuit_name ),
kmLength = res.getString( R.string.grand_prix_italy_circuit_km_length ),
laps = res.getString( R.string.grand_prix_italy_circuit_laps ),
appUri = "google.streetview:panoid=7XPiTT1Bb-cPqRABXTdToQ",
logo = R.drawable.ic_grand_prix_italy
),
Circuit(
name = res.getString( R.string.grand_prix_tuscan_circuit_name ),
kmLength = res.getString( R.string.grand_prix_tuscan_circuit_km_length ),
laps = res.getString( R.string.grand_prix_tuscan_circuit_laps ),
appUri = "geo:43.996221,11.371082?z=14",
logo = R.drawable.ic_grand_prix_tuscan
),
Circuit(
name = res.getString( R.string.grand_prix_russian_circuit_name ),
kmLength = res.getString( R.string.grand_prix_russian_circuit_km_length ),
laps = res.getString( R.string.grand_prix_russian_circuit_laps ),
appUri = "google.streetview:panoid=wuATMoMcQUaAg2tZtd5l0w",
logo = R.drawable.ic_grand_prix_russian
),
Circuit(
name = res.getString( R.string.grand_prix_germany_circuit_name ),
kmLength = res.getString( R.string.grand_prix_germany_circuit_km_length ),
laps = res.getString( R.string.grand_prix_germany_circuit_laps ),
appUri = "geo:50.334290,6.942610?z=14",
logo = R.drawable.ic_grand_prix_germany
),
Circuit(
name = res.getString( R.string.grand_prix_portugal_circuit_name ),
kmLength = res.getString( R.string.grand_prix_portugal_circuit_km_length ),
laps = res.getString( R.string.grand_prix_portugal_circuit_laps ),
appUri = "geo:37.231632,-8.628392?z=14",
logo = R.drawable.ic_grand_prix_portugal
),
Circuit(
name = res.getString( R.string.grand_prix_emilia_romagna_circuit_name ),
kmLength = res.getString( R.string.grand_prix_emilia_romagna_circuit_km_length ),
laps = res.getString( R.string.grand_prix_emilia_romagna_circuit_laps ),
appUri = "google.streetview:panoid=BQMXpbyPOUavEzjU1f4wbQ",
logo = R.drawable.ic_grand_prix_emilia_romagna
),
Circuit(
name = res.getString( R.string.grand_prix_turkey_circuit_name ),
kmLength = res.getString( R.string.grand_prix_turkey_circuit_km_length ),
laps = res.getString( R.string.grand_prix_turkey_circuit_laps ),
appUri = "geo:40.958092,29.411653?z=14",
logo = R.drawable.ic_grand_prix_turkey
),
Circuit(
name = res.getString( R.string.grand_prix_bahrain_circuit_name ),
kmLength = res.getString( R.string.grand_prix_bahrain_circuit_km_length ),
laps = res.getString( R.string.grand_prix_bahrain_circuit_laps ),
appUri = "geo:26.032197,50.512853?z=14",
logo = R.drawable.ic_grand_prix_bahrain
),
Circuit(
name = res.getString( R.string.grand_prix_bahrain_rolex_circuit_name ),
kmLength = res.getString( R.string.grand_prix_bahrain_rolex_circuit_km_length ),
laps = res.getString( R.string.grand_prix_bahrain_rolex_circuit_laps ),
appUri = "geo:26.032197,50.512853?z=14",
logo = R.drawable.ic_grand_prix_bahrain_rolex
),
Circuit(
name = res.getString( R.string.grand_prix_abu_dhabi_circuit_name ),
kmLength = res.getString( R.string.grand_prix_abu_dhabi_circuit_km_length ),
laps = res.getString( R.string.grand_prix_abu_dhabi_circuit_laps ),
appUri = "geo:24.481397,54.615535?z=14",
logo = R.drawable.ic_grand_prix_abu_dhabi
)
)
}
}

 

Muito código, certo?

Isso devido à quantidade de circuitos na temporada.

Experiência realista

Com o uso de parte das APIs Android de Mapas de Alta Qualidade:

...
Circuit(
...,
appUri = "google.streetview:panoid=8iy5DWuMERRI4GLOHzPrAQ",
...
)
...

 

Nós vamos aumentar o valor agregado do app permitindo que o usuário entre dentro do circuito:

Acessando dentro do circuito Red Bull

Note que foi preciso apenas uma linha de API para cada circuito.

Importante: é possível que você somente consiga simular com sucesso a abertura do Street View em um aparelho Android real. As versões de emulador Android têm certas limitações quanto a isso.

Você vai perceber que alguns circuitos têm o seguinte formato de API URI de acionamento de mapa:

google.streetview:panoid=IDENTIFICADOR_UNICO_PAINEL

Onde o usuário literalmente entra no circuito.

E outros circuitos têm o formato a seguir:

geo:LATITUDE,LONGITUDE?z=ZOOM

Onde o usuário tem na verdade acesso ao mapa do circuito.

Isso ocorre, pois não são todos os circuitos que têm "navegação" (StreetView completo) liberada.

Sendo assim, para este nosso projeto, optamos por deixar com a URI de acesso dentro do circuito somente aqueles que permitem navegação.

CircuitsFragment

Enfim o fragmento de circuitos.

No pacote /ui/fragment adicione o fragmento CircuitsFragment como a seguir:

package formula1.com.youtubechannel.ui.fragment

import android.os.Bundle
import formula1.com.youtubechannel.R
import formula1.com.youtubechannel.data.fixed.CircuitsData
import formula1.com.youtubechannel.model.Circuit
import formula1.com.youtubechannel.ui.adapter.ListItemAdapter

/**
* Contém a listagem de circuitos da Formula 1.
*
* @constructor cria um objeto completo do tipo
* [CircuitsFragment].
*/
class CircuitsFragment : FrameworkListFragment() {

companion object {
/**
* Constante com o identificador único do
* fragmento [CircuitsFragment] para que
* ele seja encontrado na pilha de fragmentos
* e assim não seja necessária a construção
* de mais de um objeto deste fragmento em
* memória enquanto o aplicativo estiver em
* execução.
*/
const val KEY = "CircuitsFragment_key"
}

override fun onActivityCreated(
savedInstanceState: Bundle? ){
super.onActivityCreated( savedInstanceState )

setUiModel(
titleText = getString( R.string.circuits_content_title ),
subTitleText = getString( R.string.circuits_desc )
)

val adapter = ListItemAdapter(
context = activity!!,
items = CircuitsData.getCircuits( resources ),
callExternalAppCallback = {
item -> callExternalApp(
webUri = item.getWebUri(),
failMessage = String.format(
getString( R.string.circuits_toast_alert ),
(item as Circuit).place,
item.name
)
)
},
R.layout.list_item_big_image
)

initList( adapter = adapter )
}
}

 

Note que na configuração da propriedade adapter, em onActivityCreated(), já estamos referenciando o novo layout de item:

...
val adapter = ListItemAdapter(
...,
R.layout.list_item_big_image
)
...

 

É isso. Tela de circuito finalizada.

Faltam apenas duas telas agora.

Parceiros

Essa é outra tela que tem um combo de novas entidades entrando em projeto:

Tela de Parceiros F1

Mas o layout de itens de lista será o mesmo já definido em versão inicial de app framework.

Vamos iniciar com a parte estática.

Estáticos de interface

Rótulos

No arquivo /res/values/strings.xml adicione as seguintes novas Strings:

...
<!-- PartnersFragment -->
<string name="partners_content_title">
Nossos parceiros
</string>
<string name="partners_toast_alert">
É preciso ao menos um navegador Web para abrir a
página do parceiro F1 \"%s\".
</string>

<string name="partner_rolex">
Rolex - Os relógios rolex são feitos com atenção
diferente dos detalhes.
</string>
<string name="partner_dhl">
DHL - Entregamos com velocidade!
</string>
<string name="partner_aramco">
Saudi Aramco - Onde a energia é oportunidade.
</string>
<string name="partner_pirelli">
Pirelli - Uma empresa global de pneus de alto
valor.
</string>
<string name="partner_emirates">
Emirates Airlines - Levando você a mais de 80
destinos.
</string>
<string name="partner_heineken">
Heineken - Abra seu mundo.
</string>
<string name="partner_aws">
Amazon Web Services (AWS) - Serviços de computação
em nuvem.
</string>
<string name="partner_expo_2020">
Expo 2020 Dubai Uae
</string>
<string name="partner_amg">
AMG - Desempenho de condução.
</string>
<string name="partner_caterpillar">
CAT - Construir um mundo melhor.
</string>
<string name="partner_188_bet">
188 Bet - 100% de bônus em seu primeiro depósito.
</string>
<string name="partner_citibank">
CitiBank - Simplificar a experiência do cliente,
por meio da tecnologia.
</string>
<string name="partner_petronas">
Petronas - Fornecendo energia ao mundo com energia
mais limpa.
</string>
...

 

É aquilo que já venho falando sobre em vários projetos aqui do Blog:

Toda String que é passível se ser traduzida em novas versões de app... entra então em strings.xml.

As Strings de marcas entraram em strings.xml devido ao moti de cada uma delas. Esses que podem sim passar por tradução.

Ícones

Para os ícones de marcas, aqui o trabalho foi exatamente o mesmo realizado para as imagens vetoriais de circuitos.

Em resumo:

  • Encontrar a logo da marca em formato PNG (com transparência);
  • Converter para SVG;
  • Adicionar em projeto.

Confesso que desta vez, para nenhuma das imagens de brand, foi necessário o passo de otimização de SVG.

Existem alguns sites que têm, gratuito, os ícones vetoriais de algumas marcas.

Para as marcas parceiras da Formula 1 eu praticamente não encontrei nenhum ícone que fosse aproveitável em projeto.

É isso, vamos aos ícones das empresas parceiras:

Ícone Rolex, ic_logo_rolex.xml:

Ícone Rolex

Ícone DHL, ic_logo_dhl.xml:

Ícone DHL

Ícone Saudi Aramco, ic_logo_aramco.xml:

Ícone Saudi Aramco

Ícone Pirelli, ic_logo_pirelli.xml:

Ícone Pirelli

Ícone Emirates Airlines, ic_logo_emirates.xml:

Ícone Emirates Airlines

Ícone Heineken, ic_logo_heineken.xml:

Ícone Heineken

Ícone Amazon Web Services (AWS), ic_logo_aws.xml:

Ícone Amazon Web Services (AWS)

Ícone Expo 2020 Dubai Uae, ic_logo_expo_2020_dubai_uae.xml:

Ícone Expo 2020 Dubai Uae

Ícone AMG, ic_logo_amg.xml:

Ícone AMG

Ícone Caterpillar, ic_logo_caterpillar.xml:

Ícone Caterpillar

Ícone 188 Bet, ic_logo_188_bet.xml:

Ícone 188 Bet

Ícone CitiBank, ic_logo_citibank.xml:

Ícone CitiBank

Ícone Petronas, ic_logo_petronas.xml:

Ícone Petronas

Realize o download dos ícones anteriores e coloque-os no folder /res/drawable.

Códigos dinâmicos

Classe de domínio

Agora, no pacote /model, adicione a nova classe Partner com os fontes a seguir:

package formula1.com.youtubechannel.model

import android.net.Uri

/**
* Empresa parceira da Formula 1.
*
* O objetivo desta classe (objetos desta classe)
* é apenas manter os dados importantes para a
* apresentação da empresa parceira à Formula 1.
*
* @property name nome da empresa / marca.
* @property webPage URL da página Web oficial
* da empresa.
* @property logo identificador único do logo
* da empresa.
* @constructor cria um objeto completo do tipo
* [Partner].
*/
class Partner(
val name: String,
private val webPage: String,
private val logo: Int ) : ListItem {

override fun getMainText()
= name

override fun getWebUri()
= Uri.parse( webPage )

override fun getIcon()
= logo
}

Persistência estática

No pacote /data/fixed crie a classe persistência PartnersData com os códigos fonte a seguir:

package formula1.com.youtubechannel.data.fixed

import android.content.res.Resources
import formula1.com.youtubechannel.R
import formula1.com.youtubechannel.model.Partner

/**
* Contém os dados das empresas parceiras da
* Formula 1 na temporada.
*
* O objetivo desta classe é trabalhar como uma
* persistência local estática, fixa, que contém
* os dados das empresas parceiras.
*
* Como esses dados tendem a sofrer poucas
* alterações (incluindo a inserção de novos livros)
* e com espaços de tempo longos entre as
* alterações, então a melhor escolha foi o trabalho
* deles em uma classe estática (companion object)
* que trabalha como se fosse uma persistência de
* dados estáticos.
*/
abstract class PartnersData {

companion object {
/**
* Retorna todos os parceiros da Formula 1.
*
* @return lista não mutável de objetos
* [Partner].
*/
fun getBooks( res: Resources )
= listOf(
Partner(
name = res.getString( R.string.partner_rolex ),
webPage = "https://www.rolex.com/",
logo = R.drawable.ic_logo_rolex
),
Partner(
name = res.getString( R.string.partner_dhl ),
webPage = "https://www.dhl.com/",
logo = R.drawable.ic_logo_dhl
),
Partner(
name = res.getString( R.string.partner_aramco ),
webPage = "https://www.aramco.com/",
logo = R.drawable.ic_logo_aramco
),
Partner(
name = res.getString( R.string.partner_pirelli ),
webPage = "https://www.pirelli.com/",
logo = R.drawable.ic_logo_pirelli
),
Partner(
name = res.getString( R.string.partner_emirates ),
webPage = "https://www.emirates.com/",
logo = R.drawable.ic_logo_emirates
),
Partner(
name = res.getString( R.string.partner_heineken ),
webPage = "https://www.theheinekencompany.com/",
logo = R.drawable.ic_logo_heineken
),
Partner(
name = res.getString( R.string.partner_aws ),
webPage = "https://aws.amazon.com/",
logo = R.drawable.ic_logo_aws
),
Partner(
name = res.getString( R.string.partner_expo_2020 ),
webPage = "https://www.expo2020dubai.com/",
logo = R.drawable.ic_logo_expo_2020_dubai_uae
),
Partner(
name = res.getString( R.string.partner_amg ),
webPage = "https://www.mercedes-amg.com/",
logo = R.drawable.ic_logo_amg
),
Partner(
name = res.getString( R.string.partner_caterpillar ),
webPage = "https://www.caterpillar.com/",
logo = R.drawable.ic_logo_caterpillar
),
Partner(
name = res.getString( R.string.partner_188_bet ),
webPage = "https://www.188bet.com/",
logo = R.drawable.ic_logo_188_bet
),
Partner(
name = res.getString( R.string.partner_citibank ),
webPage = "https://corporateportal.brazil.citibank.com/index.htm",
logo = R.drawable.ic_logo_citibank
),
Partner(
name = res.getString( R.string.partner_petronas ),
webPage = "https://www.petronas.com/",
logo = R.drawable.ic_logo_petronas
)
)
}
}

PartnersFragment

Enfim o fragmento de empresas parceiras.

No pacote /ui/fragment adicione o fragmento PartnersFragment com o seguinte código fonte:

package formula1.com.youtubechannel.ui.fragment

import android.os.Bundle
import formula1.com.youtubechannel.R
import formula1.com.youtubechannel.data.fixed.PartnersData
import formula1.com.youtubechannel.model.Partner
import formula1.com.youtubechannel.ui.adapter.ListItemAdapter

/**
* Contém a lista de parceiros da Formula 1.
*
* @constructor cria um objeto completo do tipo
* [PartnersFragment].
*/
class PartnersFragment : FrameworkListFragment() {

companion object {
/**
* Constante com o identificador único do
* fragmento [PartnersFragment] para que
* ele seja encontrado na pilha de fragmentos
* e assim não seja necessária a construção
* de mais de um objeto deste fragmento em
* memória enquanto o aplicativo estiver em
* execução.
*/
const val KEY = "PartnersFragment_key"
}

override fun onActivityCreated(
savedInstanceState: Bundle? ){
super.onActivityCreated( savedInstanceState )

setUiModel(
titleText = getString( R.string.partners_content_title )
)

val adapter = ListItemAdapter(
context = activity!!,
items = PartnersData.getBooks( resources ),
callExternalAppCallback = {
item -> callExternalApp(
webUri = item.getWebUri(),
failMessage = String.format(
getString( R.string.partners_toast_alert ),
(item as Partner).name
)
)
}
)

initList( adapter = adapter )
}
}

 

Pronto.

Somente mais uma tela. A de Loja.

Loja

Agora a tela que contém uma única entidade item. A tela de loja:

Tela loja

Lembrando que parte do objetivo do app é sim aumentar os ganhos do canal.

Então não vejo porque não adicionar no projeto o link para acesso à Formula 1 Store.

Estáticos de interface

Rótulos

Em /res/values/strings.xml adicione as propriedades String a seguir:

...
<!-- StoreFragment -->
<string name="store_content_title">
Loja (10% de desconto)
</string>
<string name="store_desc">
Acesse nossa loja agora e tenha até 10% de
desconto na primeira compra.
</string>
<string name="store_toast_alert">
É preciso o aplicativo navegador Web para
poder acessar a loja.
</string>
...

Ícone

Para ícone vetorial de item de lista em Loja, temos:

Ícone de compra (simulando um macacão de corrida, é sério 😁), ic_overall.xml:

Ícone de compra (simulando um macacão de corrida)

Realize o download do ícone e coloque-o no folder /res/drawable do projeto.

Códigos dinâmicos

Classe de domínio

No pacote /model adicione a classe de domínio Store com o código a seguir:

package formula1.com.youtubechannel.model

import android.net.Uri

/**
* Objeto loja da Formula 1.
*
* @property name nome da loja.
* @property webUri URL da loja.
* @property logo ícone que identifica a loja.
* @constructor cria um objeto completo do tipo
* [Store].
*/
class Store(
val name: String,
private val webUri: String,
private val logo: Int ) : ListItem {

override fun getMainText()
= name

override fun getWebUri()
= Uri.parse( webUri )

override fun getIcon()
= logo
}

Dados fixos

No pacote /data/fixed adicione a classe persistência StoreData com o exato código a seguir:

package formula1.com.youtubechannel.data.fixed

import formula1.com.youtubechannel.R
import formula1.com.youtubechannel.model.Store

/**
* Contém os dados de lojas da Formula 1.
*
* O objetivo desta classe é trabalhar como uma
* persistência local estática, fixa, que contém
* todos os dados de lojas.
*
* Como esses dados tendem a sofrer poucas
* alterações (incluindo a inserção de novos contatos)
* e com espaços de tempo longos entre as alterações,
* então a melhor escolha foi o trabalho deles em
* uma classe estática (companion object) que
* trabalha como se fosse uma persistência de dados
* estáticos.
*/
abstract class StoreData {

companion object{
/**
* Retorna a loja Formula 1.
*
* @return lista não mutável de objetos
* [Store].
*/
fun getStore()
= listOf(
Store(
name = "F1 Store",
webUri = "https://f1store4.formula1.com/",
logo = R.drawable.ic_overall
)
)
}
}

StoreFragment

Por fim o último fragmento que representa uma tela em app.

No pacote /ui/fragment adicione o fragmento StoreFragment com o código a seguir:

package formula1.com.youtubechannel.ui.fragment

import android.os.Bundle
import formula1.com.youtubechannel.R
import formula1.com.youtubechannel.data.fixed.StoreData
import formula1.com.youtubechannel.ui.adapter.ListItemAdapter

/**
* Contém a listagem de lojas online da Formula 1.
*
* @constructor cria um objeto completo do tipo
* [StoreFragment].
*/
class StoreFragment : FrameworkListFragment() {

companion object {
/**
* Constante com o identificador único do
* fragmento [StoreFragment] para que
* ele seja encontrado na pilha de fragmentos
* e assim não seja necessária a construção
* de mais de um objeto deste fragmento em
* memória enquanto o aplicativo estiver em
* execução.
*/
const val KEY = "StoreFragment_key"
}

override fun onActivityCreated(
savedInstanceState: Bundle? ){
super.onActivityCreated( savedInstanceState )

setUiModel(
titleText = getString( R.string.store_content_title ),
subTitleText = getString( R.string.store_desc )
)

val adapter = ListItemAdapter(
context = activity!!,
items = StoreData.getStore(),
callExternalAppCallback = {
item -> callExternalApp(
webUri = item.getWebUri(),
appUri = item.getAppUri(),
failMessage = String.format(
getString( R.string.store_toast_alert )
)
)
}
)

initList( adapter = adapter )
}
}

 

Agora é preciso atualizar alguns trechos de código da atividade principal.

Atividade principal

Com a adição (e possíveis remoções) de alguns fragmentos, ainda é preciso atualizar dois métodos da atividade principal.

São eles:

  • getFragment();
  • getFragmentInKey().

Sendo assim, na MainActivity atualize o método getFragment() como a seguir:

...
private fun getFragment(
itemId: Int = R.id.last_video ) : Fragment {

val key = getFragmentInKey( itemId = itemId )

var fragment = supportFragmentManager
.findFragmentByTag( key )

if( fragment == null ){
fragment = when( itemId ){
R.id.social_networks -> SocialNetworksFragment()
R.id.play_lists -> PlayListsFragment()
R.id.circuits -> CircuitsFragment()
R.id.partners -> PartnersFragment()
R.id.store -> StoreFragment()
else -> LastVideoFragment()
}
}

return fragment
}
...

 

Logo depois o método getFragmentInKey() como a seguir:

...
private fun getFragmentInKey(
itemId: Int = R.id.last_video )
= when( itemId ){
R.id.social_networks -> SocialNetworksFragment.KEY
R.id.play_lists -> PlayListsFragment.KEY
R.id.circuits -> CircuitsFragment.KEY
R.id.partners -> PartnersFragment.KEY
R.id.store -> StoreFragment.KEY
else -> LastVideoFragment.KEY
}
...

 

É isso.

Vamos às notificações.

Atualizações para notificações push

No caso das notificações nós iremos mudar poucas coisas.

Rótulo

Em /res/values/strings.xml atualize a propriedade String a seguir:

...
<string name="notification_verbose_description">
Notificação de novos vídeos do canal YouTube
Formula 1.
</string>
...

Ícone

O ícone de notificação push será:

Ícone logo da Formula 1, ic_f1_blog_color.xml:

Ícone logo da Formula 1

É exatamente o mesmo ícone utilizado no item de lista Blog da tela de Redes do canal.

Caso você ainda não tenha feito, então realize o download do ícone e coloque-o no folder /res/drawable.

Banco de dados

Caso você já tenha testado em seu ambiente de desenvolvimento o app framework.

Então para testar a versão do aplicativo que você fez para algum cliente YouTuber é prudente atualizar a versão de banco de dados.

Atualize na classe ChannelDatabase, no pacote /data/dynamic, a propriedade version como a seguir:

...
@Database(
...,
version = 21
)
...

 

Acrescente +1 ao valor atual de version. Era 20 aqui, assim colocamos 21.

Resultado

Executando a nova versão de nosso app framework e passando por todas as telas, temos:

Tela de abertura de aplicativo

 Tela de abertura de aplicativo.

Tela de Último vídeo liberado em canal

Tela de Último vídeo liberado em canal.

Carregando novo último vídeo (swipe)

Carregando novo último vídeo (swipe).

Tela de PlayLists canal

Tela de PlayLists canal.

Carregando PlayLists (swipe)

Carregando PlayLists (swipe).

Carregando PlayLists (abertura de tela)

Carregando PlayLists (abertura de tela).

Nenhuma PlayList disponível

Nenhuma PlayList disponível.

Tela das redes e site do canal

Tela das redes e site do canal.

Tela de circuitos Formula 1

Tela de circuitos Formula 1.

Item de circuito acionado

Item de circuito acionado.

Tela de parceiros

Tela de parceiros.

Tela de loja

Tela de loja.

Notificação de novo vídeo

Notificação de novo vídeo.

Notificação em bandeja de notificações

Notificação em bandeja de notificações. 

Notificação expandida

Notificação expandida.

 

 

Minha recomendação final para você que já está ao menos na terceira versão, projeto, de nosso app Android framework (está é a terceira):

Remova do projeto todas as entidades que não mais estão sendo utilizadas.

Por exemplo:

Os fragmentos CoursesFragment e BooksFragment estão na lista de entidades que podem com segurança serem removidas de projeto.

Sendo assim remova também esses fragmentos e todas as entidades vinculadas a eles.

GitHub do projeto

Como com o projeto app framework, o aplicativo de exemplo que utilizamos aqui do canal Formula 1 também está disponível em repositório público:

➙ GitHub do projeto de aplicativo Android do canal YouTube Formula 1.

Conclusão

Como informado e apresentado em projeto:

Com uma simples linha de código utilizando a API de Mapas de Alta Qualidade é possível, mesmo em um aplicativo simples...

... é possível oferecer ao usuário uma experiência melhor e mais tangível.

Em nosso caso colocamos ele dentro de circuitos da temporada 2020 da Formula 1.

Esse app framework que desenvolvemos tem na verdade infinitas possibilidades.

Uma que provavelmente será um pedido a você caso você resolva monetizar o projeto, será:

Permitir que novas postagens no Instagram, Pinterest, ... também notifiquem o usuário em app.

Aliás esse é um possível conteúdo que provavelmente estarei fazendo também sobre este nosso aplicativo framework.

Então é isso.

Surgindo dúvidas sobre o projeto deste artigo ou sobre o app framework em geral. Pode deixar na área de comentários abaixo que logo eu lhe respondo.

Por fim, não esqueça de conhecer também o meu canal no YouTube (caso você ainda não o conheça) e...

... não deixe de se inscrever na 📩 lista de e-mails para também garantir a versão em PDF não somente deste projeto de aplicativo Android, mas também de cada novo conteúdo.

Abraço.

Fontes

Lista de autódromos de Fórmula 1
Scale svg images in ImageView - Resposta de Pavan
long SVG Asset file error : R is not a valid verb. Failure occurred at position 2 of path: STRING_TOO_LARGE - Resposta de TheBigBadBoy
How to record screen with Android Studio - Resposta de Vaibhav Yadav e Glenn

Investir em Você é Barra de Ouro a R$ 2,00. Cadastre-se e receba grátis conteúdos Android sem precedentes!
Email inválido

Relacionado

Porque e Como Utilizar Vetores no AndroidPorque e Como Utilizar Vetores no AndroidAndroid
Construa Um Aplicativo Android Completo Para YouTubers - Parte 1Construa Um Aplicativo Android Completo Para YouTubers - Parte 1Android
Início do Lado Tático e Barra de Topo Personalizada - YouTuber Android App - Parte 2Início do Lado Tático e Barra de Topo Personalizada - YouTuber Android App - Parte 2Android
Criando e Configurando o Menu Principal - YouTuber Android App - Parte 3Criando e Configurando o Menu Principal - YouTuber Android App - Parte 3Android

Compartilhar

Comentários Facebook

Comentários Blog

Para código / script, coloque entre [code] e [/code] para receber marcação especifica.
Forneça seu nome válido.
Forneça seu email válido.
Forneça o comentário.
Enviando, aguarde...