mirror of
https://github.com/stonega/tsacdop
synced 2025-02-09 08:08:46 +01:00
Sync with gpodder.net.
This commit is contained in:
parent
064d63350d
commit
719d9c8cc0
BIN
assets/gpodder.png
Normal file
BIN
assets/gpodder.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 34 KiB |
365
lib/generated/intl/messages_pt.dart
Normal file
365
lib/generated/intl/messages_pt.dart
Normal file
@ -0,0 +1,365 @@
|
||||
// DO NOT EDIT. This is code generated via package:intl/generate_localized.dart
|
||||
// This is a library that provides messages for a pt locale. All the
|
||||
// messages from the main program should be duplicated here with the same
|
||||
// function name.
|
||||
|
||||
// Ignore issues from commonly used lints in this file.
|
||||
// ignore_for_file:unnecessary_brace_in_string_interps, unnecessary_new
|
||||
// ignore_for_file:prefer_single_quotes,comment_references, directives_ordering
|
||||
// ignore_for_file:annotate_overrides,prefer_generic_function_type_aliases
|
||||
// ignore_for_file:unused_import, file_names
|
||||
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:intl/message_lookup_by_library.dart';
|
||||
|
||||
final messages = new MessageLookup();
|
||||
|
||||
typedef String MessageIfAbsent(String messageStr, List<dynamic> args);
|
||||
|
||||
class MessageLookup extends MessageLookupByLibrary {
|
||||
String get localeName => 'pt';
|
||||
|
||||
static m0(groupName, count) => "${Intl.plural(count, zero: '', one: '${count} episódio de ${groupName} adicionado à lista', other: '${count} episódios de ${groupName} adicionados à lista')}";
|
||||
|
||||
static m1(count) => "${Intl.plural(count, zero: '', one: '${count} episódio adicionado à lista', other: '${count} episódios adicionados à lista')}";
|
||||
|
||||
static m2(count) => "${Intl.plural(count, zero: 'Hoje', one: 'Há ${count} dia', other: 'Há ${count} dias')}";
|
||||
|
||||
static m3(count) => "${Intl.plural(count, zero: 'Nunca', one: '${count} dia', other: '${count} dias')}";
|
||||
|
||||
static m4(count) => "${Intl.plural(count, zero: '', one: 'Episódio', other: 'Episódios')}";
|
||||
|
||||
static m5(time) => "De ${time}";
|
||||
|
||||
static m6(count) => "${Intl.plural(count, zero: 'Grupo', one: 'Grupo', other: 'Grupos')}";
|
||||
|
||||
static m7(host) => "Hospedado em ${host}";
|
||||
|
||||
static m8(count) => "${Intl.plural(count, zero: '', one: 'há ${count} hora', other: 'há ${count} horas')}";
|
||||
|
||||
static m9(count) => "${Intl.plural(count, zero: '0 horas', one: '${count} hora', other: '${count} horas')}";
|
||||
|
||||
static m10(service) => "Integrate with ${service}";
|
||||
|
||||
static m11(userName) => "Logged in as ${userName}";
|
||||
|
||||
static m12(count) => "${Intl.plural(count, zero: 'Agora', one: 'Há ${count} minuto', other: 'Há ${count} minutos')}";
|
||||
|
||||
static m13(count) => "${Intl.plural(count, zero: '0 minutos', one: '${count} minuto', other: '${count} minutos')}";
|
||||
|
||||
static m14(title) => "Obter dados ${title}";
|
||||
|
||||
static m15(title) => "A subscrição falhou, erro de rede ${title}";
|
||||
|
||||
static m16(title) => "Subscrever ${title}";
|
||||
|
||||
static m17(title) => "Subscrição falhou, podcast já existe ${title}";
|
||||
|
||||
static m18(title) => "Subscrito com sucesso ${title}";
|
||||
|
||||
static m19(title) => "Atualizar ${title}";
|
||||
|
||||
static m20(title) => "Erro de atualização ${title}";
|
||||
|
||||
static m21(count) => "${Intl.plural(count, zero: '', one: 'Podcast', other: 'Podcasts')}";
|
||||
|
||||
static m22(date) => "Publicado em ${date}";
|
||||
|
||||
static m23(date) => "Removido em ${date}";
|
||||
|
||||
static m24(count) => "${Intl.plural(count, zero: '0 segundos', one: '${count} segundo', other: '${count} segundos')}";
|
||||
|
||||
static m25(count) => "${Intl.plural(count, zero: 'Agora', one: 'Há ${count} segundo', other: 'Há ${count} segundos')}";
|
||||
|
||||
static m26(time) => "Última vez ${time}";
|
||||
|
||||
static m27(time) => "${time} Restante";
|
||||
|
||||
static m28(time) => "Para ${time}";
|
||||
|
||||
static m29(count) => "${Intl.plural(count, zero: 'Sem atualizações', one: '${count} episódio atualizado', other: '${count} episódios atualizados')}";
|
||||
|
||||
static m30(version) => "Versão: ${version}";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static _notInlinedMessages(_) => <String, Function> {
|
||||
"add" : MessageLookupByLibrary.simpleMessage("Adicionar"),
|
||||
"addEpisodeGroup" : m0,
|
||||
"addNewEpisodeAll" : m1,
|
||||
"addNewEpisodeTooltip" : MessageLookupByLibrary.simpleMessage("Adiciona novos episódios à lista de reprodução"),
|
||||
"addSomeGroups" : MessageLookupByLibrary.simpleMessage("Adiciona alguns grupos"),
|
||||
"all" : MessageLookupByLibrary.simpleMessage("Todos"),
|
||||
"autoDownload" : MessageLookupByLibrary.simpleMessage("Download automático"),
|
||||
"back" : MessageLookupByLibrary.simpleMessage("Atrás"),
|
||||
"boostVolume" : MessageLookupByLibrary.simpleMessage("Aumentar volume"),
|
||||
"buffering" : MessageLookupByLibrary.simpleMessage("A carregar"),
|
||||
"cancel" : MessageLookupByLibrary.simpleMessage("CANCELAR"),
|
||||
"cellularConfirm" : MessageLookupByLibrary.simpleMessage("Alerta de dados móveis"),
|
||||
"cellularConfirmDes" : MessageLookupByLibrary.simpleMessage("Tens a certeza que queres usar dados móveis para downloads?"),
|
||||
"changeLayout" : MessageLookupByLibrary.simpleMessage("Mudar aparência"),
|
||||
"changelog" : MessageLookupByLibrary.simpleMessage("Registo de mudanças"),
|
||||
"chooseA" : MessageLookupByLibrary.simpleMessage("Escolher um"),
|
||||
"clear" : MessageLookupByLibrary.simpleMessage("Limpar"),
|
||||
"color" : MessageLookupByLibrary.simpleMessage("Cor"),
|
||||
"confirm" : MessageLookupByLibrary.simpleMessage("CONFIRMAR"),
|
||||
"darkMode" : MessageLookupByLibrary.simpleMessage("Modo escuro"),
|
||||
"daysAgo" : m2,
|
||||
"daysCount" : m3,
|
||||
"delete" : MessageLookupByLibrary.simpleMessage("Eliminar"),
|
||||
"developer" : MessageLookupByLibrary.simpleMessage("Desenvolvedor"),
|
||||
"dismiss" : MessageLookupByLibrary.simpleMessage("Minimizar"),
|
||||
"done" : MessageLookupByLibrary.simpleMessage("Feito"),
|
||||
"download" : MessageLookupByLibrary.simpleMessage("Download"),
|
||||
"downloadRemovedToast" : MessageLookupByLibrary.simpleMessage("Download removido"),
|
||||
"downloaded" : MessageLookupByLibrary.simpleMessage("Descarregado"),
|
||||
"editGroupName" : MessageLookupByLibrary.simpleMessage("Editar nome do grupo"),
|
||||
"endOfEpisode" : MessageLookupByLibrary.simpleMessage("Fim do episódio"),
|
||||
"episode" : m4,
|
||||
"fastForward" : MessageLookupByLibrary.simpleMessage("Avanço"),
|
||||
"fastRewind" : MessageLookupByLibrary.simpleMessage("Recuo rápido"),
|
||||
"featureDiscoveryEditGroup" : MessageLookupByLibrary.simpleMessage("Prime para editar grupo"),
|
||||
"featureDiscoveryEditGroupDes" : MessageLookupByLibrary.simpleMessage("Podes alterar o nome do grupo ou apagá-lo aqui, mas o grupo Home não pode ser editado ou eliminado"),
|
||||
"featureDiscoveryEpisode" : MessageLookupByLibrary.simpleMessage("Vista de episódios"),
|
||||
"featureDiscoveryEpisodeDes" : MessageLookupByLibrary.simpleMessage("Podes manter premido para reproduzir um episódio ou adicioná-lo a uma lista de reprodução."),
|
||||
"featureDiscoveryEpisodeTitle" : MessageLookupByLibrary.simpleMessage("Mantém premido para reproduzir um episódio instantâneamente"),
|
||||
"featureDiscoveryGroup" : MessageLookupByLibrary.simpleMessage("Prime para adicionar grupo"),
|
||||
"featureDiscoveryGroupDes" : MessageLookupByLibrary.simpleMessage("O grupo por defeito para novos podcasts é Home. Podes criar novos grupos e mover os podcasts para estes, assim como adicionar podcasts a múltiplos grupos."),
|
||||
"featureDiscoveryGroupPodcast" : MessageLookupByLibrary.simpleMessage("Mantém premido para reordenar podcasts"),
|
||||
"featureDiscoveryGroupPodcastDes" : MessageLookupByLibrary.simpleMessage("Podes premir para ver mais opções, ou manter premido para reordenar podcasts em grupos."),
|
||||
"featureDiscoveryOMPL" : MessageLookupByLibrary.simpleMessage("Premir para importar um OPML"),
|
||||
"featureDiscoveryOMPLDes" : MessageLookupByLibrary.simpleMessage("Podes importar ficheiros OPML, abrir as definições ou atualizar todos os podcasts aqui."),
|
||||
"featureDiscoveryPlaylist" : MessageLookupByLibrary.simpleMessage("Prime para abrir a lista de reprodução"),
|
||||
"featureDiscoveryPlaylistDes" : MessageLookupByLibrary.simpleMessage("Podes adicionar episódios à lista de reprodução manualmente. Os episódios serão automaticamente removidos das listas de reprodução quando reproduzidos."),
|
||||
"featureDiscoveryPodcast" : MessageLookupByLibrary.simpleMessage("Vista do podcast"),
|
||||
"featureDiscoveryPodcastDes" : MessageLookupByLibrary.simpleMessage("Podes premir \"Ver Todos\" para adicionar grupos ou organizar pdcasts."),
|
||||
"featureDiscoveryPodcastTitle" : MessageLookupByLibrary.simpleMessage("Deslizar verticalmente para alterar grupos"),
|
||||
"featureDiscoverySearch" : MessageLookupByLibrary.simpleMessage("Prime para procurar podcasts"),
|
||||
"featureDiscoverySearchDes" : MessageLookupByLibrary.simpleMessage("Podes procurar pelo título do podcast, palavra-chave ou ligação RSS para subscrever novos podcasts."),
|
||||
"feedbackEmail" : MessageLookupByLibrary.simpleMessage("Escreve-me"),
|
||||
"feedbackGithub" : MessageLookupByLibrary.simpleMessage("Submeter problema"),
|
||||
"feedbackPlay" : MessageLookupByLibrary.simpleMessage("Avaliar na Play Store"),
|
||||
"feedbackTelegram" : MessageLookupByLibrary.simpleMessage("Juntar um grupo"),
|
||||
"filter" : MessageLookupByLibrary.simpleMessage("Filtro"),
|
||||
"fontStyle" : MessageLookupByLibrary.simpleMessage("Estilo do tipo de letra"),
|
||||
"fonts" : MessageLookupByLibrary.simpleMessage("Fontes"),
|
||||
"from" : m5,
|
||||
"goodNight" : MessageLookupByLibrary.simpleMessage("Boa Noite"),
|
||||
"gpodderLoginDes" : MessageLookupByLibrary.simpleMessage("Congratulations! You have linked gpodder.net account successfully. Tsacdop will automatically sync subscriptions on your device with your gpodder.net account."),
|
||||
"groupExisted" : MessageLookupByLibrary.simpleMessage("Grupo já existe"),
|
||||
"groupFilter" : MessageLookupByLibrary.simpleMessage("Filtro de grupo"),
|
||||
"groupRemoveConfirm" : MessageLookupByLibrary.simpleMessage("Tens a certeza que queres eliminar este grupo? Os podcasts serão removidos para o grupo \"Home\"."),
|
||||
"groups" : m6,
|
||||
"hideListenedSetting" : MessageLookupByLibrary.simpleMessage("Esconder ouvidos"),
|
||||
"homeGroupsSeeAll" : MessageLookupByLibrary.simpleMessage("Ver Todos"),
|
||||
"homeMenuPlaylist" : MessageLookupByLibrary.simpleMessage("Lista de Reprodução"),
|
||||
"homeSubMenuSortBy" : MessageLookupByLibrary.simpleMessage("Ordenar por"),
|
||||
"homeTabMenuFavotite" : MessageLookupByLibrary.simpleMessage("Favorito"),
|
||||
"homeTabMenuRecent" : MessageLookupByLibrary.simpleMessage("Recentes"),
|
||||
"homeToprightMenuAbout" : MessageLookupByLibrary.simpleMessage("Sobre"),
|
||||
"homeToprightMenuImportOMPL" : MessageLookupByLibrary.simpleMessage("Importar OPML"),
|
||||
"homeToprightMenuRefreshAll" : MessageLookupByLibrary.simpleMessage("Atualizar todos"),
|
||||
"hostedOn" : m7,
|
||||
"hoursAgo" : m8,
|
||||
"hoursCount" : m9,
|
||||
"import" : MessageLookupByLibrary.simpleMessage("Importar"),
|
||||
"intergateWith" : m10,
|
||||
"introFourthPage" : MessageLookupByLibrary.simpleMessage("Podes manter premido um episódio para uma ação rápida."),
|
||||
"introSecondPage" : MessageLookupByLibrary.simpleMessage("Subscreve podcasts por pesquisa ou importa um ficheiro OPML."),
|
||||
"introThirdPage" : MessageLookupByLibrary.simpleMessage("Podes criar um novo grupo para podcasts."),
|
||||
"invalidName" : MessageLookupByLibrary.simpleMessage("Invalid username"),
|
||||
"lastUpdate" : MessageLookupByLibrary.simpleMessage("Last update"),
|
||||
"later" : MessageLookupByLibrary.simpleMessage("Mais tarde"),
|
||||
"lightMode" : MessageLookupByLibrary.simpleMessage("Modo claro"),
|
||||
"like" : MessageLookupByLibrary.simpleMessage("Gosto"),
|
||||
"likeDate" : MessageLookupByLibrary.simpleMessage("Data do Gosto"),
|
||||
"liked" : MessageLookupByLibrary.simpleMessage("Gostou"),
|
||||
"listen" : MessageLookupByLibrary.simpleMessage("Ouvir"),
|
||||
"listened" : MessageLookupByLibrary.simpleMessage("Ouvido"),
|
||||
"loadMore" : MessageLookupByLibrary.simpleMessage("Carregar mais"),
|
||||
"loggedInAs" : m11,
|
||||
"login" : MessageLookupByLibrary.simpleMessage("Login"),
|
||||
"loginFailed" : MessageLookupByLibrary.simpleMessage("Login failed"),
|
||||
"logout" : MessageLookupByLibrary.simpleMessage("Logout"),
|
||||
"mark" : MessageLookupByLibrary.simpleMessage("Marcar"),
|
||||
"markConfirm" : MessageLookupByLibrary.simpleMessage("Confirmar marca"),
|
||||
"markConfirmContent" : MessageLookupByLibrary.simpleMessage("Marcar todos os episódios como ouvidos?"),
|
||||
"markListened" : MessageLookupByLibrary.simpleMessage("Marcar como ouvido"),
|
||||
"markNotListened" : MessageLookupByLibrary.simpleMessage("Marcar não ouvidos"),
|
||||
"menu" : MessageLookupByLibrary.simpleMessage("Menu"),
|
||||
"menuAllPodcasts" : MessageLookupByLibrary.simpleMessage("Todos os podcasts"),
|
||||
"menuMarkAllListened" : MessageLookupByLibrary.simpleMessage("Marcar todos como ouvidos"),
|
||||
"menuViewRSS" : MessageLookupByLibrary.simpleMessage("Visitar Feed RSS"),
|
||||
"menuVisitSite" : MessageLookupByLibrary.simpleMessage("Visitar website"),
|
||||
"minsAgo" : m12,
|
||||
"minsCount" : m13,
|
||||
"network" : MessageLookupByLibrary.simpleMessage("Rede"),
|
||||
"newGroup" : MessageLookupByLibrary.simpleMessage("Criar um novo grupo"),
|
||||
"newestFirst" : MessageLookupByLibrary.simpleMessage("Mais recentes primeiro"),
|
||||
"next" : MessageLookupByLibrary.simpleMessage("Seguinte"),
|
||||
"noEpisodeDownload" : MessageLookupByLibrary.simpleMessage("Ainda não há episódios descarregados"),
|
||||
"noEpisodeFavorite" : MessageLookupByLibrary.simpleMessage("Ainda não há episódios coletados"),
|
||||
"noEpisodeRecent" : MessageLookupByLibrary.simpleMessage("Ainda não há episódios recebidos"),
|
||||
"noPodcastGroup" : MessageLookupByLibrary.simpleMessage("Não há podcasts neste grupo"),
|
||||
"noShownote" : MessageLookupByLibrary.simpleMessage("Não há notas disponíveis para este episódio"),
|
||||
"notificaitonFatch" : m14,
|
||||
"notificationNetworkError" : m15,
|
||||
"notificationSetting" : MessageLookupByLibrary.simpleMessage("Painel de notificações"),
|
||||
"notificationSubscribe" : m16,
|
||||
"notificationSubscribeExisted" : m17,
|
||||
"notificationSuccess" : m18,
|
||||
"notificationUpdate" : m19,
|
||||
"notificationUpdateError" : m20,
|
||||
"oldestFirst" : MessageLookupByLibrary.simpleMessage("Mais antigos primeiro"),
|
||||
"passwdRequired" : MessageLookupByLibrary.simpleMessage("Password required"),
|
||||
"password" : MessageLookupByLibrary.simpleMessage("Password"),
|
||||
"pause" : MessageLookupByLibrary.simpleMessage("Pausa"),
|
||||
"play" : MessageLookupByLibrary.simpleMessage("Reproduzir"),
|
||||
"playback" : MessageLookupByLibrary.simpleMessage("Controlo da reprodução"),
|
||||
"player" : MessageLookupByLibrary.simpleMessage("Reprodutor"),
|
||||
"playerHeightMed" : MessageLookupByLibrary.simpleMessage("Médio"),
|
||||
"playerHeightShort" : MessageLookupByLibrary.simpleMessage("Baixo"),
|
||||
"playerHeightTall" : MessageLookupByLibrary.simpleMessage("Alto"),
|
||||
"playing" : MessageLookupByLibrary.simpleMessage("Em reprodução"),
|
||||
"plugins" : MessageLookupByLibrary.simpleMessage("Plugins"),
|
||||
"podcast" : m21,
|
||||
"podcastSubscribed" : MessageLookupByLibrary.simpleMessage("Podcast subscrito"),
|
||||
"popupMenuDownloadDes" : MessageLookupByLibrary.simpleMessage("Descarregar episódio"),
|
||||
"popupMenuLaterDes" : MessageLookupByLibrary.simpleMessage("Adicionar episódio à lista de reprodução"),
|
||||
"popupMenuLikeDes" : MessageLookupByLibrary.simpleMessage("Adicionar episódio aos favoritos"),
|
||||
"popupMenuMarkDes" : MessageLookupByLibrary.simpleMessage("Marcar episódio como ouvido"),
|
||||
"popupMenuPlayDes" : MessageLookupByLibrary.simpleMessage("Reproduzir episódio"),
|
||||
"privacyPolicy" : MessageLookupByLibrary.simpleMessage("Política de Privacidade"),
|
||||
"published" : m22,
|
||||
"publishedDaily" : MessageLookupByLibrary.simpleMessage("Publicado diariamente"),
|
||||
"publishedMonthly" : MessageLookupByLibrary.simpleMessage("Publicado mensalmente"),
|
||||
"publishedWeekly" : MessageLookupByLibrary.simpleMessage("Publicado semanalmente"),
|
||||
"publishedYearly" : MessageLookupByLibrary.simpleMessage("Publicado anualmente"),
|
||||
"recoverSubscribe" : MessageLookupByLibrary.simpleMessage("Recuperar subscrição"),
|
||||
"refreshArtwork" : MessageLookupByLibrary.simpleMessage("Atualizar capa"),
|
||||
"remove" : MessageLookupByLibrary.simpleMessage("Remover"),
|
||||
"removeConfirm" : MessageLookupByLibrary.simpleMessage("Confirmação de remoção"),
|
||||
"removePodcastDes" : MessageLookupByLibrary.simpleMessage("Tens a certeza que pretendes cancelar a subscrição?"),
|
||||
"removedAt" : m23,
|
||||
"save" : MessageLookupByLibrary.simpleMessage("Guardar"),
|
||||
"schedule" : MessageLookupByLibrary.simpleMessage("Horário"),
|
||||
"search" : MessageLookupByLibrary.simpleMessage("Procurar"),
|
||||
"searchEpisode" : MessageLookupByLibrary.simpleMessage("Procurar episódio"),
|
||||
"searchInvalidRss" : MessageLookupByLibrary.simpleMessage("Ligação RSS inválida"),
|
||||
"searchPodcast" : MessageLookupByLibrary.simpleMessage("Procurar podcasts"),
|
||||
"secCount" : m24,
|
||||
"secondsAgo" : m25,
|
||||
"settingStorage" : MessageLookupByLibrary.simpleMessage("Armazenamento"),
|
||||
"settings" : MessageLookupByLibrary.simpleMessage("Definições"),
|
||||
"settingsAccentColor" : MessageLookupByLibrary.simpleMessage("Cor de realce"),
|
||||
"settingsAccentColorDes" : MessageLookupByLibrary.simpleMessage("Incluir cor de sobreposição"),
|
||||
"settingsAppIntro" : MessageLookupByLibrary.simpleMessage("Introdução da Aplicação"),
|
||||
"settingsAppearance" : MessageLookupByLibrary.simpleMessage("Aparência"),
|
||||
"settingsAppearanceDes" : MessageLookupByLibrary.simpleMessage("Cores e temas"),
|
||||
"settingsAudioCache" : MessageLookupByLibrary.simpleMessage("Cache de áudio"),
|
||||
"settingsAudioCacheDes" : MessageLookupByLibrary.simpleMessage("Tamanho máximo da cache de áudio"),
|
||||
"settingsAutoDelete" : MessageLookupByLibrary.simpleMessage("Eliminar downloads automaticamente após"),
|
||||
"settingsAutoDeleteDes" : MessageLookupByLibrary.simpleMessage("30 dias por defeito"),
|
||||
"settingsAutoPlayDes" : MessageLookupByLibrary.simpleMessage("Reproduzir automaticamente o episódio seguinte"),
|
||||
"settingsBackup" : MessageLookupByLibrary.simpleMessage("Cópia de segurança"),
|
||||
"settingsBackupDes" : MessageLookupByLibrary.simpleMessage("Cópia de segurança dos dados da aplicação"),
|
||||
"settingsBoostVolume" : MessageLookupByLibrary.simpleMessage("Nível de aumento de volume"),
|
||||
"settingsBoostVolumeDes" : MessageLookupByLibrary.simpleMessage("Alterar nível de aumento de volume"),
|
||||
"settingsDefaultGrid" : MessageLookupByLibrary.simpleMessage("Vista de grelha predefinida"),
|
||||
"settingsDefaultGridDownload" : MessageLookupByLibrary.simpleMessage("Aba de downloads"),
|
||||
"settingsDefaultGridFavorite" : MessageLookupByLibrary.simpleMessage("Aba de favoritos"),
|
||||
"settingsDefaultGridPodcast" : MessageLookupByLibrary.simpleMessage("Página de podcasts"),
|
||||
"settingsDefaultGridRecent" : MessageLookupByLibrary.simpleMessage("Aba de recentes"),
|
||||
"settingsDiscovery" : MessageLookupByLibrary.simpleMessage("Reiniciar tutorial"),
|
||||
"settingsEnableSyncing" : MessageLookupByLibrary.simpleMessage("Ativar sincronização"),
|
||||
"settingsEnableSyncingDes" : MessageLookupByLibrary.simpleMessage("Atualizar todos os podcasts em segundo plano para obter os episódios mais recentes"),
|
||||
"settingsExportDes" : MessageLookupByLibrary.simpleMessage("Exportar e importar definições da aplicação"),
|
||||
"settingsFastForwardSec" : MessageLookupByLibrary.simpleMessage("Avançar segundos"),
|
||||
"settingsFastForwardSecDes" : MessageLookupByLibrary.simpleMessage("Muda os segundos de avanço no reprodutor"),
|
||||
"settingsFeedback" : MessageLookupByLibrary.simpleMessage("Feedback"),
|
||||
"settingsFeedbackDes" : MessageLookupByLibrary.simpleMessage("Erros e sugestões"),
|
||||
"settingsHistory" : MessageLookupByLibrary.simpleMessage("Histórico"),
|
||||
"settingsHistoryDes" : MessageLookupByLibrary.simpleMessage("Dados de audição"),
|
||||
"settingsInfo" : MessageLookupByLibrary.simpleMessage("Informações"),
|
||||
"settingsInterface" : MessageLookupByLibrary.simpleMessage("Interface"),
|
||||
"settingsLanguages" : MessageLookupByLibrary.simpleMessage("Idiomas"),
|
||||
"settingsLanguagesDes" : MessageLookupByLibrary.simpleMessage("Mudar idioma"),
|
||||
"settingsLayout" : MessageLookupByLibrary.simpleMessage("Esquema"),
|
||||
"settingsLayoutDes" : MessageLookupByLibrary.simpleMessage("Esquema da aplicação"),
|
||||
"settingsLibraries" : MessageLookupByLibrary.simpleMessage("Bibliotecas"),
|
||||
"settingsLibrariesDes" : MessageLookupByLibrary.simpleMessage("Bibliotecas de código aberto usados nesta aplicação"),
|
||||
"settingsManageDownload" : MessageLookupByLibrary.simpleMessage("Gerir downloads"),
|
||||
"settingsManageDownloadDes" : MessageLookupByLibrary.simpleMessage("Gerir arquivos de aúdio descarregados"),
|
||||
"settingsMenuAutoPlay" : MessageLookupByLibrary.simpleMessage("Reproduzir seguinte automaticamente"),
|
||||
"settingsNetworkCellular" : MessageLookupByLibrary.simpleMessage("Perguntar antes de usar dados móveis"),
|
||||
"settingsNetworkCellularAuto" : MessageLookupByLibrary.simpleMessage("Descarregar automaticamente usando os dados móveis"),
|
||||
"settingsNetworkCellularAutoDes" : MessageLookupByLibrary.simpleMessage("Podes configurar o descarregamento automático na página de gestão de grupos"),
|
||||
"settingsNetworkCellularDes" : MessageLookupByLibrary.simpleMessage("Perguntar a confirmar o uso de dados móveis ao descarregar episódios"),
|
||||
"settingsPlayDes" : MessageLookupByLibrary.simpleMessage("Lista de reprodução e reprodutor"),
|
||||
"settingsPlayerHeight" : MessageLookupByLibrary.simpleMessage("Altura do reprodutor"),
|
||||
"settingsPlayerHeightDes" : MessageLookupByLibrary.simpleMessage("Mudar a altura do reprodutor a teu gosto"),
|
||||
"settingsPopupMenu" : MessageLookupByLibrary.simpleMessage("Menu pop-up de episódios"),
|
||||
"settingsPopupMenuDes" : MessageLookupByLibrary.simpleMessage("Muda o menu pop-up de episódios"),
|
||||
"settingsPrefrence" : MessageLookupByLibrary.simpleMessage("Preferências"),
|
||||
"settingsRealDark" : MessageLookupByLibrary.simpleMessage("Escuro AMOLED"),
|
||||
"settingsRealDarkDes" : MessageLookupByLibrary.simpleMessage("Ativa caso o modo escuro não seja suficientemente escuro"),
|
||||
"settingsRewindSec" : MessageLookupByLibrary.simpleMessage("Segundos de recuo"),
|
||||
"settingsRewindSecDes" : MessageLookupByLibrary.simpleMessage("Muda os segundos de recuo no reprodutor"),
|
||||
"settingsSTAuto" : MessageLookupByLibrary.simpleMessage("Ligar temporizador automaticamente"),
|
||||
"settingsSTAutoDes" : MessageLookupByLibrary.simpleMessage("Ligar temporizador automaticamente num horário definido"),
|
||||
"settingsSTDefaultTime" : MessageLookupByLibrary.simpleMessage("Tempo predefinido"),
|
||||
"settingsSTDefautTimeDes" : MessageLookupByLibrary.simpleMessage("Tempo predefinido para temporizador"),
|
||||
"settingsSTMode" : MessageLookupByLibrary.simpleMessage("Modo de temporizador automático"),
|
||||
"settingsSpeeds" : MessageLookupByLibrary.simpleMessage("Velocidades"),
|
||||
"settingsSpeedsDes" : MessageLookupByLibrary.simpleMessage("Customizar as velocidades disponíveis"),
|
||||
"settingsStorageDes" : MessageLookupByLibrary.simpleMessage("Gerir cache e armazenamento de downloads"),
|
||||
"settingsSyncing" : MessageLookupByLibrary.simpleMessage("Sincronização"),
|
||||
"settingsSyncingDes" : MessageLookupByLibrary.simpleMessage("Atualizar podcasts em segundo plano"),
|
||||
"settingsTapToOpenPopupMenu" : MessageLookupByLibrary.simpleMessage("Prime para abrir o menu pop-up"),
|
||||
"settingsTapToOpenPopupMenuDes" : MessageLookupByLibrary.simpleMessage("Precisas manter premido para abrir a página do episódio"),
|
||||
"settingsTheme" : MessageLookupByLibrary.simpleMessage("Tema"),
|
||||
"settingsUpdateInterval" : MessageLookupByLibrary.simpleMessage("Intervalo de atualização"),
|
||||
"settingsUpdateIntervalDes" : MessageLookupByLibrary.simpleMessage("24 horas predefinidas"),
|
||||
"share" : MessageLookupByLibrary.simpleMessage("Partilhar"),
|
||||
"showNotesFonts" : MessageLookupByLibrary.simpleMessage("Mostrar tipo de letra das notas"),
|
||||
"size" : MessageLookupByLibrary.simpleMessage("Tamanho"),
|
||||
"skipSecondsAtEnd" : MessageLookupByLibrary.simpleMessage("Saltar segundos no fim"),
|
||||
"skipSecondsAtStart" : MessageLookupByLibrary.simpleMessage("Saltar segundos no início"),
|
||||
"skipSilence" : MessageLookupByLibrary.simpleMessage("Saltar silêncio"),
|
||||
"skipToNext" : MessageLookupByLibrary.simpleMessage("Saltar para o próximo"),
|
||||
"sleepTimer" : MessageLookupByLibrary.simpleMessage("Temporizador"),
|
||||
"status" : MessageLookupByLibrary.simpleMessage("Status"),
|
||||
"stop" : MessageLookupByLibrary.simpleMessage("Parar"),
|
||||
"subscribe" : MessageLookupByLibrary.simpleMessage("Subscrever"),
|
||||
"subscribeExportDes" : MessageLookupByLibrary.simpleMessage("Exportar ficheiro OPML de todos os podcasts"),
|
||||
"syncNow" : MessageLookupByLibrary.simpleMessage("Sync now"),
|
||||
"systemDefault" : MessageLookupByLibrary.simpleMessage("Predefinido do sistema"),
|
||||
"timeLastPlayed" : m26,
|
||||
"timeLeft" : m27,
|
||||
"to" : m28,
|
||||
"toastAddPlaylist" : MessageLookupByLibrary.simpleMessage("Adicionado à lista de reprodução"),
|
||||
"toastDiscovery" : MessageLookupByLibrary.simpleMessage("Característica \"Descobrir\" ligada, por favor reinicia a aplicação"),
|
||||
"toastFileError" : MessageLookupByLibrary.simpleMessage("Erro no ficheiro, subscrição falhou"),
|
||||
"toastFileNotValid" : MessageLookupByLibrary.simpleMessage("Ficheiro inválido"),
|
||||
"toastHomeGroupNotSupport" : MessageLookupByLibrary.simpleMessage("Grupo Home não é suportado"),
|
||||
"toastImportSettingsSuccess" : MessageLookupByLibrary.simpleMessage("Definições importadas com sucesso"),
|
||||
"toastOneGroup" : MessageLookupByLibrary.simpleMessage("Seleciona pelo menos um grupo"),
|
||||
"toastPodcastRecovering" : MessageLookupByLibrary.simpleMessage("A recuperar, espera um momento"),
|
||||
"toastReadFile" : MessageLookupByLibrary.simpleMessage("Ficheiro lido com sucesso"),
|
||||
"toastRecoverFailed" : MessageLookupByLibrary.simpleMessage("Recuperação do podcast falhou"),
|
||||
"toastRemovePlaylist" : MessageLookupByLibrary.simpleMessage("Episódio removido da lista de reprodução"),
|
||||
"toastSettingSaved" : MessageLookupByLibrary.simpleMessage("Definições guardadas"),
|
||||
"toastTimeEqualEnd" : MessageLookupByLibrary.simpleMessage("Tempo marcado é igual ao tempo de fim"),
|
||||
"toastTimeEqualStart" : MessageLookupByLibrary.simpleMessage("Tempo marcado é igual ao tempo de início"),
|
||||
"translators" : MessageLookupByLibrary.simpleMessage("Tradutores"),
|
||||
"understood" : MessageLookupByLibrary.simpleMessage("Compreendido"),
|
||||
"undo" : MessageLookupByLibrary.simpleMessage("DESFAZER"),
|
||||
"unlike" : MessageLookupByLibrary.simpleMessage("Não gosto"),
|
||||
"unliked" : MessageLookupByLibrary.simpleMessage("Episódio removido dos favoritos"),
|
||||
"updateDate" : MessageLookupByLibrary.simpleMessage("Atualizar data"),
|
||||
"updateEpisodesCount" : m29,
|
||||
"updateFailed" : MessageLookupByLibrary.simpleMessage("Atuallização falhou, erro de conexão"),
|
||||
"username" : MessageLookupByLibrary.simpleMessage("Username"),
|
||||
"usernameRequired" : MessageLookupByLibrary.simpleMessage("Username requeired"),
|
||||
"version" : m30
|
||||
};
|
||||
}
|
@ -39,43 +39,47 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
|
||||
static m9(count) => "${Intl.plural(count, zero: '0小时', other: '${count} 小时')}";
|
||||
|
||||
static m10(count) => "${Intl.plural(count, zero: '刚刚', other: '${count}分钟前')}";
|
||||
static m10(service) => "绑定 ${service}";
|
||||
|
||||
static m11(count) => "${Intl.plural(count, zero: '0分钟', other: '${count}分钟')}";
|
||||
static m11(userName) => "使用${userName}登入";
|
||||
|
||||
static m12(title) => "获取数据 ${title}";
|
||||
static m12(count) => "${Intl.plural(count, zero: '刚刚', other: '${count}分钟前')}";
|
||||
|
||||
static m13(title) => "订阅失败,网络错误 ${title}";
|
||||
static m13(count) => "${Intl.plural(count, zero: '0分钟', other: '${count}分钟')}";
|
||||
|
||||
static m14(title) => "订阅 ${title}";
|
||||
static m14(title) => "获取数据 ${title}";
|
||||
|
||||
static m15(title) => "订阅失败,播客已存在 ${title}";
|
||||
static m15(title) => "订阅失败,网络错误 ${title}";
|
||||
|
||||
static m16(title) => "订阅成功 ${title}";
|
||||
static m16(title) => "订阅 ${title}";
|
||||
|
||||
static m17(title) => "更新 ${title}";
|
||||
static m17(title) => "订阅失败,播客已存在 ${title}";
|
||||
|
||||
static m18(title) => "更新失败 ${title}";
|
||||
static m18(title) => "订阅成功 ${title}";
|
||||
|
||||
static m19(count) => "${Intl.plural(count, zero: '', other: '播客')}";
|
||||
static m19(title) => "更新 ${title}";
|
||||
|
||||
static m20(date) => "${date}上线";
|
||||
static m20(title) => "更新失败 ${title}";
|
||||
|
||||
static m21(date) => "${date}移除";
|
||||
static m21(count) => "${Intl.plural(count, zero: '', other: '播客')}";
|
||||
|
||||
static m22(count) => "${Intl.plural(count, zero: '0 秒', other: '${count} 秒')}";
|
||||
static m22(date) => "${date}上线";
|
||||
|
||||
static m23(count) => "${Intl.plural(count, zero: '刚刚', other: '${count}秒前')}";
|
||||
static m23(date) => "${date}移除";
|
||||
|
||||
static m24(time) => "上次播放${time}";
|
||||
static m24(count) => "${Intl.plural(count, zero: '0 秒', other: '${count} 秒')}";
|
||||
|
||||
static m25(time) => "剩余 ${time}";
|
||||
static m25(count) => "${Intl.plural(count, zero: '刚刚', other: '${count}秒前')}";
|
||||
|
||||
static m26(time) => "到${time}";
|
||||
static m26(time) => "上次播放${time}";
|
||||
|
||||
static m27(count) => "${Intl.plural(count, zero: '未有更新', other: '更新 ${count} 集节目')}";
|
||||
static m27(time) => "剩余 ${time}";
|
||||
|
||||
static m28(version) => "版本:${version}";
|
||||
static m28(time) => "到${time}";
|
||||
|
||||
static m29(count) => "${Intl.plural(count, zero: '未有更新', other: '更新 ${count} 集节目')}";
|
||||
|
||||
static m30(version) => "版本:${version}";
|
||||
|
||||
final messages = _notInlinedMessages(_notInlinedMessages);
|
||||
static _notInlinedMessages(_) => <String, Function> {
|
||||
@ -140,6 +144,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"fonts" : MessageLookupByLibrary.simpleMessage("字体"),
|
||||
"from" : m5,
|
||||
"goodNight" : MessageLookupByLibrary.simpleMessage("晚安"),
|
||||
"gpodderLoginDes" : MessageLookupByLibrary.simpleMessage("恭喜!您已经成功绑定 gpodder.net 账号,Tsacdop 将会自动同步您的订阅到 gpodder.net 账户。"),
|
||||
"groupExisted" : MessageLookupByLibrary.simpleMessage("组名已使用"),
|
||||
"groupFilter" : MessageLookupByLibrary.simpleMessage("分组"),
|
||||
"groupRemoveConfirm" : MessageLookupByLibrary.simpleMessage("您确认要移除该分组吗?播客将被移动到 Home 分组。"),
|
||||
@ -157,9 +162,12 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"hoursAgo" : m8,
|
||||
"hoursCount" : m9,
|
||||
"import" : MessageLookupByLibrary.simpleMessage("导入"),
|
||||
"intergateWith" : m10,
|
||||
"introFourthPage" : MessageLookupByLibrary.simpleMessage("您可以长按节目打开快捷菜单。"),
|
||||
"introSecondPage" : MessageLookupByLibrary.simpleMessage("您可以通过搜索订阅播客,也可以直接导入OPML文件。"),
|
||||
"introThirdPage" : MessageLookupByLibrary.simpleMessage("您可以创建分组,上下滑动切换分组。"),
|
||||
"invalidName" : MessageLookupByLibrary.simpleMessage("用户名错误"),
|
||||
"lastUpdate" : MessageLookupByLibrary.simpleMessage("最近更新"),
|
||||
"later" : MessageLookupByLibrary.simpleMessage("稍后"),
|
||||
"lightMode" : MessageLookupByLibrary.simpleMessage("明亮模式"),
|
||||
"like" : MessageLookupByLibrary.simpleMessage("喜欢"),
|
||||
@ -168,6 +176,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"listen" : MessageLookupByLibrary.simpleMessage("收听"),
|
||||
"listened" : MessageLookupByLibrary.simpleMessage("已收听"),
|
||||
"loadMore" : MessageLookupByLibrary.simpleMessage("加载更多"),
|
||||
"loggedInAs" : m11,
|
||||
"login" : MessageLookupByLibrary.simpleMessage("登入"),
|
||||
"loginFailed" : MessageLookupByLibrary.simpleMessage("登入失败"),
|
||||
"logout" : MessageLookupByLibrary.simpleMessage("注销"),
|
||||
"mark" : MessageLookupByLibrary.simpleMessage("标记"),
|
||||
"markConfirm" : MessageLookupByLibrary.simpleMessage("确认标记"),
|
||||
"markConfirmContent" : MessageLookupByLibrary.simpleMessage("是否确认标记全部节目为已收听?"),
|
||||
@ -178,8 +190,8 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"menuMarkAllListened" : MessageLookupByLibrary.simpleMessage("标记所有已收听"),
|
||||
"menuViewRSS" : MessageLookupByLibrary.simpleMessage("查看 RSS"),
|
||||
"menuVisitSite" : MessageLookupByLibrary.simpleMessage("访问网站"),
|
||||
"minsAgo" : m10,
|
||||
"minsCount" : m11,
|
||||
"minsAgo" : m12,
|
||||
"minsCount" : m13,
|
||||
"network" : MessageLookupByLibrary.simpleMessage("网络"),
|
||||
"newGroup" : MessageLookupByLibrary.simpleMessage("创建分组"),
|
||||
"newestFirst" : MessageLookupByLibrary.simpleMessage("由新到旧"),
|
||||
@ -189,15 +201,17 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"noEpisodeRecent" : MessageLookupByLibrary.simpleMessage("暂无节目"),
|
||||
"noPodcastGroup" : MessageLookupByLibrary.simpleMessage("分组无播客"),
|
||||
"noShownote" : MessageLookupByLibrary.simpleMessage("节目简介暂未收到。"),
|
||||
"notificaitonFatch" : m12,
|
||||
"notificationNetworkError" : m13,
|
||||
"notificaitonFatch" : m14,
|
||||
"notificationNetworkError" : m15,
|
||||
"notificationSetting" : MessageLookupByLibrary.simpleMessage("通知栏"),
|
||||
"notificationSubscribe" : m14,
|
||||
"notificationSubscribeExisted" : m15,
|
||||
"notificationSuccess" : m16,
|
||||
"notificationUpdate" : m17,
|
||||
"notificationUpdateError" : m18,
|
||||
"notificationSubscribe" : m16,
|
||||
"notificationSubscribeExisted" : m17,
|
||||
"notificationSuccess" : m18,
|
||||
"notificationUpdate" : m19,
|
||||
"notificationUpdateError" : m20,
|
||||
"oldestFirst" : MessageLookupByLibrary.simpleMessage("由旧到新"),
|
||||
"passwdRequired" : MessageLookupByLibrary.simpleMessage("密码为空"),
|
||||
"password" : MessageLookupByLibrary.simpleMessage("密码"),
|
||||
"pause" : MessageLookupByLibrary.simpleMessage("暂停"),
|
||||
"play" : MessageLookupByLibrary.simpleMessage("播放"),
|
||||
"playback" : MessageLookupByLibrary.simpleMessage("播放控制"),
|
||||
@ -207,7 +221,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"playerHeightTall" : MessageLookupByLibrary.simpleMessage("高"),
|
||||
"playing" : MessageLookupByLibrary.simpleMessage("正在播放"),
|
||||
"plugins" : MessageLookupByLibrary.simpleMessage("插件"),
|
||||
"podcast" : m19,
|
||||
"podcast" : m21,
|
||||
"podcastSubscribed" : MessageLookupByLibrary.simpleMessage("播客已订阅"),
|
||||
"popupMenuDownloadDes" : MessageLookupByLibrary.simpleMessage("下载节目"),
|
||||
"popupMenuLaterDes" : MessageLookupByLibrary.simpleMessage("添加到播放列表"),
|
||||
@ -215,7 +229,7 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"popupMenuMarkDes" : MessageLookupByLibrary.simpleMessage("设置为已收听"),
|
||||
"popupMenuPlayDes" : MessageLookupByLibrary.simpleMessage("播放节目"),
|
||||
"privacyPolicy" : MessageLookupByLibrary.simpleMessage("隐私条款"),
|
||||
"published" : m20,
|
||||
"published" : m22,
|
||||
"publishedDaily" : MessageLookupByLibrary.simpleMessage("每日更新"),
|
||||
"publishedMonthly" : MessageLookupByLibrary.simpleMessage("每月更新"),
|
||||
"publishedWeekly" : MessageLookupByLibrary.simpleMessage("每周更新"),
|
||||
@ -225,15 +239,15 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"remove" : MessageLookupByLibrary.simpleMessage("移除"),
|
||||
"removeConfirm" : MessageLookupByLibrary.simpleMessage("取消订阅"),
|
||||
"removePodcastDes" : MessageLookupByLibrary.simpleMessage("您确认要取消订阅吗?"),
|
||||
"removedAt" : m21,
|
||||
"removedAt" : m23,
|
||||
"save" : MessageLookupByLibrary.simpleMessage("保存"),
|
||||
"schedule" : MessageLookupByLibrary.simpleMessage("定时"),
|
||||
"search" : MessageLookupByLibrary.simpleMessage("搜索"),
|
||||
"searchEpisode" : MessageLookupByLibrary.simpleMessage("搜索节目"),
|
||||
"searchInvalidRss" : MessageLookupByLibrary.simpleMessage("RSS 链接错误"),
|
||||
"searchPodcast" : MessageLookupByLibrary.simpleMessage("搜索播客"),
|
||||
"secCount" : m22,
|
||||
"secondsAgo" : m23,
|
||||
"secCount" : m24,
|
||||
"secondsAgo" : m25,
|
||||
"settingStorage" : MessageLookupByLibrary.simpleMessage("储存空间"),
|
||||
"settings" : MessageLookupByLibrary.simpleMessage("设置"),
|
||||
"settingsAccentColor" : MessageLookupByLibrary.simpleMessage("次要颜色"),
|
||||
@ -313,13 +327,15 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"skipSilence" : MessageLookupByLibrary.simpleMessage("跳过无声"),
|
||||
"skipToNext" : MessageLookupByLibrary.simpleMessage("下一首"),
|
||||
"sleepTimer" : MessageLookupByLibrary.simpleMessage("睡眠模式"),
|
||||
"status" : MessageLookupByLibrary.simpleMessage("状态"),
|
||||
"stop" : MessageLookupByLibrary.simpleMessage("停止"),
|
||||
"subscribe" : MessageLookupByLibrary.simpleMessage("订阅"),
|
||||
"subscribeExportDes" : MessageLookupByLibrary.simpleMessage("导出 OPML 文件"),
|
||||
"syncNow" : MessageLookupByLibrary.simpleMessage("立即同步"),
|
||||
"systemDefault" : MessageLookupByLibrary.simpleMessage("系统默认"),
|
||||
"timeLastPlayed" : m24,
|
||||
"timeLeft" : m25,
|
||||
"to" : m26,
|
||||
"timeLastPlayed" : m26,
|
||||
"timeLeft" : m27,
|
||||
"to" : m28,
|
||||
"toastAddPlaylist" : MessageLookupByLibrary.simpleMessage("添加到播放列表"),
|
||||
"toastDiscovery" : MessageLookupByLibrary.simpleMessage("重启应用后可查看"),
|
||||
"toastFileError" : MessageLookupByLibrary.simpleMessage("文件错误,导入失败"),
|
||||
@ -340,8 +356,10 @@ class MessageLookup extends MessageLookupByLibrary {
|
||||
"unlike" : MessageLookupByLibrary.simpleMessage("取消喜欢"),
|
||||
"unliked" : MessageLookupByLibrary.simpleMessage("从收藏移除"),
|
||||
"updateDate" : MessageLookupByLibrary.simpleMessage("更新日期"),
|
||||
"updateEpisodesCount" : m27,
|
||||
"updateEpisodesCount" : m29,
|
||||
"updateFailed" : MessageLookupByLibrary.simpleMessage("更新失败"),
|
||||
"version" : m28
|
||||
"username" : MessageLookupByLibrary.simpleMessage("用户名"),
|
||||
"usernameRequired" : MessageLookupByLibrary.simpleMessage("用户名为空"),
|
||||
"version" : m30
|
||||
};
|
||||
}
|
||||
|
@ -11,7 +11,7 @@ import 'package:line_icons/line_icons.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../service/ompl_build.dart';
|
||||
import '../service/opml_build.dart';
|
||||
import '../settings/settting.dart';
|
||||
import '../state/podcast_group.dart';
|
||||
import '../state/refresh_podcast.dart';
|
||||
@ -58,7 +58,8 @@ class _PopupMenuState extends State<PopupMenu> {
|
||||
final s = context.s;
|
||||
var file = File(path);
|
||||
try {
|
||||
Map<String, List<OmplOutline>> data = PodcastsBackup.parseOMPL(file);
|
||||
final opml = file.readAsStringSync();
|
||||
Map<String, List<OmplOutline>> data = PodcastsBackup.parseOMPL(opml);
|
||||
for (var entry in data.entries) {
|
||||
var title = entry.key;
|
||||
var list = entry.value.reversed;
|
||||
@ -83,14 +84,16 @@ class _PopupMenuState extends State<PopupMenu> {
|
||||
void _getFilePath() async {
|
||||
final s = context.s;
|
||||
try {
|
||||
var filePath = await FilePicker.getFilePath(type: FileType.any);
|
||||
if (filePath == '') {
|
||||
var filePickResult =
|
||||
await FilePicker.platform.pickFiles(type: FileType.any);
|
||||
if (filePickResult == null) {
|
||||
return;
|
||||
}
|
||||
Fluttertoast.showToast(
|
||||
msg: s.toastReadFile,
|
||||
gravity: ToastGravity.TOP,
|
||||
);
|
||||
final filePath = filePickResult.files.first.path;
|
||||
_saveOmpl(filePath);
|
||||
} on PlatformException catch (e) {
|
||||
developer.log(e.toString(), name: 'Get OMPL file');
|
||||
|
@ -2,10 +2,10 @@ import 'package:flutter/material.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../service/api_search.dart';
|
||||
import '../service/search_api.dart';
|
||||
import '../state/search_state.dart';
|
||||
import '../type/search_genre.dart';
|
||||
import '../type/searchpodcast.dart';
|
||||
import '../type/search_api/search_genre.dart';
|
||||
import '../type/search_api/searchpodcast.dart';
|
||||
import '../util/custom_widget.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import 'search_podcast.dart';
|
||||
@ -113,7 +113,7 @@ class DiscoveryPageState extends State<DiscoveryPage> {
|
||||
));
|
||||
|
||||
Future<List<OnlinePodcast>> _getTopPodcasts({int page}) async {
|
||||
final searchEngine = SearchEngine();
|
||||
final searchEngine = ListenNotesSearch();
|
||||
var searchResult = await searchEngine.fetchBestPodcast(
|
||||
genre: '',
|
||||
page: page,
|
||||
@ -141,13 +141,12 @@ class DiscoveryPageState extends State<DiscoveryPage> {
|
||||
if (snapshot.hasData && snapshot.data.isNotEmpty) {
|
||||
final history = snapshot.data;
|
||||
return SizedBox(
|
||||
height: 50,
|
||||
child: Wrap(
|
||||
direction: Axis.horizontal,
|
||||
children: history
|
||||
.map<Widget>((e) => Padding(
|
||||
padding: const EdgeInsets.fromLTRB(
|
||||
8, 8, 0, 8),
|
||||
8, 2, 0, 0),
|
||||
child: FlatButton.icon(
|
||||
color: Colors
|
||||
.accents[history.indexOf(e)]
|
||||
@ -159,7 +158,7 @@ class DiscoveryPageState extends State<DiscoveryPage> {
|
||||
onPressed: () => widget.onTap(e),
|
||||
label: Text(e),
|
||||
icon: Icon(
|
||||
Icons.bookmark_border,
|
||||
Icons.search,
|
||||
size: 20,
|
||||
),
|
||||
),
|
||||
@ -313,7 +312,7 @@ class __TopPodcastListState extends State<_TopPodcastList> {
|
||||
bool _loading;
|
||||
int _page;
|
||||
Future<List<OnlinePodcast>> _getTopPodcasts({Genre genre, int page}) async {
|
||||
final searchEngine = SearchEngine();
|
||||
final searchEngine = ListenNotesSearch();
|
||||
var searchResult = await searchEngine.fetchBestPodcast(
|
||||
genre: genre.id,
|
||||
page: page,
|
||||
|
@ -11,11 +11,11 @@ import 'package:provider/provider.dart';
|
||||
import 'package:webfeed/webfeed.dart';
|
||||
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../service/api_search.dart';
|
||||
import '../service/search_api.dart';
|
||||
import '../state/podcast_group.dart';
|
||||
import '../state/search_state.dart';
|
||||
import '../type/searchepisodes.dart';
|
||||
import '../type/searchpodcast.dart';
|
||||
import '../type/search_api/searchepisodes.dart';
|
||||
import '../type/search_api/searchpodcast.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
import 'pocast_discovery.dart';
|
||||
|
||||
@ -359,7 +359,7 @@ class _SearchListState extends State<SearchList> {
|
||||
Future<List<OnlinePodcast>> _getList(
|
||||
String searchText, int nextOffset) async {
|
||||
if (nextOffset == 0) _saveHistory(searchText);
|
||||
final searchEngine = SearchEngine();
|
||||
final searchEngine = ListenNotesSearch();
|
||||
var searchResult = await searchEngine.searchPodcasts(
|
||||
searchText: searchText, nextOffset: nextOffset);
|
||||
_offset = searchResult.nextOffset;
|
||||
@ -562,7 +562,7 @@ class _SearchResultDetailState extends State<SearchResultDetail>
|
||||
|
||||
Future<List<OnlineEpisode>> _getEpisodes(
|
||||
{String id, int nextEpisodeDate}) async {
|
||||
var searchEngine = SearchEngine();
|
||||
var searchEngine = ListenNotesSearch();
|
||||
var searchResult = await searchEngine.fetchEpisode(
|
||||
id: id, nextEpisodeDate: nextEpisodeDate);
|
||||
_nextEpisdoeDate = searchResult.nextEpisodeDate;
|
||||
|
@ -140,6 +140,8 @@
|
||||
},
|
||||
"goodNight": "Good Night",
|
||||
"@goodNight": {},
|
||||
"gpodderLoginDes": "Congratulations! You have linked gpodder.net account successfully. Tsacdop will automatically sync subscriptions on your device with your gpodder.net account.",
|
||||
"@gpodderLoginDes": {},
|
||||
"groupExisted": "Group already exists",
|
||||
"@groupExisted": {
|
||||
"description": "Group name validate in add group dialog. User can't add group with same name."
|
||||
@ -180,12 +182,25 @@
|
||||
"@hoursCount": {},
|
||||
"import": "Import",
|
||||
"@import": {},
|
||||
"intergateWith": "Integrate with {service}",
|
||||
"@intergateWith": {
|
||||
"description": "Integrate with",
|
||||
"placeholders": {
|
||||
"service": {}
|
||||
}
|
||||
},
|
||||
"introFourthPage": "You can long press on episode card for quick actions.",
|
||||
"@introFourthPage": {},
|
||||
"introSecondPage": "Subscribe podcast via search or import OPML file.",
|
||||
"@introSecondPage": {},
|
||||
"introThirdPage": "You can create new group for podcasts.",
|
||||
"@introThirdPage": {},
|
||||
"invalidName": "Invalid username",
|
||||
"@invalidName": {},
|
||||
"lastUpdate": "Last update",
|
||||
"@lastUpdate": {
|
||||
"description": "gpodder.net update"
|
||||
},
|
||||
"later": "Later",
|
||||
"@later": {},
|
||||
"lightMode": "Light mode",
|
||||
@ -204,6 +219,23 @@
|
||||
"@listened": {},
|
||||
"loadMore": "Load more",
|
||||
"@loadMore": {},
|
||||
"loggedInAs": "Logged in as {userName}",
|
||||
"@loggedInAs": {
|
||||
"description": "gpodder.net",
|
||||
"placeholders": {
|
||||
"userName": {}
|
||||
}
|
||||
},
|
||||
"login": "Login",
|
||||
"@login": {
|
||||
"description": "gpodder.net login"
|
||||
},
|
||||
"loginFailed": "Login failed",
|
||||
"@loginFailed": {},
|
||||
"logout": "Logout",
|
||||
"@logout": {
|
||||
"description": "gpodder.net logout"
|
||||
},
|
||||
"mark": "Mark",
|
||||
"@mark": {
|
||||
"description": "In listen history page, if a episode is marked as listened."
|
||||
@ -292,6 +324,10 @@
|
||||
},
|
||||
"oldestFirst": "Oldest first",
|
||||
"@oldestFirst": {},
|
||||
"passwdRequired": "Password required",
|
||||
"@passwdRequired": {},
|
||||
"password": "Password",
|
||||
"@password": {},
|
||||
"pause": "Pause",
|
||||
"@pause": {},
|
||||
"play": "Play",
|
||||
@ -547,12 +583,18 @@
|
||||
"@skipToNext": {},
|
||||
"sleepTimer": "Sleep timer",
|
||||
"@sleepTimer": {},
|
||||
"status": "Status",
|
||||
"@status": {
|
||||
"description": "gpodder.net status"
|
||||
},
|
||||
"stop": "Stop",
|
||||
"@stop": {},
|
||||
"subscribe": "Subscribe",
|
||||
"@subscribe": {},
|
||||
"subscribeExportDes": "Export OPML file of all podcasts",
|
||||
"@subscribeExportDes": {},
|
||||
"syncNow": "Sync now",
|
||||
"@syncNow": {},
|
||||
"systemDefault": "System default",
|
||||
"@systemDefault": {},
|
||||
"timeLastPlayed": "Last time {time}",
|
||||
@ -628,6 +670,10 @@
|
||||
"@updateEpisodesCount": {},
|
||||
"updateFailed": "Update failed, network error",
|
||||
"@updateFailed": {},
|
||||
"username": "Username",
|
||||
"@username": {},
|
||||
"usernameRequired": "Username required",
|
||||
"@usernameRequired": {},
|
||||
"version": "Version: {version}",
|
||||
"@version": {
|
||||
"placeholders": {
|
||||
|
@ -140,6 +140,8 @@
|
||||
},
|
||||
"goodNight": "Buenas noches",
|
||||
"@goodNight": {},
|
||||
"gpodderLoginDes": "Congratulations! You have linked gpodder.net account successfully. Tsacdop will automatically sync subscriptions on your device with your gpodder.net account.",
|
||||
"@gpodderLoginDes": {},
|
||||
"groupExisted": "El grupo ya existe",
|
||||
"@groupExisted": {
|
||||
"description": "Group name validate in add group dialog. User can't add group with same name."
|
||||
@ -180,12 +182,25 @@
|
||||
"@hoursCount": {},
|
||||
"import": "Importar",
|
||||
"@import": {},
|
||||
"intergateWith": "Integrate with {service}",
|
||||
"@intergateWith": {
|
||||
"description": "Integrate with",
|
||||
"placeholders": {
|
||||
"service": {}
|
||||
}
|
||||
},
|
||||
"introFourthPage": "Puedes mantener presionado un episodio para realizar acciones rápidas",
|
||||
"@introFourthPage": {},
|
||||
"introSecondPage": "Suscribete a podcasts buscándolos, o importando un archivo OPML",
|
||||
"@introSecondPage": {},
|
||||
"introThirdPage": "Puedes crear un nuevo grupo de podcasts",
|
||||
"@introThirdPage": {},
|
||||
"invalidName": "Invalid username",
|
||||
"@invalidName": {},
|
||||
"lastUpdate": "Last update",
|
||||
"@lastUpdate": {
|
||||
"description": "gpodder.net update"
|
||||
},
|
||||
"later": "Despues",
|
||||
"@later": {},
|
||||
"lightMode": "Modo claro",
|
||||
@ -204,6 +219,23 @@
|
||||
"@listened": {},
|
||||
"loadMore": "Cargar mas",
|
||||
"@loadMore": {},
|
||||
"loggedInAs": "Logged in as {userName}",
|
||||
"@loggedInAs": {
|
||||
"description": "gpodder.net",
|
||||
"placeholders": {
|
||||
"userName": {}
|
||||
}
|
||||
},
|
||||
"login": "Loign",
|
||||
"@login": {
|
||||
"description": "gpodder.net login"
|
||||
},
|
||||
"loginFailed": "Login failed",
|
||||
"@loginFailed": {},
|
||||
"logout": "Logout",
|
||||
"@logout": {
|
||||
"description": "gpodder.net logout"
|
||||
},
|
||||
"mark": "Completado",
|
||||
"@mark": {
|
||||
"description": "In listen history page, if a episode is marked as listened."
|
||||
@ -292,6 +324,10 @@
|
||||
},
|
||||
"oldestFirst": "Mas antiguos primero",
|
||||
"@oldestFirst": {},
|
||||
"passwdRequired": "Password required",
|
||||
"@passwdRequired": {},
|
||||
"password": "Password",
|
||||
"@password": {},
|
||||
"pause": "Pause",
|
||||
"@pause": {},
|
||||
"play": "Reproducir",
|
||||
@ -547,12 +583,18 @@
|
||||
"@skipToNext": {},
|
||||
"sleepTimer": "Temporizador de sueño",
|
||||
"@sleepTimer": {},
|
||||
"status": "Status",
|
||||
"@status": {
|
||||
"description": "gpodder.net status"
|
||||
},
|
||||
"stop": "Stop",
|
||||
"@stop": {},
|
||||
"subscribe": "Suscribir",
|
||||
"@subscribe": {},
|
||||
"subscribeExportDes": "Exportar OPML de todos los podcasts",
|
||||
"@subscribeExportDes": {},
|
||||
"syncNow": "Sync now",
|
||||
"@syncNow": {},
|
||||
"systemDefault": "Acorde al sistema",
|
||||
"@systemDefault": {},
|
||||
"timeLastPlayed": "Tiempo previo {time}",
|
||||
@ -628,6 +670,10 @@
|
||||
"@updateEpisodesCount": {},
|
||||
"updateFailed": "Actualización fallida, error de red",
|
||||
"@updateFailed": {},
|
||||
"username": "Username",
|
||||
"@username": {},
|
||||
"usernameRequired": "Username required",
|
||||
"@usernameRequired": {},
|
||||
"version": "Versión: {version}",
|
||||
"@version": {
|
||||
"placeholders": {
|
||||
|
@ -140,6 +140,8 @@
|
||||
},
|
||||
"goodNight": "Bonne nuit",
|
||||
"@goodNight": {},
|
||||
"gpodderLoginDes": "Congratulations! You have linked gpodder.net account successfully. Tsacdop will automatically sync subscriptions on your device with your gpodder.net account.",
|
||||
"@gpodderLoginDes": {},
|
||||
"groupExisted": "Ce groupe existe déjà",
|
||||
"@groupExisted": {
|
||||
"description": "Group name validate in add group dialog. User can't add group with same name."
|
||||
@ -180,12 +182,25 @@
|
||||
"@hoursCount": {},
|
||||
"import": "Importer",
|
||||
"@import": {},
|
||||
"intergateWith": "Integrate with {service}",
|
||||
"@intergateWith": {
|
||||
"description": "Integrate with",
|
||||
"placeholders": {
|
||||
"service": {}
|
||||
}
|
||||
},
|
||||
"introFourthPage": "Un appui long sur un épisode lance les actions rapides.",
|
||||
"@introFourthPage": {},
|
||||
"introSecondPage": "S'abonner aux podcasts via la section recherche ou un fichier OPML.",
|
||||
"@introSecondPage": {},
|
||||
"introThirdPage": "Vous pouvez créer des groupes de podcasts.",
|
||||
"@introThirdPage": {},
|
||||
"invalidName": "Invalid username",
|
||||
"@invalidName": {},
|
||||
"lastUpdate": "Last update",
|
||||
"@lastUpdate": {
|
||||
"description": "gpodder.net update"
|
||||
},
|
||||
"later": "Plus tard",
|
||||
"@later": {},
|
||||
"lightMode": "Mode clair",
|
||||
@ -204,6 +219,23 @@
|
||||
"@listened": {},
|
||||
"loadMore": "Voir plus",
|
||||
"@loadMore": {},
|
||||
"loggedInAs": "Logged in as {userName}",
|
||||
"@loggedInAs": {
|
||||
"description": "gpodder.net",
|
||||
"placeholders": {
|
||||
"userName": {}
|
||||
}
|
||||
},
|
||||
"login": "Login",
|
||||
"@login": {
|
||||
"description": "gpodder.net login"
|
||||
},
|
||||
"loginFailed": "Login failed",
|
||||
"@loginFailed": {},
|
||||
"logout": "Logout",
|
||||
"@logout": {
|
||||
"description": "gpodder.net logout"
|
||||
},
|
||||
"mark": "✓",
|
||||
"@mark": {
|
||||
"description": "In listen history page, if a episode is marked as listened."
|
||||
@ -292,6 +324,10 @@
|
||||
},
|
||||
"oldestFirst": "Le plus ancien en premier",
|
||||
"@oldestFirst": {},
|
||||
"passwdRequired": "Password required",
|
||||
"@passwdRequired": {},
|
||||
"password": "Password",
|
||||
"@password": {},
|
||||
"pause": "Pause",
|
||||
"@pause": {},
|
||||
"play": "Lecture",
|
||||
@ -547,12 +583,18 @@
|
||||
"@skipToNext": {},
|
||||
"sleepTimer": "Minuterie",
|
||||
"@sleepTimer": {},
|
||||
"status": "Status",
|
||||
"@status": {
|
||||
"description": "gpodder.net status"
|
||||
},
|
||||
"stop": "Stop",
|
||||
"@stop": {},
|
||||
"subscribe": "S'abonner",
|
||||
"@subscribe": {},
|
||||
"subscribeExportDes": "Exporter le fichier OPML de tous les podcasts.",
|
||||
"@subscribeExportDes": {},
|
||||
"syncNow": "Sync now",
|
||||
"@syncNow": {},
|
||||
"systemDefault": "Système par défaut",
|
||||
"@systemDefault": {},
|
||||
"timeLastPlayed": "Dernière écoute à {time}",
|
||||
@ -628,6 +670,10 @@
|
||||
"@updateEpisodesCount": {},
|
||||
"updateFailed": "Échec de la mise à jour, erreur réseau",
|
||||
"@updateFailed": {},
|
||||
"username": "Username",
|
||||
"@username": {},
|
||||
"usernameRequired": "Username required",
|
||||
"@usernameRequired": {},
|
||||
"version": "Version : {version}",
|
||||
"@version": {
|
||||
"placeholders": {
|
||||
|
683
lib/l10n/intl_pt.arb
Normal file
683
lib/l10n/intl_pt.arb
Normal file
@ -0,0 +1,683 @@
|
||||
{
|
||||
"@@locale": "pt",
|
||||
"add": "Adicionar",
|
||||
"@add": {
|
||||
"description": "Subscribe new podcast"
|
||||
},
|
||||
"addEpisodeGroup": "{count, plural, zero{} one{{count} episódio de {groupName} adicionado à lista} other{{count} episódios de {groupName} adicionados à lista}}",
|
||||
"@addEpisodeGroup": {
|
||||
"placeholders": {
|
||||
"groupName": {}
|
||||
}
|
||||
},
|
||||
"addNewEpisodeAll": "{count, plural, zero{} one{{count} episódio adicionado à lista} other{{count} episódios adicionados à lista}}",
|
||||
"@addNewEpisodeAll": {},
|
||||
"addNewEpisodeTooltip": "Adiciona novos episódios à lista de reprodução",
|
||||
"@addNewEpisodeTooltip": {},
|
||||
"addSomeGroups": "Adiciona alguns grupos",
|
||||
"@addSomeGroups": {
|
||||
"description": "Please add new groups"
|
||||
},
|
||||
"all": "Todos",
|
||||
"@all": {},
|
||||
"autoDownload": "Download automático",
|
||||
"@autoDownload": {},
|
||||
"back": "Atrás",
|
||||
"@back": {},
|
||||
"boostVolume": "Aumentar volume",
|
||||
"@boostVolume": {
|
||||
"description": "Boost volume in player widget."
|
||||
},
|
||||
"buffering": "A carregar",
|
||||
"@buffering": {},
|
||||
"cancel": "CANCELAR",
|
||||
"@cancel": {},
|
||||
"cellularConfirm": "Alerta de dados móveis",
|
||||
"@cellularConfirm": {},
|
||||
"cellularConfirmDes": "Tens a certeza que queres usar dados móveis para downloads?",
|
||||
"@cellularConfirmDes": {},
|
||||
"changeLayout": "Mudar aparência",
|
||||
"@changeLayout": {},
|
||||
"changelog": "Registo de mudanças",
|
||||
"@changelog": {},
|
||||
"chooseA": "Escolher um",
|
||||
"@chooseA": {},
|
||||
"clear": "Limpar",
|
||||
"@clear": {},
|
||||
"color": "Cor",
|
||||
"@color": {},
|
||||
"confirm": "CONFIRMAR",
|
||||
"@confirm": {},
|
||||
"darkMode": "Modo escuro",
|
||||
"@darkMode": {},
|
||||
"daysAgo": "{count, plural, zero{Hoje} one{Há {count} dia} other{Há {count} dias}}",
|
||||
"@daysAgo": {},
|
||||
"daysCount": "{count, plural, zero{Nunca} one{{count} dia} other{{count} dias}}",
|
||||
"@daysCount": {},
|
||||
"delete": "Eliminar",
|
||||
"@delete": {},
|
||||
"developer": "Desenvolvedor",
|
||||
"@developer": {
|
||||
"description": "Can also translate to About me"
|
||||
},
|
||||
"dismiss": "Minimizar",
|
||||
"@dismiss": {},
|
||||
"done": "Feito",
|
||||
"@done": {},
|
||||
"download": "Download",
|
||||
"@download": {},
|
||||
"downloaded": "Descarregado",
|
||||
"@downloaded": {},
|
||||
"downloadRemovedToast": "Download removido",
|
||||
"@downloadRemovedToast": {},
|
||||
"editGroupName": "Editar nome do grupo",
|
||||
"@editGroupName": {},
|
||||
"endOfEpisode": "Fim do episódio",
|
||||
"@endOfEpisode": {},
|
||||
"episode": "{count, plural, zero{} one{Episódio} other{Episódios}}",
|
||||
"@episode": {},
|
||||
"fastForward": "Avanço",
|
||||
"@fastForward": {},
|
||||
"fastRewind": "Recuo rápido",
|
||||
"@fastRewind": {},
|
||||
"featureDiscoveryEditGroup": "Prime para editar grupo",
|
||||
"@featureDiscoveryEditGroup": {},
|
||||
"featureDiscoveryEditGroupDes": "Podes alterar o nome do grupo ou apagá-lo aqui, mas o grupo Home não pode ser editado ou eliminado",
|
||||
"@featureDiscoveryEditGroupDes": {},
|
||||
"featureDiscoveryEpisode": "Vista de episódios",
|
||||
"@featureDiscoveryEpisode": {},
|
||||
"featureDiscoveryEpisodeDes": "Podes manter premido para reproduzir um episódio ou adicioná-lo a uma lista de reprodução.",
|
||||
"@featureDiscoveryEpisodeDes": {},
|
||||
"featureDiscoveryEpisodeTitle": "Mantém premido para reproduzir um episódio instantâneamente",
|
||||
"@featureDiscoveryEpisodeTitle": {},
|
||||
"featureDiscoveryGroup": "Prime para adicionar grupo",
|
||||
"@featureDiscoveryGroup": {},
|
||||
"featureDiscoveryGroupDes": "O grupo por defeito para novos podcasts é Home. Podes criar novos grupos e mover os podcasts para estes, assim como adicionar podcasts a múltiplos grupos.",
|
||||
"@featureDiscoveryGroupDes": {},
|
||||
"featureDiscoveryGroupPodcast": "Mantém premido para reordenar podcasts",
|
||||
"@featureDiscoveryGroupPodcast": {},
|
||||
"featureDiscoveryGroupPodcastDes": "Podes premir para ver mais opções, ou manter premido para reordenar podcasts em grupos.",
|
||||
"@featureDiscoveryGroupPodcastDes": {},
|
||||
"featureDiscoveryOMPL": "Premir para importar um OPML",
|
||||
"@featureDiscoveryOMPL": {},
|
||||
"featureDiscoveryOMPLDes": "Podes importar ficheiros OPML, abrir as definições ou atualizar todos os podcasts aqui.",
|
||||
"@featureDiscoveryOMPLDes": {},
|
||||
"featureDiscoveryPlaylist": "Prime para abrir a lista de reprodução",
|
||||
"@featureDiscoveryPlaylist": {},
|
||||
"featureDiscoveryPlaylistDes": "Podes adicionar episódios à lista de reprodução manualmente. Os episódios serão automaticamente removidos das listas de reprodução quando reproduzidos.",
|
||||
"@featureDiscoveryPlaylistDes": {},
|
||||
"featureDiscoveryPodcast": "Vista do podcast",
|
||||
"@featureDiscoveryPodcast": {},
|
||||
"featureDiscoveryPodcastDes": "Podes premir \"Ver Todos\" para adicionar grupos ou organizar pdcasts.",
|
||||
"@featureDiscoveryPodcastDes": {},
|
||||
"featureDiscoveryPodcastTitle": "Deslizar verticalmente para alterar grupos",
|
||||
"@featureDiscoveryPodcastTitle": {},
|
||||
"featureDiscoverySearch": "Prime para procurar podcasts",
|
||||
"@featureDiscoverySearch": {},
|
||||
"featureDiscoverySearchDes": "Podes procurar pelo título do podcast, palavra-chave ou ligação RSS para subscrever novos podcasts.",
|
||||
"@featureDiscoverySearchDes": {},
|
||||
"feedbackEmail": "Escreve-me",
|
||||
"@feedbackEmail": {},
|
||||
"feedbackGithub": "Submeter problema",
|
||||
"@feedbackGithub": {},
|
||||
"feedbackPlay": "Avaliar na Play Store",
|
||||
"@feedbackPlay": {
|
||||
"description": "Rate on Google Play Store.\nUser can tap to open play link."
|
||||
},
|
||||
"feedbackTelegram": "Juntar um grupo",
|
||||
"@feedbackTelegram": {},
|
||||
"filter": "Filtro",
|
||||
"@filter": {},
|
||||
"fonts": "Fontes",
|
||||
"@fonts": {},
|
||||
"fontStyle": "Estilo do tipo de letra",
|
||||
"@fontStyle": {},
|
||||
"from": "De {time}",
|
||||
"@from": {
|
||||
"placeholders": {
|
||||
"time": {}
|
||||
}
|
||||
},
|
||||
"goodNight": "Boa Noite",
|
||||
"@goodNight": {},
|
||||
"gpodderLoginDes": "Congratulations! You have linked gpodder.net account successfully. Tsacdop will automatically sync subscriptions on your device with your gpodder.net account.",
|
||||
"@gpodderLoginDes": {},
|
||||
"groupExisted": "Grupo já existe",
|
||||
"@groupExisted": {
|
||||
"description": "Group name validate in add group dialog. User can't add group with same name."
|
||||
},
|
||||
"groupFilter": "Filtro de grupo",
|
||||
"@groupFilter": {},
|
||||
"groupRemoveConfirm": "Tens a certeza que queres eliminar este grupo? Os podcasts serão removidos para o grupo \"Home\".",
|
||||
"@groupRemoveConfirm": {},
|
||||
"groups": "{count, plural, zero{Grupo} one{Grupo} other{Grupos}}",
|
||||
"@groups": {},
|
||||
"hideListenedSetting": "Esconder ouvidos",
|
||||
"@hideListenedSetting": {},
|
||||
"homeGroupsSeeAll": "Ver Todos",
|
||||
"@homeGroupsSeeAll": {},
|
||||
"homeMenuPlaylist": "Lista de Reprodução",
|
||||
"@homeMenuPlaylist": {},
|
||||
"homeSubMenuSortBy": "Ordenar por",
|
||||
"@homeSubMenuSortBy": {},
|
||||
"homeTabMenuFavotite": "Favorito",
|
||||
"@homeTabMenuFavotite": {},
|
||||
"homeTabMenuRecent": "Recentes",
|
||||
"@homeTabMenuRecent": {},
|
||||
"homeToprightMenuAbout": "Sobre",
|
||||
"@homeToprightMenuAbout": {},
|
||||
"homeToprightMenuImportOMPL": "Importar OPML",
|
||||
"@homeToprightMenuImportOMPL": {},
|
||||
"homeToprightMenuRefreshAll": "Atualizar todos",
|
||||
"@homeToprightMenuRefreshAll": {},
|
||||
"hostedOn": "Hospedado em {host}",
|
||||
"@hostedOn": {
|
||||
"placeholders": {
|
||||
"host": {}
|
||||
}
|
||||
},
|
||||
"hoursAgo": "{count, plural, zero{} one{há {count} hora} other{há {count} horas}}",
|
||||
"@hoursAgo": {},
|
||||
"hoursCount": "{count, plural, zero{0 horas} one{{count} hora} other{{count} horas}}",
|
||||
"@hoursCount": {},
|
||||
"import": "Importar",
|
||||
"@import": {},
|
||||
"intergateWith": "Integrate with {service}",
|
||||
"@intergateWith": {
|
||||
"description": "Integrate with",
|
||||
"placeholders": {
|
||||
"service": {}
|
||||
}
|
||||
},
|
||||
"introFourthPage": "Podes manter premido um episódio para uma ação rápida.",
|
||||
"@introFourthPage": {},
|
||||
"introSecondPage": "Subscreve podcasts por pesquisa ou importa um ficheiro OPML.",
|
||||
"@introSecondPage": {},
|
||||
"introThirdPage": "Podes criar um novo grupo para podcasts.",
|
||||
"@introThirdPage": {},
|
||||
"invalidName": "Invalid username",
|
||||
"@invalidName": {},
|
||||
"lastUpdate": "Last update",
|
||||
"@lastUpdate": {
|
||||
"description": "gpodder.net update"
|
||||
},
|
||||
"later": "Mais tarde",
|
||||
"@later": {},
|
||||
"lightMode": "Modo claro",
|
||||
"@lightMode": {},
|
||||
"like": "Gosto",
|
||||
"@like": {},
|
||||
"liked": "Gostou",
|
||||
"@liked": {},
|
||||
"likeDate": "Data do Gosto",
|
||||
"@likeDate": {
|
||||
"description": "Favorite tab, sort by like date."
|
||||
},
|
||||
"listen": "Ouvir",
|
||||
"@listen": {},
|
||||
"listened": "Ouvido",
|
||||
"@listened": {},
|
||||
"loadMore": "Carregar mais",
|
||||
"@loadMore": {},
|
||||
"loggedInAs": "Logged in as {userName}",
|
||||
"@loggedInAs": {
|
||||
"description": "gpodder.net",
|
||||
"placeholders": {
|
||||
"userName": {}
|
||||
}
|
||||
},
|
||||
"login": "Login",
|
||||
"@login": {
|
||||
"description": "gpodder.net login"
|
||||
},
|
||||
"loginFailed": "Login failed",
|
||||
"@loginFailed": {},
|
||||
"logout": "Logout",
|
||||
"@logout": {
|
||||
"description": "gpodder.net logout"
|
||||
},
|
||||
"mark": "Marcar",
|
||||
"@mark": {
|
||||
"description": "In listen history page, if a episode is marked as listened."
|
||||
},
|
||||
"markConfirm": "Confirmar marca",
|
||||
"@markConfirm": {},
|
||||
"markConfirmContent": "Marcar todos os episódios como ouvidos?",
|
||||
"@markConfirmContent": {},
|
||||
"markListened": "Marcar como ouvido",
|
||||
"@markListened": {},
|
||||
"markNotListened": "Marcar não ouvidos",
|
||||
"@markNotListened": {},
|
||||
"menu": "Menu",
|
||||
"@menu": {},
|
||||
"menuAllPodcasts": "Todos os podcasts",
|
||||
"@menuAllPodcasts": {},
|
||||
"menuMarkAllListened": "Marcar todos como ouvidos",
|
||||
"@menuMarkAllListened": {},
|
||||
"menuViewRSS": "Visitar Feed RSS",
|
||||
"@menuViewRSS": {},
|
||||
"menuVisitSite": "Visitar website",
|
||||
"@menuVisitSite": {},
|
||||
"minsAgo": "{count, plural, zero{Agora} one{Há {count} minuto} other{Há {count} minutos}}",
|
||||
"@minsAgo": {},
|
||||
"minsCount": "{count, plural, zero{0 minutos} one{{count} minuto} other{{count} minutos}}",
|
||||
"@minsCount": {},
|
||||
"network": "Rede",
|
||||
"@network": {},
|
||||
"newestFirst": "Mais recentes primeiro",
|
||||
"@newestFirst": {},
|
||||
"newGroup": "Criar um novo grupo",
|
||||
"@newGroup": {},
|
||||
"next": "Seguinte",
|
||||
"@next": {},
|
||||
"noEpisodeDownload": "Ainda não há episódios descarregados",
|
||||
"@noEpisodeDownload": {},
|
||||
"noEpisodeFavorite": "Ainda não há episódios coletados",
|
||||
"@noEpisodeFavorite": {},
|
||||
"noEpisodeRecent": "Ainda não há episódios recebidos",
|
||||
"@noEpisodeRecent": {},
|
||||
"noPodcastGroup": "Não há podcasts neste grupo",
|
||||
"@noPodcastGroup": {},
|
||||
"noShownote": "Não há notas disponíveis para este episódio",
|
||||
"@noShownote": {
|
||||
"description": "Means this episode have no show notes."
|
||||
},
|
||||
"notificaitonFatch": "Obter dados {title}",
|
||||
"@notificaitonFatch": {},
|
||||
"notificationNetworkError": "A subscrição falhou, erro de rede {title}",
|
||||
"@notificationNetworkError": {
|
||||
"placeholders": {
|
||||
"title": {}
|
||||
}
|
||||
},
|
||||
"notificationSetting": "Painel de notificações",
|
||||
"@notificationSetting": {},
|
||||
"notificationSubscribe": "Subscrever {title}",
|
||||
"@notificationSubscribe": {
|
||||
"placeholders": {
|
||||
"title": {}
|
||||
}
|
||||
},
|
||||
"notificationSubscribeExisted": "Subscrição falhou, podcast já existe {title}",
|
||||
"@notificationSubscribeExisted": {
|
||||
"placeholders": {
|
||||
"title": {}
|
||||
}
|
||||
},
|
||||
"notificationSuccess": "Subscrito com sucesso {title}",
|
||||
"@notificationSuccess": {
|
||||
"placeholders": {
|
||||
"title": {}
|
||||
}
|
||||
},
|
||||
"notificationUpdate": "Atualizar {title}",
|
||||
"@notificationUpdate": {
|
||||
"placeholders": {
|
||||
"title": {}
|
||||
}
|
||||
},
|
||||
"notificationUpdateError": "Erro de atualização {title}",
|
||||
"@notificationUpdateError": {
|
||||
"placeholders": {
|
||||
"title": {}
|
||||
}
|
||||
},
|
||||
"oldestFirst": "Mais antigos primeiro",
|
||||
"@oldestFirst": {},
|
||||
"passwdRequired": "Password required",
|
||||
"@passwdRequired": {},
|
||||
"password": "Password",
|
||||
"@password": {},
|
||||
"pause": "Pausa",
|
||||
"@pause": {},
|
||||
"play": "Reproduzir",
|
||||
"@play": {},
|
||||
"playback": "Controlo da reprodução",
|
||||
"@playback": {},
|
||||
"player": "Reprodutor",
|
||||
"@player": {},
|
||||
"playerHeightMed": "Médio",
|
||||
"@playerHeightMed": {},
|
||||
"playerHeightShort": "Baixo",
|
||||
"@playerHeightShort": {},
|
||||
"playerHeightTall": "Alto",
|
||||
"@playerHeightTall": {},
|
||||
"playing": "Em reprodução",
|
||||
"@playing": {},
|
||||
"plugins": "Plugins",
|
||||
"@plugins": {},
|
||||
"podcast": "{count, plural, zero{} one{Podcast} other{Podcasts}}",
|
||||
"@podcast": {},
|
||||
"podcastSubscribed": "Podcast subscrito",
|
||||
"@podcastSubscribed": {},
|
||||
"popupMenuDownloadDes": "Descarregar episódio",
|
||||
"@popupMenuDownloadDes": {},
|
||||
"popupMenuLaterDes": "Adicionar episódio à lista de reprodução",
|
||||
"@popupMenuLaterDes": {},
|
||||
"popupMenuLikeDes": "Adicionar episódio aos favoritos",
|
||||
"@popupMenuLikeDes": {},
|
||||
"popupMenuMarkDes": "Marcar episódio como ouvido",
|
||||
"@popupMenuMarkDes": {},
|
||||
"popupMenuPlayDes": "Reproduzir episódio",
|
||||
"@popupMenuPlayDes": {},
|
||||
"privacyPolicy": "Política de Privacidade",
|
||||
"@privacyPolicy": {},
|
||||
"published": "Publicado em {date}",
|
||||
"@published": {
|
||||
"placeholders": {
|
||||
"date": {}
|
||||
}
|
||||
},
|
||||
"publishedDaily": "Publicado diariamente",
|
||||
"@publishedDaily": {},
|
||||
"publishedMonthly": "Publicado mensalmente",
|
||||
"@publishedMonthly": {},
|
||||
"publishedWeekly": "Publicado semanalmente",
|
||||
"@publishedWeekly": {
|
||||
"description": "In search podcast detail page."
|
||||
},
|
||||
"publishedYearly": "Publicado anualmente",
|
||||
"@publishedYearly": {},
|
||||
"recoverSubscribe": "Recuperar subscrição",
|
||||
"@recoverSubscribe": {
|
||||
"description": "User can recover subscribe podcast after remove it in subscribe history page."
|
||||
},
|
||||
"refreshArtwork": "Atualizar capa",
|
||||
"@refreshArtwork": {},
|
||||
"remove": "Remover",
|
||||
"@remove": {
|
||||
"description": "Remove not \"removed\". \nRemove a podcast or a group."
|
||||
},
|
||||
"removeConfirm": "Confirmação de remoção",
|
||||
"@removeConfirm": {
|
||||
"description": "unsubscribe podcast dialog"
|
||||
},
|
||||
"removedAt": "Removido em {date}",
|
||||
"@removedAt": {
|
||||
"description": "For example :Removed at 2020.10.10",
|
||||
"placeholders": {
|
||||
"date": {}
|
||||
}
|
||||
},
|
||||
"removePodcastDes": "Tens a certeza que pretendes cancelar a subscrição?",
|
||||
"@removePodcastDes": {},
|
||||
"save": "Guardar",
|
||||
"@save": {},
|
||||
"schedule": "Horário",
|
||||
"@schedule": {},
|
||||
"search": "Procurar",
|
||||
"@search": {},
|
||||
"searchEpisode": "Procurar episódio",
|
||||
"@searchEpisode": {},
|
||||
"searchInvalidRss": "Ligação RSS inválida",
|
||||
"@searchInvalidRss": {},
|
||||
"searchPodcast": "Procurar podcasts",
|
||||
"@searchPodcast": {},
|
||||
"secCount": "{count, plural, zero{0 segundos} one{{count} segundo} other{{count} segundos}}",
|
||||
"@secCount": {},
|
||||
"secondsAgo": "{count, plural, zero{Agora} one{Há {count} segundo} other{Há {count} segundos}}",
|
||||
"@secondsAgo": {},
|
||||
"settings": "Definições",
|
||||
"@settings": {},
|
||||
"settingsAccentColor": "Cor de realce",
|
||||
"@settingsAccentColor": {},
|
||||
"settingsAccentColorDes": "Incluir cor de sobreposição",
|
||||
"@settingsAccentColorDes": {},
|
||||
"settingsAppearance": "Aparência",
|
||||
"@settingsAppearance": {},
|
||||
"settingsAppearanceDes": "Cores e temas",
|
||||
"@settingsAppearanceDes": {},
|
||||
"settingsAppIntro": "Introdução da Aplicação",
|
||||
"@settingsAppIntro": {},
|
||||
"settingsAudioCache": "Cache de áudio",
|
||||
"@settingsAudioCache": {},
|
||||
"settingsAudioCacheDes": "Tamanho máximo da cache de áudio",
|
||||
"@settingsAudioCacheDes": {},
|
||||
"settingsAutoDelete": "Eliminar downloads automaticamente após",
|
||||
"@settingsAutoDelete": {},
|
||||
"settingsAutoDeleteDes": "30 dias por defeito",
|
||||
"@settingsAutoDeleteDes": {},
|
||||
"settingsAutoPlayDes": "Reproduzir automaticamente o episódio seguinte",
|
||||
"@settingsAutoPlayDes": {},
|
||||
"settingsBackup": "Cópia de segurança",
|
||||
"@settingsBackup": {},
|
||||
"settingsBackupDes": "Cópia de segurança dos dados da aplicação",
|
||||
"@settingsBackupDes": {},
|
||||
"settingsBoostVolume": "Nível de aumento de volume",
|
||||
"@settingsBoostVolume": {},
|
||||
"settingsBoostVolumeDes": "Alterar nível de aumento de volume",
|
||||
"@settingsBoostVolumeDes": {},
|
||||
"settingsDefaultGrid": "Vista de grelha predefinida",
|
||||
"@settingsDefaultGrid": {},
|
||||
"settingsDefaultGridDownload": "Aba de downloads",
|
||||
"@settingsDefaultGridDownload": {},
|
||||
"settingsDefaultGridFavorite": "Aba de favoritos",
|
||||
"@settingsDefaultGridFavorite": {},
|
||||
"settingsDefaultGridPodcast": "Página de podcasts",
|
||||
"@settingsDefaultGridPodcast": {},
|
||||
"settingsDefaultGridRecent": "Aba de recentes",
|
||||
"@settingsDefaultGridRecent": {},
|
||||
"settingsDiscovery": "Reiniciar tutorial",
|
||||
"@settingsDiscovery": {
|
||||
"description": "Reset feature discovery state. User tap it and restart app, will see features tutorial again."
|
||||
},
|
||||
"settingsEnableSyncing": "Ativar sincronização",
|
||||
"@settingsEnableSyncing": {},
|
||||
"settingsEnableSyncingDes": "Atualizar todos os podcasts em segundo plano para obter os episódios mais recentes",
|
||||
"@settingsEnableSyncingDes": {},
|
||||
"settingsExportDes": "Exportar e importar definições da aplicação",
|
||||
"@settingsExportDes": {},
|
||||
"settingsFastForwardSec": "Avançar segundos",
|
||||
"@settingsFastForwardSec": {},
|
||||
"settingsFastForwardSecDes": "Muda os segundos de avanço no reprodutor",
|
||||
"@settingsFastForwardSecDes": {},
|
||||
"settingsFeedback": "Feedback",
|
||||
"@settingsFeedback": {},
|
||||
"settingsFeedbackDes": "Erros e sugestões",
|
||||
"@settingsFeedbackDes": {},
|
||||
"settingsHistory": "Histórico",
|
||||
"@settingsHistory": {},
|
||||
"settingsHistoryDes": "Dados de audição",
|
||||
"@settingsHistoryDes": {},
|
||||
"settingsInfo": "Informações",
|
||||
"@settingsInfo": {},
|
||||
"settingsInterface": "Interface",
|
||||
"@settingsInterface": {},
|
||||
"settingsLanguages": "Idiomas",
|
||||
"@settingsLanguages": {},
|
||||
"settingsLanguagesDes": "Mudar idioma",
|
||||
"@settingsLanguagesDes": {},
|
||||
"settingsLayout": "Esquema",
|
||||
"@settingsLayout": {},
|
||||
"settingsLayoutDes": "Esquema da aplicação",
|
||||
"@settingsLayoutDes": {},
|
||||
"settingsLibraries": "Bibliotecas",
|
||||
"@settingsLibraries": {},
|
||||
"settingsLibrariesDes": "Bibliotecas de código aberto usados nesta aplicação",
|
||||
"@settingsLibrariesDes": {},
|
||||
"settingsManageDownload": "Gerir downloads",
|
||||
"@settingsManageDownload": {},
|
||||
"settingsManageDownloadDes": "Gerir arquivos de aúdio descarregados",
|
||||
"@settingsManageDownloadDes": {},
|
||||
"settingsMenuAutoPlay": "Reproduzir seguinte automaticamente",
|
||||
"@settingsMenuAutoPlay": {},
|
||||
"settingsNetworkCellular": "Perguntar antes de usar dados móveis",
|
||||
"@settingsNetworkCellular": {},
|
||||
"settingsNetworkCellularAuto": "Descarregar automaticamente usando os dados móveis",
|
||||
"@settingsNetworkCellularAuto": {},
|
||||
"settingsNetworkCellularAutoDes": "Podes configurar o descarregamento automático na página de gestão de grupos",
|
||||
"@settingsNetworkCellularAutoDes": {},
|
||||
"settingsNetworkCellularDes": "Perguntar a confirmar o uso de dados móveis ao descarregar episódios",
|
||||
"@settingsNetworkCellularDes": {},
|
||||
"settingsPlayDes": "Lista de reprodução e reprodutor",
|
||||
"@settingsPlayDes": {},
|
||||
"settingsPlayerHeight": "Altura do reprodutor",
|
||||
"@settingsPlayerHeight": {},
|
||||
"settingsPlayerHeightDes": "Mudar a altura do reprodutor a teu gosto",
|
||||
"@settingsPlayerHeightDes": {},
|
||||
"settingsPopupMenu": "Menu pop-up de episódios",
|
||||
"@settingsPopupMenu": {},
|
||||
"settingsPopupMenuDes": "Muda o menu pop-up de episódios",
|
||||
"@settingsPopupMenuDes": {},
|
||||
"settingsPrefrence": "Preferências",
|
||||
"@settingsPrefrence": {},
|
||||
"settingsRealDark": "Escuro AMOLED",
|
||||
"@settingsRealDark": {},
|
||||
"settingsRealDarkDes": "Ativa caso o modo escuro não seja suficientemente escuro",
|
||||
"@settingsRealDarkDes": {},
|
||||
"settingsRewindSec": "Segundos de recuo",
|
||||
"@settingsRewindSec": {},
|
||||
"settingsRewindSecDes": "Muda os segundos de recuo no reprodutor",
|
||||
"@settingsRewindSecDes": {},
|
||||
"settingsSpeeds": "Velocidades",
|
||||
"@settingsSpeeds": {
|
||||
"description": "Playback speeds setting."
|
||||
},
|
||||
"settingsSpeedsDes": "Customizar as velocidades disponíveis",
|
||||
"@settingsSpeedsDes": {
|
||||
"description": "Playback speed setting description"
|
||||
},
|
||||
"settingsSTAuto": "Ligar temporizador automaticamente",
|
||||
"@settingsSTAuto": {},
|
||||
"settingsSTAutoDes": "Ligar temporizador automaticamente num horário definido",
|
||||
"@settingsSTAutoDes": {},
|
||||
"settingsSTDefaultTime": "Tempo predefinido",
|
||||
"@settingsSTDefaultTime": {},
|
||||
"settingsSTDefautTimeDes": "Tempo predefinido para temporizador",
|
||||
"@settingsSTDefautTimeDes": {},
|
||||
"settingsSTMode": "Modo de temporizador automático",
|
||||
"@settingsSTMode": {},
|
||||
"settingsStorageDes": "Gerir cache e armazenamento de downloads",
|
||||
"@settingsStorageDes": {},
|
||||
"settingsSyncing": "Sincronização",
|
||||
"@settingsSyncing": {},
|
||||
"settingsSyncingDes": "Atualizar podcasts em segundo plano",
|
||||
"@settingsSyncingDes": {},
|
||||
"settingsTapToOpenPopupMenu": "Prime para abrir o menu pop-up",
|
||||
"@settingsTapToOpenPopupMenu": {},
|
||||
"settingsTapToOpenPopupMenuDes": "Precisas manter premido para abrir a página do episódio",
|
||||
"@settingsTapToOpenPopupMenuDes": {},
|
||||
"settingsTheme": "Tema",
|
||||
"@settingsTheme": {},
|
||||
"settingStorage": "Armazenamento",
|
||||
"@settingStorage": {},
|
||||
"settingsUpdateInterval": "Intervalo de atualização",
|
||||
"@settingsUpdateInterval": {},
|
||||
"settingsUpdateIntervalDes": "24 horas predefinidas",
|
||||
"@settingsUpdateIntervalDes": {},
|
||||
"share": "Partilhar",
|
||||
"@share": {},
|
||||
"showNotesFonts": "Mostrar tipo de letra das notas",
|
||||
"@showNotesFonts": {},
|
||||
"size": "Tamanho",
|
||||
"@size": {},
|
||||
"skipSecondsAtEnd": "Saltar segundos no fim",
|
||||
"@skipSecondsAtEnd": {},
|
||||
"skipSecondsAtStart": "Saltar segundos no início",
|
||||
"@skipSecondsAtStart": {},
|
||||
"skipSilence": "Saltar silêncio",
|
||||
"@skipSilence": {
|
||||
"description": "Feature skip silence"
|
||||
},
|
||||
"skipToNext": "Saltar para o próximo",
|
||||
"@skipToNext": {},
|
||||
"sleepTimer": "Temporizador",
|
||||
"@sleepTimer": {},
|
||||
"status": "Status",
|
||||
"@status": {
|
||||
"description": "gpodder.net status"
|
||||
},
|
||||
"stop": "Parar",
|
||||
"@stop": {},
|
||||
"subscribe": "Subscrever",
|
||||
"@subscribe": {},
|
||||
"subscribeExportDes": "Exportar ficheiro OPML de todos os podcasts",
|
||||
"@subscribeExportDes": {},
|
||||
"syncNow": "Sync now",
|
||||
"@syncNow": {},
|
||||
"systemDefault": "Predefinido do sistema",
|
||||
"@systemDefault": {},
|
||||
"timeLastPlayed": "Última vez {time}",
|
||||
"@timeLastPlayed": {
|
||||
"description": "Show last time stop position in player when a episode have been played.",
|
||||
"placeholders": {
|
||||
"time": {}
|
||||
}
|
||||
},
|
||||
"timeLeft": "{time} Restante",
|
||||
"@timeLeft": {
|
||||
"placeholders": {
|
||||
"time": {}
|
||||
}
|
||||
},
|
||||
"to": "Para {time}",
|
||||
"@to": {
|
||||
"placeholders": {
|
||||
"time": {}
|
||||
}
|
||||
},
|
||||
"toastAddPlaylist": "Adicionado à lista de reprodução",
|
||||
"@toastAddPlaylist": {},
|
||||
"toastDiscovery": "Característica \"Descobrir\" ligada, por favor reinicia a aplicação",
|
||||
"@toastDiscovery": {
|
||||
"description": "Toast displayed when user tap Discovery Features Again in settings page."
|
||||
},
|
||||
"toastFileError": "Erro no ficheiro, subscrição falhou",
|
||||
"@toastFileError": {},
|
||||
"toastFileNotValid": "Ficheiro inválido",
|
||||
"@toastFileNotValid": {},
|
||||
"toastHomeGroupNotSupport": "Grupo Home não é suportado",
|
||||
"@toastHomeGroupNotSupport": {},
|
||||
"toastImportSettingsSuccess": "Definições importadas com sucesso",
|
||||
"@toastImportSettingsSuccess": {},
|
||||
"toastOneGroup": "Seleciona pelo menos um grupo",
|
||||
"@toastOneGroup": {},
|
||||
"toastPodcastRecovering": "A recuperar, espera um momento",
|
||||
"@toastPodcastRecovering": {
|
||||
"description": "Resubscribe removed podcast"
|
||||
},
|
||||
"toastReadFile": "Ficheiro lido com sucesso",
|
||||
"@toastReadFile": {},
|
||||
"toastRecoverFailed": "Recuperação do podcast falhou",
|
||||
"@toastRecoverFailed": {
|
||||
"description": "Resubscribe removed podast"
|
||||
},
|
||||
"toastRemovePlaylist": "Episódio removido da lista de reprodução",
|
||||
"@toastRemovePlaylist": {},
|
||||
"toastSettingSaved": "Definições guardadas",
|
||||
"@toastSettingSaved": {},
|
||||
"toastTimeEqualEnd": "Tempo marcado é igual ao tempo de fim",
|
||||
"@toastTimeEqualEnd": {
|
||||
"description": "User can't choose the same time as schedule end time."
|
||||
},
|
||||
"toastTimeEqualStart": "Tempo marcado é igual ao tempo de início",
|
||||
"@toastTimeEqualStart": {
|
||||
"description": "User can't choose the same time as schedule start time."
|
||||
},
|
||||
"translators": "Tradutores",
|
||||
"@translators": {},
|
||||
"understood": "Compreendido",
|
||||
"@understood": {},
|
||||
"undo": "DESFAZER",
|
||||
"@undo": {},
|
||||
"unlike": "Não gosto",
|
||||
"@unlike": {},
|
||||
"unliked": "Episódio removido dos favoritos",
|
||||
"@unliked": {},
|
||||
"updateDate": "Atualizar data",
|
||||
"@updateDate": {},
|
||||
"updateEpisodesCount": "{count, plural, zero{Sem atualizações} one{{count} episódio atualizado} other{{count} episódios atualizados}}",
|
||||
"@updateEpisodesCount": {},
|
||||
"updateFailed": "Atuallização falhou, erro de conexão",
|
||||
"@updateFailed": {},
|
||||
"username": "Username",
|
||||
"@username": {},
|
||||
"usernameRequired": "Username requeired",
|
||||
"@usernameRequired": {},
|
||||
"version": "Versão: {version}",
|
||||
"@version": {
|
||||
"placeholders": {
|
||||
"version": {}
|
||||
}
|
||||
}
|
||||
}
|
@ -140,6 +140,8 @@
|
||||
},
|
||||
"goodNight": "晚安",
|
||||
"@goodNight": {},
|
||||
"gpodderLoginDes": "恭喜!您已经成功绑定 gpodder.net 账号,Tsacdop 将会自动同步您的订阅到 gpodder.net 账户。",
|
||||
"@gpodderLoginDes": {},
|
||||
"groupExisted": "组名已使用",
|
||||
"@groupExisted": {
|
||||
"description": "Group name validate in add group dialog. User can't add group with same name."
|
||||
@ -180,12 +182,25 @@
|
||||
"@hoursCount": {},
|
||||
"import": "导入",
|
||||
"@import": {},
|
||||
"intergateWith": "绑定 {service}",
|
||||
"@intergateWith": {
|
||||
"description": "Integrate with",
|
||||
"placeholders": {
|
||||
"service": {}
|
||||
}
|
||||
},
|
||||
"introFourthPage": "您可以长按节目打开快捷菜单。",
|
||||
"@introFourthPage": {},
|
||||
"introSecondPage": "您可以通过搜索订阅播客,也可以直接导入OPML文件。",
|
||||
"@introSecondPage": {},
|
||||
"introThirdPage": "您可以创建分组,上下滑动切换分组。",
|
||||
"@introThirdPage": {},
|
||||
"invalidName": "用户名错误",
|
||||
"@invalidName": {},
|
||||
"lastUpdate": "最近更新",
|
||||
"@lastUpdate": {
|
||||
"description": "gpodder.net update"
|
||||
},
|
||||
"later": "稍后",
|
||||
"@later": {},
|
||||
"lightMode": "明亮模式",
|
||||
@ -204,6 +219,23 @@
|
||||
"@listened": {},
|
||||
"loadMore": "加载更多",
|
||||
"@loadMore": {},
|
||||
"loggedInAs": "使用{userName}登入",
|
||||
"@loggedInAs": {
|
||||
"description": "gpodder.net",
|
||||
"placeholders": {
|
||||
"userName": {}
|
||||
}
|
||||
},
|
||||
"login": "登入",
|
||||
"@login": {
|
||||
"description": "gpodder.net login"
|
||||
},
|
||||
"loginFailed": "登入失败",
|
||||
"@loginFailed": {},
|
||||
"logout": "注销",
|
||||
"@logout": {
|
||||
"description": "gpodder.net logout"
|
||||
},
|
||||
"mark": "标记",
|
||||
"@mark": {
|
||||
"description": "In listen history page, if a episode is marked as listened."
|
||||
@ -292,6 +324,10 @@
|
||||
},
|
||||
"oldestFirst": "由旧到新",
|
||||
"@oldestFirst": {},
|
||||
"passwdRequired": "密码为空",
|
||||
"@passwdRequired": {},
|
||||
"password": "密码",
|
||||
"@password": {},
|
||||
"pause": "暂停",
|
||||
"@pause": {},
|
||||
"play": "播放",
|
||||
@ -547,12 +583,18 @@
|
||||
"@skipToNext": {},
|
||||
"sleepTimer": "睡眠模式",
|
||||
"@sleepTimer": {},
|
||||
"status": "状态",
|
||||
"@status": {
|
||||
"description": "gpodder.net status"
|
||||
},
|
||||
"stop": "停止",
|
||||
"@stop": {},
|
||||
"subscribe": "订阅",
|
||||
"@subscribe": {},
|
||||
"subscribeExportDes": "导出 OPML 文件",
|
||||
"@subscribeExportDes": {},
|
||||
"syncNow": "立即同步",
|
||||
"@syncNow": {},
|
||||
"systemDefault": "系统默认",
|
||||
"@systemDefault": {},
|
||||
"timeLastPlayed": "上次播放{time}",
|
||||
@ -628,6 +670,10 @@
|
||||
"@updateEpisodesCount": {},
|
||||
"updateFailed": "更新失败",
|
||||
"@updateFailed": {},
|
||||
"username": "用户名",
|
||||
"@username": {},
|
||||
"usernameRequired": "用户名为空",
|
||||
"@usernameRequired": {},
|
||||
"version": "版本:{version}",
|
||||
"@version": {
|
||||
"placeholders": {
|
||||
|
@ -46,6 +46,13 @@ const String notificationLayoutKey = 'notificationLayoutKey';
|
||||
const String showNotesFontKey = 'showNotesFontKey';
|
||||
const String speedListKey = 'speedListKey';
|
||||
const String searchHistoryKey = 'searchHistoryKey';
|
||||
const String gpodderApiKey = 'gpodderApiKey';
|
||||
const String gpodderAddKey = 'gpodderAddKey';
|
||||
const String gpodderRemoveKey = 'gpodderRemoveKey';
|
||||
const String gpodderSyncStatusKey = 'gpodderSyncStatusKey';
|
||||
const String gpodderSyncDateTimeKey = 'gpodderSyncDateTimeKey';
|
||||
const String gpodderRemoteAddKey = 'gpodderRemoteAddKey';
|
||||
const String gpodderRemoteRemoveKey = 'gpodderRemoteRemoveKey';
|
||||
|
||||
class KeyValueStorage {
|
||||
final String key;
|
||||
@ -178,4 +185,13 @@ class KeyValueStorage {
|
||||
}
|
||||
return prefs.getDouble(key);
|
||||
}
|
||||
|
||||
Future<void> addList(List<String> addList) async {
|
||||
final list = await getStringList();
|
||||
await saveStringList(list..addAll(addList));
|
||||
}
|
||||
|
||||
Future<void> clearList() async {
|
||||
await saveStringList([]);
|
||||
}
|
||||
}
|
||||
|
@ -242,7 +242,7 @@ class DBHelper {
|
||||
return ['', ''];
|
||||
}
|
||||
|
||||
Future delPodcastLocal(String id) async {
|
||||
Future<void> delPodcastLocal(String id) async {
|
||||
var dbClient = await database;
|
||||
await dbClient.rawDelete('DELETE FROM PodcastLocal WHERE id =?', [id]);
|
||||
List<Map> list = await dbClient.rawQuery(
|
||||
@ -255,10 +255,10 @@ class DBHelper {
|
||||
}
|
||||
}
|
||||
await dbClient.rawDelete('DELETE FROM Episodes WHERE feed_id=?', [id]);
|
||||
var _milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
var milliseconds = DateTime.now().millisecondsSinceEpoch;
|
||||
await dbClient.rawUpdate(
|
||||
"""UPDATE SubscribeHistory SET remove_date = ? , status = ? WHERE id = ?""",
|
||||
[_milliseconds, 1, id]);
|
||||
[milliseconds, 1, id]);
|
||||
}
|
||||
|
||||
Future<void> saveHistory(PlayHistory history) async {
|
||||
|
@ -466,7 +466,7 @@ class __PodcastCardState extends State<_PodcastCard>
|
||||
splashColor: Colors.red.withAlpha(70),
|
||||
onPressed: () {
|
||||
groupList.removePodcast(
|
||||
widget.podcastLocal.id);
|
||||
widget.podcastLocal);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child: Text(
|
||||
|
@ -390,7 +390,7 @@ class _PodcastSettingState extends State<PodcastSetting> {
|
||||
FlatButton(
|
||||
splashColor: Colors.red.withAlpha(70),
|
||||
onPressed: () async {
|
||||
await groupList.removePodcast(widget.podcastLocal.id);
|
||||
await groupList.removePodcast(widget.podcastLocal);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
child:
|
||||
|
@ -59,7 +59,7 @@ class _AboutPodcastState extends State<AboutPodcast> {
|
||||
splashColor: context.accentColor.withAlpha(70),
|
||||
padding: EdgeInsets.all(10.0),
|
||||
onPressed: () {
|
||||
_groupList.removePodcast(widget.podcastLocal.id);
|
||||
_groupList.removePodcast(widget.podcastLocal);
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
textColor: Colors.red,
|
||||
|
239
lib/service/gpodder_api.dart
Normal file
239
lib/service/gpodder_api.dart
Normal file
@ -0,0 +1,239 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:developer' as developer;
|
||||
|
||||
import 'package:cookie_jar/cookie_jar.dart';
|
||||
import 'package:device_info/device_info.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:dio_cookie_manager/dio_cookie_manager.dart';
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
|
||||
enum GpodderSyncStatus { none, success, fail, authError }
|
||||
|
||||
class Gpodder {
|
||||
final _dio = Dio(BaseOptions(
|
||||
connectTimeout: 30000,
|
||||
receiveTimeout: 90000,
|
||||
sendTimeout: 90000,
|
||||
));
|
||||
final _storage = KeyValueStorage(gpodderApiKey);
|
||||
final _addStorage = KeyValueStorage(gpodderAddKey);
|
||||
final _removeStorage = KeyValueStorage(gpodderRemoveKey);
|
||||
final _remoteAddStorage = KeyValueStorage(gpodderRemoteAddKey);
|
||||
final _remoteRemoveStorage = KeyValueStorage(gpodderRemoteRemoveKey);
|
||||
final _dateTimeStorage = KeyValueStorage(gpodderSyncDateTimeKey);
|
||||
final _statusStorage = KeyValueStorage(gpodderSyncStatusKey);
|
||||
|
||||
final _baseUrl = "https://gpodder.net";
|
||||
|
||||
Future<void> _initDio() async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
var cookieJar = PersistCookieJar(dir: "${dir.path}/.cookies/");
|
||||
_dio.interceptors.add(CookieManager(cookieJar));
|
||||
}
|
||||
|
||||
Future<int> login({String username, String password}) async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
var cookieJar = PersistCookieJar(dir: "${dir.path}/.cookies/");
|
||||
cookieJar.delete(Uri.parse(_baseUrl));
|
||||
_dio.interceptors.add(CookieManager(cookieJar));
|
||||
final basicAuth =
|
||||
'Basic ${base64Encode(utf8.encode('$username:$password'))}';
|
||||
var status;
|
||||
Response response;
|
||||
try {
|
||||
response = await _dio.post('$_baseUrl/api/2/auth/$username/login.json',
|
||||
options:
|
||||
Options(headers: <String, String>{'authorization': basicAuth}));
|
||||
status = response.statusCode;
|
||||
} catch (e) {
|
||||
developer.log(e.toString(), name: 'gpoderr login error');
|
||||
return 0;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Future<int> logout() async {
|
||||
final loginInfo = await _storage.getStringList();
|
||||
final username = loginInfo[0];
|
||||
await _initDio();
|
||||
var status;
|
||||
try {
|
||||
var response = await _dio.post(
|
||||
'$_baseUrl/api/2/auth/$username/logout.json',
|
||||
);
|
||||
status = response.statusCode;
|
||||
} catch (e) {
|
||||
developer.log(e.toString(), name: 'gpoderr logout error');
|
||||
if (status == 400) {
|
||||
await _initService();
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (status == 200) {
|
||||
await _initService();
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Future<void> _initService() async {
|
||||
final dir = await getApplicationDocumentsDirectory();
|
||||
var cookieJar = PersistCookieJar(dir: "${dir.path}/.cookies/");
|
||||
cookieJar.delete(Uri.parse(_baseUrl));
|
||||
await _storage.clearList();
|
||||
await _addStorage.clearList();
|
||||
await _remoteAddStorage.clearList();
|
||||
await _removeStorage.clearList();
|
||||
await _remoteAddStorage.clearList();
|
||||
await _statusStorage.saveInt(0);
|
||||
await _dateTimeStorage.saveInt(0);
|
||||
}
|
||||
|
||||
Future<int> checkLogin(String username) async {
|
||||
await _initDio();
|
||||
var response = await _dio.post(
|
||||
'$_baseUrl/api/2/auth/$username/login.json',
|
||||
);
|
||||
final status = response.statusCode;
|
||||
return status;
|
||||
}
|
||||
|
||||
Future<int> updateDevice(String username) async {
|
||||
await _initDio();
|
||||
final deviceId = Uuid().v1();
|
||||
final androidInfo = await DeviceInfoPlugin().androidInfo;
|
||||
var status = 0;
|
||||
try {
|
||||
var response = await _dio
|
||||
.post("$_baseUrl/api/2/devices/$username/$deviceId.json", data: {
|
||||
"caption": "Tsacdop on ${androidInfo.model}",
|
||||
"type": "mobile"
|
||||
});
|
||||
status = response.statusCode;
|
||||
} catch (e) {
|
||||
developer.log(e.toString(), name: 'gpodder update device error');
|
||||
return 0;
|
||||
}
|
||||
if (status == 200) {
|
||||
await _storage.saveStringList([username, deviceId]);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Future<String> getAllPodcast() async {
|
||||
final loginInfo = await _storage.getStringList();
|
||||
final username = loginInfo[0];
|
||||
Response response;
|
||||
await _initDio();
|
||||
try {
|
||||
response = await _dio.get(
|
||||
'$_baseUrl/subscriptions/$username.opml',
|
||||
);
|
||||
} catch (e) {
|
||||
developer.log(e.toString(), name: 'gpodder update podcasts error');
|
||||
return '';
|
||||
}
|
||||
return response.data;
|
||||
}
|
||||
|
||||
Future<int> uploadSubscriptions() async {
|
||||
final syncDataTime = DateTime.now().millisecondsSinceEpoch;
|
||||
await _dateTimeStorage.saveInt(syncDataTime);
|
||||
final loginInfo = await _storage.getStringList();
|
||||
final username = loginInfo[0];
|
||||
final deviceId = loginInfo[1];
|
||||
await _initDio();
|
||||
final dbHelper = DBHelper();
|
||||
final podcasts = await dbHelper.getPodcastLocalAll();
|
||||
var subscriptions = '';
|
||||
for (var podcast in podcasts) {
|
||||
subscriptions += '${podcast.rssUrl}\n';
|
||||
}
|
||||
var status;
|
||||
try {
|
||||
final response = await _dio.put(
|
||||
'$_baseUrl/subscriptions/$username/$deviceId.txt',
|
||||
data: subscriptions);
|
||||
status = response.statusCode;
|
||||
} catch (e) {
|
||||
developer.log(e.toString(), name: 'gpodder update podcasts error');
|
||||
return 0;
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Future<int> getChanges() async {
|
||||
final loginInfo = await _storage.getStringList();
|
||||
final username = loginInfo[0];
|
||||
final deviceId = loginInfo[1];
|
||||
final syncDataTime = DateTime.now().millisecondsSinceEpoch;
|
||||
await _dateTimeStorage.saveInt(syncDataTime);
|
||||
final timeStamp = loginInfo.length == 3 ? int.parse(loginInfo[2]) : 0;
|
||||
var status;
|
||||
Response response;
|
||||
await _initDio();
|
||||
try {
|
||||
response = await _dio.get(
|
||||
"$_baseUrl/api/2/subscriptions/$username/$deviceId.json",
|
||||
queryParameters: {'since': timeStamp});
|
||||
status = response.statusCode;
|
||||
} catch (e) {
|
||||
developer.log(e.toString(), name: 'gpodder update podcasts error');
|
||||
if (status == 401) {
|
||||
_statusStorage.saveInt(3);
|
||||
} else {
|
||||
_statusStorage.saveInt(2);
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
if (status == 200) {
|
||||
Map changes = jsonDecode(response.toString());
|
||||
final timeStamp = changes['timestamp'];
|
||||
final addList = changes['add'].cast<String>();
|
||||
final removeList = changes['remove'].cast<String>();
|
||||
print(removeList);
|
||||
await _storage.saveStringList([username, deviceId, timeStamp.toString()]);
|
||||
await _remoteAddStorage.addList(addList);
|
||||
await _remoteRemoveStorage.addList(removeList);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
|
||||
Future<int> updateChange() async {
|
||||
final loginInfo = await _storage.getStringList();
|
||||
final addList = await _addStorage.getStringList();
|
||||
final removeList = await _removeStorage.getStringList();
|
||||
final username = loginInfo[0];
|
||||
final deviceId = loginInfo[1];
|
||||
await _initDio();
|
||||
var status;
|
||||
Response response;
|
||||
try {
|
||||
response = await _dio.post(
|
||||
'$_baseUrl/api/2/subscriptions/$username/$deviceId.json',
|
||||
data: {'add': addList, 'remove': removeList});
|
||||
status = response.statusCode;
|
||||
} catch (e) {
|
||||
if (status == 401) {
|
||||
_statusStorage.saveInt(3);
|
||||
} else {
|
||||
_statusStorage.saveInt(2);
|
||||
}
|
||||
developer.log(e.toString(), name: 'gpodder update podcasts error');
|
||||
return 0;
|
||||
}
|
||||
if (status == 200) {
|
||||
await _addStorage.clearList();
|
||||
await _removeStorage.clearList();
|
||||
await _statusStorage.saveInt(1);
|
||||
Map changes = jsonDecode(response.toString());
|
||||
final timeStamp = changes['timestamp'] as int;
|
||||
await _storage
|
||||
.saveStringList([username, deviceId, (timeStamp + 1).toString()]);
|
||||
}
|
||||
return status;
|
||||
}
|
||||
}
|
@ -1,5 +1,4 @@
|
||||
import 'dart:developer' as developer;
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:xml/xml.dart' as xml;
|
||||
import '../state/podcast_group.dart';
|
||||
@ -21,9 +20,9 @@ class OmplOutline {
|
||||
class PodcastsBackup {
|
||||
///Group list for backup.
|
||||
final List<PodcastGroup> groups;
|
||||
PodcastsBackup(this.groups) : assert(groups.length > 0);
|
||||
PodcastsBackup(this.groups) : assert(groups.isNotEmpty);
|
||||
|
||||
omplBuilder() {
|
||||
xml.XmlNode omplBuilder() {
|
||||
var builder = xml.XmlBuilder();
|
||||
builder.processing('xml', 'version="1.0" encoding="UTF-8"');
|
||||
builder.element('ompl', nest: () {
|
||||
@ -55,9 +54,9 @@ class PodcastsBackup {
|
||||
return builder.build();
|
||||
}
|
||||
|
||||
static parseOMPL(File file) {
|
||||
static parseOMPL(String opml) {
|
||||
var data = <String, List<OmplOutline>>{};
|
||||
var opml = file.readAsStringSync();
|
||||
// var opml = file.readAsStringSync();
|
||||
var content = xml.XmlDocument.parse(opml);
|
||||
var title =
|
||||
content.findAllElements('head').first.findElements('title').first.text;
|
@ -3,11 +3,11 @@ import 'dart:convert';
|
||||
import 'package:dio/dio.dart';
|
||||
|
||||
import '../.env.dart';
|
||||
import '../type/search_top_podcast.dart';
|
||||
import '../type/searchepisodes.dart';
|
||||
import '../type/searchpodcast.dart';
|
||||
import '../type/search_api/search_top_podcast.dart';
|
||||
import '../type/search_api/searchepisodes.dart';
|
||||
import '../type/search_api/searchpodcast.dart';
|
||||
|
||||
class SearchEngine {
|
||||
class ListenNotesSearch {
|
||||
final apiKey = environment['apiKey'];
|
||||
Future<SearchPodcast<dynamic>> searchPodcasts(
|
||||
{String searchText, int nextOffset}) async {
|
||||
@ -51,3 +51,16 @@ class SearchEngine {
|
||||
return searchResult;
|
||||
}
|
||||
}
|
||||
|
||||
class ItunesSearch {
|
||||
Future<SearchPodcast<dynamic>> searchPodcasts(
|
||||
{String searchText, int limit}) async {
|
||||
var url = "https://itunes.apple.com/search/search?q="
|
||||
"${Uri.encodeComponent(searchText)}${"&media=podcast&entity=podcast&limit=$limit"}";
|
||||
var response = await Dio()
|
||||
.get(url, options: Options(headers: {'Accept': "application/json"}));
|
||||
Map searchResultMap = jsonDecode(response.toString());
|
||||
var searchResult = SearchPodcast.fromJson(searchResultMap);
|
||||
return searchResult;
|
||||
}
|
||||
}
|
@ -3,17 +3,23 @@ import 'dart:developer' as developer;
|
||||
import 'dart:io';
|
||||
|
||||
import 'package:file_picker/file_picker.dart';
|
||||
import 'package:flutter/gestures.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter/services.dart';
|
||||
import 'package:flutter_file_dialog/flutter_file_dialog.dart';
|
||||
import 'package:fluttertoast/fluttertoast.dart';
|
||||
import 'package:line_icons/line_icons.dart';
|
||||
import 'package:path/path.dart';
|
||||
import 'package:path/path.dart' as path;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:provider/provider.dart';
|
||||
import 'package:tsacdop/util/custom_widget.dart';
|
||||
import 'package:tuple/tuple.dart';
|
||||
import 'package:wc_flutter_share/wc_flutter_share.dart';
|
||||
|
||||
import '../service/ompl_build.dart';
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../service/gpodder_api.dart';
|
||||
import '../service/opml_build.dart';
|
||||
import '../state/podcast_group.dart';
|
||||
import '../state/setting_state.dart';
|
||||
import '../type/settings_backup.dart';
|
||||
@ -25,17 +31,20 @@ class DataBackup extends StatefulWidget {
|
||||
}
|
||||
|
||||
class _DataBackupState extends State<DataBackup> {
|
||||
final _gpodder = Gpodder();
|
||||
var _syncing = false;
|
||||
|
||||
Future<File> _exportOmpl(BuildContext context) async {
|
||||
var groups = context.read<GroupList>().groups;
|
||||
var ompl = PodcastsBackup(groups).omplBuilder();
|
||||
var opml = PodcastsBackup(groups).omplBuilder();
|
||||
var tempdir = await getTemporaryDirectory();
|
||||
var now = DateTime.now();
|
||||
var datePlus = now.year.toString() +
|
||||
now.month.toString() +
|
||||
now.day.toString() +
|
||||
now.second.toString();
|
||||
var file = File(join(tempdir.path, 'tsacdop_ompl_$datePlus.xml'));
|
||||
await file.writeAsString(ompl.toString());
|
||||
var file = File(path.join(tempdir.path, 'tsacdop_ompl_$datePlus.xml'));
|
||||
await file.writeAsString(opml.toXmlString());
|
||||
return file;
|
||||
}
|
||||
|
||||
@ -63,12 +72,12 @@ class _DataBackupState extends State<DataBackup> {
|
||||
now.month.toString() +
|
||||
now.day.toString() +
|
||||
now.second.toString();
|
||||
var file = File(join(tempdir.path, 'tsacdop_settings_$datePlus.json'));
|
||||
var file = File(path.join(tempdir.path, 'tsacdop_settings_$datePlus.json'));
|
||||
await file.writeAsString(jsonEncode(json));
|
||||
return file;
|
||||
}
|
||||
|
||||
Future _importSetting(String path, BuildContext context) async {
|
||||
Future<void> _importSetting(String path, BuildContext context) async {
|
||||
final s = context.s;
|
||||
var settings = context.read<SettingState>();
|
||||
var file = File(path);
|
||||
@ -89,23 +98,83 @@ class _DataBackupState extends State<DataBackup> {
|
||||
}
|
||||
}
|
||||
|
||||
Widget _syncStauts(int index) {
|
||||
switch (index) {
|
||||
case 1:
|
||||
return Text('Success', style: TextStyle(color: Colors.green));
|
||||
break;
|
||||
case 2:
|
||||
return Text('Failed', style: TextStyle(color: Colors.red));
|
||||
break;
|
||||
case 3:
|
||||
return Text('Unauthorized', style: TextStyle(color: Colors.red));
|
||||
break;
|
||||
default:
|
||||
return Text('Unknown');
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void _getFilePath(BuildContext context) async {
|
||||
final s = context.s;
|
||||
try {
|
||||
var filePath = await FilePicker.getFilePath(type: FileType.any);
|
||||
if (filePath == '') {
|
||||
var filePickResult =
|
||||
await FilePicker.platform.pickFiles(type: FileType.any);
|
||||
if (filePickResult == null) {
|
||||
return;
|
||||
}
|
||||
Fluttertoast.showToast(
|
||||
msg: s.toastReadFile,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
final filePath = filePickResult.files.first.path;
|
||||
_importSetting(filePath, context);
|
||||
} on PlatformException catch (e) {
|
||||
developer.log(e.toString(), name: 'Get file path');
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _logout() async {
|
||||
await _gpodder.logout();
|
||||
final subscribeWorker = context.read<GroupList>();
|
||||
subscribeWorker.cancelWork();
|
||||
Fluttertoast.showToast(
|
||||
msg: 'Logout successfully',
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
if (mounted) setState(() {});
|
||||
}
|
||||
|
||||
Future<List<String>> _getLoginInfo() async {
|
||||
final storage = KeyValueStorage(gpodderApiKey);
|
||||
return await storage.getStringList();
|
||||
}
|
||||
|
||||
Future<void> _syncNow() async {
|
||||
setState(() {
|
||||
_syncing = true;
|
||||
});
|
||||
final gpodder = Gpodder();
|
||||
final status = await gpodder.getChanges();
|
||||
|
||||
if (status == 200) {
|
||||
final groupList = context.read<GroupList>();
|
||||
await gpodder.updateChange();
|
||||
await groupList.gpodderSyncNow();
|
||||
}
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_syncing = false;
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
Future<Tuple2<int, int>> _getSyncStatus() async {
|
||||
final syncDateTime = await KeyValueStorage(gpodderSyncDateTimeKey).getInt();
|
||||
final statusIndex = await KeyValueStorage(gpodderSyncStatusKey).getInt();
|
||||
return Tuple2(syncDateTime, statusIndex);
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = context.s;
|
||||
@ -129,13 +198,136 @@ class _DataBackupState extends State<DataBackup> {
|
||||
Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
),
|
||||
FutureBuilder<List<String>>(
|
||||
future: _getLoginInfo(),
|
||||
initialData: [],
|
||||
builder: (context, snapshot) {
|
||||
final loginInfo = snapshot.data;
|
||||
return Container(
|
||||
height: 160,
|
||||
width: double.infinity,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.center,
|
||||
crossAxisAlignment: CrossAxisAlignment.center,
|
||||
children: [
|
||||
Stack(
|
||||
children: [
|
||||
Hero(
|
||||
tag: 'gpodder.net',
|
||||
child: CircleAvatar(
|
||||
minRadius: 40,
|
||||
backgroundColor: context.primaryColor,
|
||||
child: SizedBox(
|
||||
height: 60,
|
||||
width: 60,
|
||||
child: Image.asset('assets/gpodder.png')),
|
||||
),
|
||||
),
|
||||
if (_syncing)
|
||||
Positioned(
|
||||
left: context.width / 2 - 40,
|
||||
child: SizedBox(
|
||||
height: 80,
|
||||
width: 80,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 1,
|
||||
),
|
||||
),
|
||||
),
|
||||
if (_syncing)
|
||||
Positioned(
|
||||
bottom: 39,
|
||||
left: context.width / 2 - 12,
|
||||
child: _OpenEye()),
|
||||
if (_syncing)
|
||||
Positioned(
|
||||
bottom: 39,
|
||||
left: context.width / 2 + 3,
|
||||
child: _OpenEye()),
|
||||
],
|
||||
),
|
||||
Text(
|
||||
loginInfo.isEmpty
|
||||
? s.intergateWith('gpodder.net')
|
||||
: s.loggedInAs(loginInfo.first),
|
||||
style: TextStyle(color: Colors.purple[700])),
|
||||
ButtonTheme(
|
||||
height: 32,
|
||||
child: OutlineButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
side: BorderSide(color: Colors.purple[700])),
|
||||
highlightedBorderColor: Colors.purple[700],
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
LineIcons.user,
|
||||
color: Colors.purple[700],
|
||||
size: context.textTheme.headline6.fontSize,
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Text(loginInfo.isEmpty ? s.login : s.logout,
|
||||
style:
|
||||
TextStyle(color: Colors.purple[700])),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
if (loginInfo.isEmpty) {
|
||||
Navigator.push(
|
||||
context,
|
||||
MaterialPageRoute(
|
||||
builder: (context) => _LoginGpodder(),
|
||||
fullscreenDialog: true));
|
||||
} else {
|
||||
_logout();
|
||||
}
|
||||
},
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
);
|
||||
}),
|
||||
FutureBuilder<List<String>>(
|
||||
future: _getLoginInfo(),
|
||||
initialData: [],
|
||||
builder: (context, snapshot) {
|
||||
final loginInfo = snapshot.data;
|
||||
if (loginInfo.isNotEmpty) {
|
||||
return ListTile(
|
||||
contentPadding:
|
||||
const EdgeInsets.only(left: 70.0, right: 20),
|
||||
onTap: _syncNow,
|
||||
title: Text(s.syncNow),
|
||||
subtitle: FutureBuilder<Tuple2<int, int>>(
|
||||
future: _getSyncStatus(),
|
||||
initialData: Tuple2(0, 0),
|
||||
builder: (context, snapshot) {
|
||||
final dateTime = snapshot.data.item1;
|
||||
final status = snapshot.data.item2;
|
||||
return Wrap(
|
||||
children: [
|
||||
Text(
|
||||
'${s.lastUpdate}: ${dateTime.toDate(context)}'),
|
||||
SizedBox(width: 8),
|
||||
Text('${s.status}: '),
|
||||
_syncStauts(status),
|
||||
],
|
||||
);
|
||||
}),
|
||||
);
|
||||
}
|
||||
return Center();
|
||||
}),
|
||||
Divider(height: 1),
|
||||
Container(
|
||||
height: 30.0,
|
||||
padding: EdgeInsets.symmetric(horizontal: 70),
|
||||
padding: EdgeInsets.fromLTRB(70, 0, 70, 0),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(s.subscribe,
|
||||
style: context.textTheme.bodyText1
|
||||
.copyWith(color: Theme.of(context).accentColor)),
|
||||
.copyWith(color: context.accentColor)),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
@ -147,49 +339,55 @@ class _DataBackupState extends State<DataBackup> {
|
||||
child: Row(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
children: [
|
||||
OutlineButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
side: BorderSide(color: Colors.green[700])),
|
||||
highlightedBorderColor: Colors.green[700],
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
LineIcons.save,
|
||||
color: Colors.green[700],
|
||||
size: context.textTheme.headline6.fontSize,
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Text(s.save,
|
||||
style: TextStyle(color: Colors.green[700])),
|
||||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
var file = await _exportOmpl(context);
|
||||
await _saveFile(file);
|
||||
}),
|
||||
ButtonTheme(
|
||||
height: 32,
|
||||
child: OutlineButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
side: BorderSide(color: Colors.green[700])),
|
||||
highlightedBorderColor: Colors.green[700],
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
LineIcons.save,
|
||||
color: Colors.green[700],
|
||||
size: context.textTheme.headline6.fontSize,
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Text(s.save,
|
||||
style: TextStyle(color: Colors.green[700])),
|
||||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
var file = await _exportOmpl(context);
|
||||
await _saveFile(file);
|
||||
}),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
OutlineButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
side: BorderSide(color: Colors.blue[700])),
|
||||
highlightedBorderColor: Colors.blue[700],
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.share,
|
||||
size: context.textTheme.headline6.fontSize,
|
||||
color: Colors.blue[700],
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Text(s.share,
|
||||
style: TextStyle(color: Colors.blue[700])),
|
||||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
var file = await _exportOmpl(context);
|
||||
await _shareFile(file);
|
||||
})
|
||||
ButtonTheme(
|
||||
height: 32,
|
||||
child: OutlineButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
side: BorderSide(color: Colors.blue[700])),
|
||||
highlightedBorderColor: Colors.blue[700],
|
||||
child: Row(
|
||||
children: [
|
||||
Icon(
|
||||
Icons.share,
|
||||
size: context.textTheme.headline6.fontSize,
|
||||
color: Colors.blue[700],
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Text(s.share,
|
||||
style: TextStyle(color: Colors.blue[700])),
|
||||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
var file = await _exportOmpl(context);
|
||||
await _shareFile(file);
|
||||
}),
|
||||
)
|
||||
],
|
||||
),
|
||||
),
|
||||
@ -210,81 +408,503 @@ class _DataBackupState extends State<DataBackup> {
|
||||
Padding(
|
||||
padding: EdgeInsets.only(left: 70.0, right: 10),
|
||||
child: Wrap(children: [
|
||||
OutlineButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
side: BorderSide(color: Colors.green[700])),
|
||||
highlightedBorderColor: Colors.green[700],
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
LineIcons.save,
|
||||
color: Colors.green[700],
|
||||
size: context.textTheme.headline6.fontSize,
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Text(s.save,
|
||||
style: TextStyle(color: Colors.green[700])),
|
||||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
var file = await _exportSetting(context);
|
||||
await _saveFile(file);
|
||||
}),
|
||||
SizedBox(width: 10),
|
||||
OutlineButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
side: BorderSide(color: Colors.blue[700])),
|
||||
highlightedBorderColor: Colors.blue[700],
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.share,
|
||||
size: context.textTheme.headline6.fontSize,
|
||||
color: Colors.blue[700],
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Text(s.share,
|
||||
style: TextStyle(color: Colors.blue[700])),
|
||||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
var file = await _exportSetting(context);
|
||||
await _shareFile(file);
|
||||
}),
|
||||
SizedBox(width: 10),
|
||||
ButtonTheme(
|
||||
height: 32,
|
||||
child: OutlineButton(
|
||||
highlightedBorderColor: Colors.red[700],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
side: BorderSide(color: Colors.red[700])),
|
||||
side: BorderSide(color: Colors.green[700])),
|
||||
highlightedBorderColor: Colors.green[700],
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
LineIcons.paperclip_solid,
|
||||
LineIcons.save,
|
||||
color: Colors.green[700],
|
||||
size: context.textTheme.headline6.fontSize,
|
||||
color: Colors.red[700],
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Text(s.import,
|
||||
style: TextStyle(color: Colors.red[700])),
|
||||
Text(s.save,
|
||||
style: TextStyle(color: Colors.green[700])),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
_getFilePath(context);
|
||||
onPressed: () async {
|
||||
var file = await _exportSetting(context);
|
||||
await _saveFile(file);
|
||||
}),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
ButtonTheme(
|
||||
height: 32,
|
||||
child: OutlineButton(
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
side: BorderSide(color: Colors.blue[700])),
|
||||
highlightedBorderColor: Colors.blue[700],
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
Icons.share,
|
||||
size: context.textTheme.headline6.fontSize,
|
||||
color: Colors.blue[700],
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Text(s.share,
|
||||
style: TextStyle(color: Colors.blue[700])),
|
||||
],
|
||||
),
|
||||
onPressed: () async {
|
||||
var file = await _exportSetting(context);
|
||||
await _shareFile(file);
|
||||
}),
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
ButtonTheme(
|
||||
height: 32,
|
||||
child: OutlineButton(
|
||||
highlightedBorderColor: Colors.red[700],
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.circular(100.0),
|
||||
side: BorderSide(color: Colors.red[700])),
|
||||
child: Row(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Icon(
|
||||
LineIcons.paperclip_solid,
|
||||
size: context.textTheme.headline6.fontSize,
|
||||
color: Colors.red[700],
|
||||
),
|
||||
SizedBox(width: 10),
|
||||
Text(s.import,
|
||||
style: TextStyle(color: Colors.red[700])),
|
||||
],
|
||||
),
|
||||
onPressed: () {
|
||||
_getFilePath(context);
|
||||
},
|
||||
),
|
||||
),
|
||||
]),
|
||||
),
|
||||
Divider()
|
||||
Divider(height: 1)
|
||||
],
|
||||
)),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class _OpenEye extends StatefulWidget {
|
||||
_OpenEye({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
__OpenEyeState createState() => __OpenEyeState();
|
||||
}
|
||||
|
||||
class __OpenEyeState extends State<_OpenEye>
|
||||
with SingleTickerProviderStateMixin {
|
||||
double _radius = 0.0;
|
||||
Animation _animation;
|
||||
AnimationController _controller;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
_controller =
|
||||
AnimationController(vsync: this, duration: Duration(seconds: 1));
|
||||
_animation = Tween(begin: 0.0, end: 1.0).animate(_controller)
|
||||
..addListener(() {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_radius = _animation.value;
|
||||
});
|
||||
}
|
||||
});
|
||||
_controller.forward();
|
||||
_controller.addStatusListener((status) async {
|
||||
if (status == AnimationStatus.completed) {
|
||||
await Future.delayed(Duration(milliseconds: 400));
|
||||
_controller.reverse();
|
||||
} else if (status == AnimationStatus.dismissed) {
|
||||
_controller.forward();
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@override
|
||||
void dispose() {
|
||||
_controller.dispose();
|
||||
super.dispose();
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return DotIndicator(radius: 8 * _radius + 0.5, color: Colors.white);
|
||||
}
|
||||
}
|
||||
|
||||
enum LoginStatus { none, error, start, syncing, complete }
|
||||
|
||||
class _LoginGpodder extends StatefulWidget {
|
||||
_LoginGpodder({Key key}) : super(key: key);
|
||||
|
||||
@override
|
||||
__LoginGpodderState createState() => __LoginGpodderState();
|
||||
}
|
||||
|
||||
class __LoginGpodderState extends State<_LoginGpodder> {
|
||||
var _username = '';
|
||||
var _password = '';
|
||||
LoginStatus _loginStatus;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
_loginStatus = LoginStatus.none;
|
||||
super.initState();
|
||||
}
|
||||
|
||||
final GlobalKey<FormFieldState<String>> _passwordFieldKey =
|
||||
GlobalKey<FormFieldState<String>>();
|
||||
final GlobalKey<FormState> _formKey = GlobalKey<FormState>();
|
||||
final _gpodder = Gpodder();
|
||||
|
||||
Future<void> _handleLogin() async {
|
||||
setState(() => _loginStatus = LoginStatus.start);
|
||||
final form = _formKey.currentState;
|
||||
if (form.validate()) {
|
||||
form.save();
|
||||
final status =
|
||||
await _gpodder.login(username: _username, password: _password);
|
||||
if (status == 200) {
|
||||
final updateDevice = await _gpodder.updateDevice(_username);
|
||||
if (updateDevice == 200) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_loginStatus = LoginStatus.syncing;
|
||||
});
|
||||
}
|
||||
final uploadStatus = await _gpodder.uploadSubscriptions();
|
||||
await _getSubscriptions(_gpodder);
|
||||
if (uploadStatus == 200) {
|
||||
if (mounted) {
|
||||
setState(() {
|
||||
_loginStatus = LoginStatus.complete;
|
||||
});
|
||||
}
|
||||
}
|
||||
} else {
|
||||
if (mounted) setState(() => _loginStatus = LoginStatus.error);
|
||||
Fluttertoast.showToast(
|
||||
msg: context.s.loginFailed,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (mounted) setState(() => _loginStatus = LoginStatus.error);
|
||||
Fluttertoast.showToast(
|
||||
msg: context.s.loginFailed,
|
||||
gravity: ToastGravity.BOTTOM,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
if (mounted) setState(() => _loginStatus = LoginStatus.none);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _getSubscriptions(Gpodder gpodder) async {
|
||||
var subscribeWorker = context.read<GroupList>();
|
||||
var rssExp = RegExp(r'^(https?):\/\/(.*)');
|
||||
final opml = await gpodder.getAllPodcast();
|
||||
if (opml != '') {
|
||||
Map<String, List<OmplOutline>> data = PodcastsBackup.parseOMPL(opml);
|
||||
for (var entry in data.entries) {
|
||||
var list = entry.value.reversed;
|
||||
for (var rss in list) {
|
||||
var rssLink = rssExp.stringMatch(rss.xmlUrl);
|
||||
if (rssLink != null) {
|
||||
final dbHelper = DBHelper();
|
||||
final exist = dbHelper.checkPodcast(rssLink);
|
||||
if (exist == '') {
|
||||
var item = SubscribeItem(rssLink, rss.text, group: 'Home');
|
||||
await subscribeWorker.setSubscribeItem(item, syncGpodder: false);
|
||||
await Future.delayed(Duration(milliseconds: 200));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
await subscribeWorker.cancelWork();
|
||||
subscribeWorker.setWorkManager(4);
|
||||
}
|
||||
|
||||
String _validateName(String value) {
|
||||
if (value.isEmpty) {
|
||||
return context.s.usernameRequired;
|
||||
}
|
||||
final nameExp = RegExp(r'^[A-Za-z ]+$');
|
||||
if (!nameExp.hasMatch(value)) {
|
||||
return context.s.invalidName;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
String _validatePassword(String value) {
|
||||
final passwordField = _passwordFieldKey.currentState;
|
||||
if (passwordField.value == null || passwordField.value.isEmpty) {
|
||||
return context.s.passwdRequired;
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
Widget _loginStatusButton() {
|
||||
switch (_loginStatus) {
|
||||
case LoginStatus.none:
|
||||
return Text(
|
||||
context.s.login,
|
||||
style: TextStyle(color: Colors.white),
|
||||
);
|
||||
break;
|
||||
case LoginStatus.syncing:
|
||||
return Text(
|
||||
context.s.settingsSyncing,
|
||||
style: TextStyle(color: Colors.white),
|
||||
);
|
||||
break;
|
||||
case LoginStatus.start:
|
||||
return SizedBox(
|
||||
height: 20,
|
||||
width: 20,
|
||||
child: CircularProgressIndicator(
|
||||
strokeWidth: 2,
|
||||
valueColor: AlwaysStoppedAnimation<Color>(Colors.white),
|
||||
),
|
||||
);
|
||||
default:
|
||||
return Text(
|
||||
context.s.login,
|
||||
style: TextStyle(color: Colors.white),
|
||||
);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
final s = context.s;
|
||||
return AnnotatedRegion<SystemUiOverlayStyle>(
|
||||
value: SystemUiOverlayStyle(
|
||||
statusBarIconBrightness: Brightness.dark,
|
||||
systemNavigationBarColor: Theme.of(context).primaryColor,
|
||||
systemNavigationBarIconBrightness:
|
||||
Theme.of(context).accentColorBrightness,
|
||||
),
|
||||
child: Scaffold(
|
||||
resizeToAvoidBottomInset: true,
|
||||
body: SafeArea(
|
||||
top: false,
|
||||
child: CustomScrollView(
|
||||
slivers: [
|
||||
SliverAppBar(
|
||||
brightness: Brightness.dark,
|
||||
iconTheme: IconThemeData(
|
||||
color: Colors.white,
|
||||
),
|
||||
elevation: 0,
|
||||
backgroundColor: context.accentColor,
|
||||
expandedHeight: 200,
|
||||
flexibleSpace: Container(
|
||||
height: 200,
|
||||
width: double.infinity,
|
||||
color: context.accentColor,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.end,
|
||||
children: [
|
||||
Hero(
|
||||
tag: 'gpodder.net',
|
||||
child: CircleAvatar(
|
||||
minRadius: 50,
|
||||
backgroundColor:
|
||||
context.primaryColor.withOpacity(0.3),
|
||||
child: SizedBox(
|
||||
height: 80,
|
||||
width: 80,
|
||||
child: Image.asset('assets/gpodder.png')),
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(8.0),
|
||||
child: Text(s.intergateWith('gpodder.net'),
|
||||
style: TextStyle(
|
||||
color: Colors.white,
|
||||
fontWeight: FontWeight.bold)),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
_loginStatus == LoginStatus.complete
|
||||
? SliverList(
|
||||
delegate: SliverChildListDelegate([
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(40.0, 50, 40, 100),
|
||||
child: Text(
|
||||
s.gpodderLoginDes,
|
||||
textAlign: TextAlign.center,
|
||||
),
|
||||
),
|
||||
Center(
|
||||
child: OutlineButton(
|
||||
onPressed: () {
|
||||
Navigator.of(context).pop();
|
||||
},
|
||||
highlightedBorderColor: context.accentColor,
|
||||
child: Text(s.back)),
|
||||
)
|
||||
]),
|
||||
)
|
||||
: Form(
|
||||
key: _formKey,
|
||||
autovalidate: false,
|
||||
child: AutofillGroup(
|
||||
child: SliverList(
|
||||
delegate: SliverChildListDelegate(
|
||||
[
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(40, 20, 40, 10),
|
||||
child: TextFormField(
|
||||
decoration: InputDecoration(
|
||||
labelStyle:
|
||||
TextStyle(color: context.accentColor),
|
||||
focusColor: context.accentColor,
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: context.accentColor,
|
||||
width: 2)),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(
|
||||
color: context.accentColor)),
|
||||
labelText: s.username,
|
||||
),
|
||||
maxLines: 1,
|
||||
autofocus: true,
|
||||
validator: _validateName,
|
||||
autofillHints: [AutofillHints.username],
|
||||
onSaved: (value) {
|
||||
setState(() => _username = value);
|
||||
},
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(40, 10, 40, 20),
|
||||
child: PasswordField(
|
||||
fieldKey: _passwordFieldKey,
|
||||
labelText: s.password,
|
||||
validator: _validatePassword,
|
||||
onSaved: (value) {
|
||||
setState(() {
|
||||
_password = value;
|
||||
});
|
||||
},
|
||||
),
|
||||
),
|
||||
Padding(
|
||||
padding:
|
||||
const EdgeInsets.fromLTRB(40, 10, 40, 20),
|
||||
child: InkWell(
|
||||
onTap: () {
|
||||
_handleLogin();
|
||||
},
|
||||
borderRadius: BorderRadius.circular(5.0),
|
||||
child: Container(
|
||||
height: 40,
|
||||
width: 150,
|
||||
decoration: BoxDecoration(
|
||||
color: context.accentColor,
|
||||
borderRadius:
|
||||
BorderRadius.circular(5.0)),
|
||||
child: Center(child: _loginStatusButton()),
|
||||
),
|
||||
),
|
||||
),
|
||||
SizedBox(
|
||||
height:
|
||||
MediaQuery.of(context).viewInsets.bottom,
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
class PasswordField extends StatefulWidget {
|
||||
const PasswordField({
|
||||
this.fieldKey,
|
||||
this.hintText,
|
||||
this.labelText,
|
||||
this.helperText,
|
||||
this.onSaved,
|
||||
this.validator,
|
||||
this.onFieldSubmitted,
|
||||
});
|
||||
|
||||
final Key fieldKey;
|
||||
final String hintText;
|
||||
final String labelText;
|
||||
final String helperText;
|
||||
final FormFieldSetter<String> onSaved;
|
||||
final FormFieldValidator<String> validator;
|
||||
final ValueChanged<String> onFieldSubmitted;
|
||||
|
||||
@override
|
||||
_PasswordFieldState createState() => _PasswordFieldState();
|
||||
}
|
||||
|
||||
class _PasswordFieldState extends State<PasswordField> {
|
||||
bool _obscureText = true;
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return TextFormField(
|
||||
key: widget.fieldKey,
|
||||
obscureText: _obscureText,
|
||||
autofillHints: [AutofillHints.password],
|
||||
maxLength: 100,
|
||||
onSaved: widget.onSaved,
|
||||
validator: widget.validator,
|
||||
onFieldSubmitted: widget.onFieldSubmitted,
|
||||
decoration: InputDecoration(
|
||||
hintStyle: TextStyle(color: context.accentColor),
|
||||
labelStyle: TextStyle(color: context.accentColor),
|
||||
border: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: context.accentColor)),
|
||||
focusedBorder: OutlineInputBorder(
|
||||
borderSide: BorderSide(color: context.accentColor, width: 2)),
|
||||
hintText: widget.hintText,
|
||||
labelText: widget.labelText,
|
||||
helperText: widget.helperText,
|
||||
suffixIcon: GestureDetector(
|
||||
dragStartBehavior: DragStartBehavior.down,
|
||||
onTap: () {
|
||||
setState(() {
|
||||
_obscureText = !_obscureText;
|
||||
});
|
||||
},
|
||||
child: Icon(
|
||||
_obscureText ? Icons.visibility : Icons.visibility_off,
|
||||
color: context.accentColor,
|
||||
semanticLabel: _obscureText ? 'Show' : 'Hide',
|
||||
),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -13,7 +13,7 @@ import 'package:webfeed/webfeed.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../state/podcast_group.dart';
|
||||
import '../type/play_histroy.dart';
|
||||
import '../type/searchpodcast.dart';
|
||||
import '../type/search_api/searchpodcast.dart';
|
||||
import '../type/sub_history.dart';
|
||||
import '../util/extension_helper.dart';
|
||||
|
||||
@ -128,7 +128,7 @@ class _PlayedHistoryState extends State<PlayedHistory>
|
||||
Theme.of(context).accentColorBrightness,
|
||||
),
|
||||
child: Scaffold(
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
backgroundColor: context.primaryColor,
|
||||
body: SafeArea(
|
||||
child: NestedScrollView(
|
||||
headerSliverBuilder: (context, innerBoxScrolled) {
|
||||
|
@ -62,6 +62,7 @@ class _SettingsState extends State<Settings> {
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
@ -71,9 +72,7 @@ class _SettingsState extends State<Settings> {
|
||||
padding: EdgeInsets.symmetric(horizontal: 70),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(s.settingsPrefrence,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
style: context.textTheme.bodyText1
|
||||
.copyWith(color: context.accentColor)),
|
||||
),
|
||||
ListTile(
|
||||
|
@ -26,97 +26,73 @@ class SyncingSetting extends StatelessWidget {
|
||||
backgroundColor: Theme.of(context).primaryColor,
|
||||
),
|
||||
body: SingleChildScrollView(
|
||||
scrollDirection: Axis.vertical,
|
||||
child: Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Selector<SettingState, Tuple2<bool, int>>(
|
||||
selector: (_, settings) =>
|
||||
Tuple2(settings.autoUpdate, settings.updateInterval),
|
||||
builder: (_, data, __) => Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: EdgeInsets.all(10.0),
|
||||
),
|
||||
Container(
|
||||
height: 30.0,
|
||||
padding: const EdgeInsets.symmetric(horizontal: 70),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(s.settingsSyncing,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.copyWith(color: Theme.of(context).accentColor)),
|
||||
),
|
||||
Padding(
|
||||
padding: const EdgeInsets.all(5.0),
|
||||
),
|
||||
ListView(
|
||||
physics: const BouncingScrollPhysics(),
|
||||
shrinkWrap: true,
|
||||
scrollDirection: Axis.vertical,
|
||||
children: <Widget>[
|
||||
ListTile(
|
||||
onTap: () {
|
||||
if (settings.autoUpdate) {
|
||||
settings.autoUpdate = false;
|
||||
settings.cancelWork();
|
||||
} else {
|
||||
settings.autoUpdate = true;
|
||||
settings.setWorkManager(data.item2);
|
||||
}
|
||||
},
|
||||
contentPadding: const EdgeInsets.only(
|
||||
left: 70.0, right: 20, bottom: 10),
|
||||
title: Text(s.settingsEnableSyncing),
|
||||
subtitle: Text(s.settingsEnableSyncingDes),
|
||||
trailing: Transform.scale(
|
||||
scale: 0.9,
|
||||
child: Switch(
|
||||
value: data.item1,
|
||||
onChanged: (boo) async {
|
||||
settings.autoUpdate = boo;
|
||||
if (boo) {
|
||||
settings.setWorkManager(data.item2);
|
||||
} else {
|
||||
settings.cancelWork();
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding:
|
||||
const EdgeInsets.only(left: 70.0, right: 20),
|
||||
title: Text(s.settingsUpdateInterval),
|
||||
subtitle: Text(s.settingsUpdateIntervalDes),
|
||||
trailing: MyDropdownButton(
|
||||
hint: Text(s.hoursCount(data.item2)),
|
||||
underline: Center(),
|
||||
elevation: 1,
|
||||
displayItemCount: 5,
|
||||
value: data.item2,
|
||||
onChanged: data.item1
|
||||
? (value) async {
|
||||
await settings.cancelWork();
|
||||
settings.setWorkManager(value);
|
||||
}
|
||||
: null,
|
||||
items: <int>[1, 2, 4, 8, 24, 48]
|
||||
.map<DropdownMenuItem<int>>((e) {
|
||||
return DropdownMenuItem<int>(
|
||||
value: e, child: Text(s.hoursCount(e)));
|
||||
}).toList()),
|
||||
),
|
||||
Divider(height: 1),
|
||||
],
|
||||
),
|
||||
],
|
||||
child: Selector<SettingState, Tuple2<bool, int>>(
|
||||
selector: (_, settings) =>
|
||||
Tuple2(settings.autoUpdate, settings.updateInterval),
|
||||
builder: (_, data, __) => Column(
|
||||
mainAxisAlignment: MainAxisAlignment.start,
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: <Widget>[
|
||||
Padding(
|
||||
padding: const EdgeInsets.fromLTRB(70, 20, 70, 10),
|
||||
child: Text(s.settingsSyncing,
|
||||
style: context.textTheme.bodyText1
|
||||
.copyWith(color: context.accentColor)),
|
||||
),
|
||||
),
|
||||
],
|
||||
ListTile(
|
||||
onTap: () {
|
||||
if (settings.autoUpdate) {
|
||||
settings.autoUpdate = false;
|
||||
settings.cancelWork();
|
||||
} else {
|
||||
settings.autoUpdate = true;
|
||||
settings.setWorkManager(data.item2);
|
||||
}
|
||||
},
|
||||
contentPadding:
|
||||
const EdgeInsets.only(left: 70.0, right: 20, bottom: 10),
|
||||
title: Text(s.settingsEnableSyncing),
|
||||
subtitle: Text(s.settingsEnableSyncingDes),
|
||||
trailing: Transform.scale(
|
||||
scale: 0.9,
|
||||
child: Switch(
|
||||
value: data.item1,
|
||||
onChanged: (boo) async {
|
||||
settings.autoUpdate = boo;
|
||||
if (boo) {
|
||||
settings.setWorkManager(data.item2);
|
||||
} else {
|
||||
settings.cancelWork();
|
||||
}
|
||||
}),
|
||||
),
|
||||
),
|
||||
ListTile(
|
||||
contentPadding: const EdgeInsets.only(left: 70.0, right: 20),
|
||||
title: Text(s.settingsUpdateInterval),
|
||||
subtitle: Text(s.settingsUpdateIntervalDes),
|
||||
trailing: MyDropdownButton(
|
||||
hint: Text(s.hoursCount(data.item2)),
|
||||
underline: Center(),
|
||||
elevation: 1,
|
||||
displayItemCount: 5,
|
||||
value: data.item2,
|
||||
onChanged: data.item1
|
||||
? (value) async {
|
||||
await settings.cancelWork();
|
||||
settings.setWorkManager(value);
|
||||
}
|
||||
: null,
|
||||
items: <int>[1, 2, 4, 8, 24, 48]
|
||||
.map<DropdownMenuItem<int>>((e) {
|
||||
return DropdownMenuItem<int>(
|
||||
value: e, child: Text(s.hoursCount(e)));
|
||||
}).toList()),
|
||||
),
|
||||
Divider(height: 1),
|
||||
],
|
||||
),
|
||||
),
|
||||
),
|
||||
),
|
||||
|
@ -185,10 +185,8 @@ class ThemeSetting extends StatelessWidget {
|
||||
padding: EdgeInsets.symmetric(horizontal: 70),
|
||||
alignment: Alignment.centerLeft,
|
||||
child: Text(s.fontStyle,
|
||||
style: Theme.of(context)
|
||||
.textTheme
|
||||
.bodyText1
|
||||
.copyWith(color: Theme.of(context).accentColor)),
|
||||
style: context.textTheme.bodyText1
|
||||
.copyWith(color: context.accentColor)),
|
||||
),
|
||||
Selector<SettingState, int>(
|
||||
selector: (_, setting) => setting.showNotesFontIndex,
|
||||
|
@ -3,23 +3,39 @@ import 'dart:developer' as developer;
|
||||
import 'dart:io';
|
||||
import 'dart:isolate';
|
||||
import 'dart:math' as math;
|
||||
import 'dart:ui';
|
||||
|
||||
import 'package:color_thief_flutter/color_thief_flutter.dart';
|
||||
import 'package:dio/dio.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:flutter/foundation.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
import 'package:flutter_isolate/flutter_isolate.dart';
|
||||
import 'package:image/image.dart' as img;
|
||||
import 'package:path_provider/path_provider.dart';
|
||||
import 'package:webfeed/webfeed.dart';
|
||||
import 'package:uuid/uuid.dart';
|
||||
import 'package:equatable/equatable.dart';
|
||||
import 'package:webfeed/webfeed.dart';
|
||||
import 'package:workmanager/workmanager.dart';
|
||||
|
||||
import '../local_storage/key_value_storage.dart';
|
||||
import '../local_storage/sqflite_localpodcast.dart';
|
||||
import '../service/gpodder_api.dart';
|
||||
import '../type/fireside_data.dart';
|
||||
import '../type/podcastlocal.dart';
|
||||
|
||||
void callbackDispatcher() {
|
||||
if (Platform.isAndroid) {
|
||||
Workmanager.executeTask((task, inputData) async {
|
||||
final gpodder = Gpodder();
|
||||
final status = await gpodder.getChanges();
|
||||
if (status == 200) {
|
||||
await gpodder.updateChange();
|
||||
}
|
||||
return Future.value(true);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
class GroupEntity {
|
||||
final String name;
|
||||
final String id;
|
||||
@ -134,16 +150,21 @@ class SubscribeItem {
|
||||
|
||||
///Podcast group, default Home.
|
||||
String group;
|
||||
|
||||
///sync to gpodder
|
||||
bool syncWithGpodder;
|
||||
SubscribeItem(this.url, this.title,
|
||||
{this.subscribeState = SubscribeState.none,
|
||||
this.id = '',
|
||||
this.imgUrl = '',
|
||||
this.group = ''});
|
||||
this.group = '',
|
||||
this.syncWithGpodder = true});
|
||||
}
|
||||
|
||||
class GroupList extends ChangeNotifier {
|
||||
/// List of all gourps.
|
||||
final List<PodcastGroup> _groups = [];
|
||||
|
||||
List<PodcastGroup> get groups => _groups;
|
||||
|
||||
final DBHelper _dbHelper = DBHelper();
|
||||
@ -155,6 +176,7 @@ class GroupList extends ChangeNotifier {
|
||||
|
||||
/// Default false, true during loading groups from storage.
|
||||
bool _isLoading = false;
|
||||
|
||||
bool get isLoading => _isLoading;
|
||||
|
||||
/// Svae ordered gourps info before saved.
|
||||
@ -177,8 +199,9 @@ class GroupList extends ChangeNotifier {
|
||||
|
||||
/// Add subsribe item
|
||||
SubscribeItem _subscribeItem;
|
||||
setSubscribeItem(SubscribeItem item) async {
|
||||
setSubscribeItem(SubscribeItem item, {bool syncGpodder = true}) async {
|
||||
_subscribeItem = item;
|
||||
if (syncGpodder) _syncAdd(item.url);
|
||||
await _start();
|
||||
}
|
||||
|
||||
@ -187,6 +210,13 @@ class GroupList extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
Future<void> _syncAdd(String rssUrl) async {
|
||||
final check = await _checkGpodderLoggedin();
|
||||
if (check) {
|
||||
await _addStorage.addList([rssUrl]);
|
||||
}
|
||||
}
|
||||
|
||||
Future _start() async {
|
||||
if (_created == false) {
|
||||
await _createIsolate();
|
||||
@ -197,7 +227,8 @@ class GroupList extends ChangeNotifier {
|
||||
_subscribeItem.url,
|
||||
_subscribeItem.title,
|
||||
_subscribeItem.imgUrl,
|
||||
_subscribeItem.group
|
||||
_subscribeItem.group,
|
||||
_subscribeItem.syncWithGpodder
|
||||
]);
|
||||
}
|
||||
}
|
||||
@ -217,7 +248,8 @@ class GroupList extends ChangeNotifier {
|
||||
_subscribeItem.url,
|
||||
_subscribeItem.title,
|
||||
_subscribeItem.imgUrl,
|
||||
_subscribeItem.group
|
||||
_subscribeItem.group,
|
||||
_subscribeItem.syncWithGpodder
|
||||
]);
|
||||
} else if (message is List) {
|
||||
_setCurrentSubscribeItem(SubscribeItem(
|
||||
@ -238,6 +270,65 @@ class GroupList extends ChangeNotifier {
|
||||
});
|
||||
}
|
||||
|
||||
///Set gpodder sync
|
||||
final _loginInfp = KeyValueStorage(gpodderApiKey);
|
||||
final _addStorage = KeyValueStorage(gpodderAddKey);
|
||||
final _removeStorage = KeyValueStorage(gpodderRemoveKey);
|
||||
final _remoteAddStorage = KeyValueStorage(gpodderRemoteAddKey);
|
||||
final _remoteRemoveStorage = KeyValueStorage(gpodderRemoteRemoveKey);
|
||||
|
||||
Future<bool> _checkGpodderLoggedin() async {
|
||||
final loginInfo = await _loginInfp.getStringList();
|
||||
return loginInfo.isNotEmpty;
|
||||
}
|
||||
|
||||
Future<void> gpodderSyncNow() async {
|
||||
final addList = await _remoteAddStorage.getStringList();
|
||||
final removeList = await _remoteRemoveStorage.getStringList();
|
||||
|
||||
if (removeList.isNotEmpty) {
|
||||
for (var rssLink in removeList) {
|
||||
final exist = await _dbHelper.checkPodcast(rssLink);
|
||||
if (exist != '') {
|
||||
await _unsubscribe(exist);
|
||||
}
|
||||
}
|
||||
await _remoteAddStorage.clearList();
|
||||
}
|
||||
if (addList.isNotEmpty) {
|
||||
for (var rssLink in addList) {
|
||||
final exist = await _dbHelper.checkPodcast(rssLink);
|
||||
if (exist == '') {
|
||||
var item = SubscribeItem(rssLink, rssLink, group: 'Home');
|
||||
_subscribeItem = item;
|
||||
await _start();
|
||||
|
||||
await Future.delayed(Duration(milliseconds: 200));
|
||||
}
|
||||
}
|
||||
await _remoteRemoveStorage.clearList();
|
||||
}
|
||||
}
|
||||
|
||||
void setWorkManager(int hour) {
|
||||
Workmanager.initialize(
|
||||
callbackDispatcher,
|
||||
isInDebugMode: false,
|
||||
);
|
||||
Workmanager.registerPeriodicTask("2", "gpodder_sync",
|
||||
frequency: Duration(hours: hour),
|
||||
initialDelay: Duration(seconds: 10),
|
||||
constraints: Constraints(
|
||||
networkType: NetworkType.connected,
|
||||
));
|
||||
developer.log('work manager init done + (gpodder sync)');
|
||||
}
|
||||
|
||||
Future cancelWork() async {
|
||||
await Workmanager.cancelByUniqueName('2');
|
||||
developer.log('work job cancelled');
|
||||
}
|
||||
|
||||
void addToOrderChanged(PodcastGroup group) {
|
||||
_orderChanged.add(group);
|
||||
notifyListeners();
|
||||
@ -261,6 +352,7 @@ class GroupList extends ChangeNotifier {
|
||||
@override
|
||||
void addListener(VoidCallback listener) {
|
||||
loadGroups().then((value) => super.addListener(listener));
|
||||
gpodderSyncNow();
|
||||
}
|
||||
|
||||
@override
|
||||
@ -414,7 +506,14 @@ class GroupList extends ChangeNotifier {
|
||||
}
|
||||
|
||||
/// Unsubscribe podcast
|
||||
Future<void> removePodcast(String id) async {
|
||||
Future<void> _syncRemove(String rssUrl) async {
|
||||
final check = await _checkGpodderLoggedin();
|
||||
if (check) {
|
||||
await _removeStorage.addList([rssUrl]);
|
||||
}
|
||||
}
|
||||
|
||||
Future<void> _unsubscribe(String id) async {
|
||||
_isLoading = true;
|
||||
notifyListeners();
|
||||
for (var group in _groups) {
|
||||
@ -429,7 +528,15 @@ class GroupList extends ChangeNotifier {
|
||||
notifyListeners();
|
||||
}
|
||||
|
||||
saveOrder(PodcastGroup group) async {
|
||||
Future<void> removePodcast(
|
||||
PodcastLocal podcast,
|
||||
) async {
|
||||
_syncRemove(podcast.rssUrl);
|
||||
final id = podcast.id;
|
||||
await _unsubscribe(id);
|
||||
}
|
||||
|
||||
Future<void> saveOrder(PodcastGroup group) async {
|
||||
group.podcastList = group.orderedPodcasts.map((e) => e.id).toList();
|
||||
await _saveGroup();
|
||||
await group.getPodcasts();
|
||||
@ -563,6 +670,13 @@ Future<void> subIsolateEntryPoint(SendPort sendPort) async {
|
||||
}
|
||||
await dbHelper.savePodcastRss(p, uuid);
|
||||
|
||||
// if (item.syncWithGpodder) {
|
||||
// final gpodder = Gpodder();
|
||||
// await gpodder.updateChange({
|
||||
// 'add': [item.url]
|
||||
// });
|
||||
// }
|
||||
|
||||
sendPort.send([item.title, item.url, 3, uuid]);
|
||||
|
||||
await Future.delayed(Duration(seconds: 2));
|
||||
@ -600,9 +714,9 @@ Future<void> subIsolateEntryPoint(SendPort sendPort) async {
|
||||
}
|
||||
|
||||
subReceivePort.distinct().listen((message) {
|
||||
if (message is List<String>) {
|
||||
if (message is List<dynamic>) {
|
||||
items.add(SubscribeItem(message[0], message[1],
|
||||
imgUrl: message[2], group: message[3]));
|
||||
imgUrl: message[2], group: message[3], syncWithGpodder: message[4]));
|
||||
if (!_running) {
|
||||
_subscribe(items.first);
|
||||
_running = true;
|
||||
|
@ -1,5 +1,5 @@
|
||||
import 'package:flutter/material.dart';
|
||||
import '../type/searchpodcast.dart';
|
||||
import '../type/search_api/searchpodcast.dart';
|
||||
|
||||
class SearchState extends ChangeNotifier {
|
||||
final List<OnlinePodcast> _subscribedList = [];
|
||||
|
@ -161,7 +161,7 @@ class SettingState extends ChangeNotifier {
|
||||
}
|
||||
|
||||
Future cancelWork() async {
|
||||
await Workmanager.cancelAll();
|
||||
await Workmanager.cancelByUniqueName('1');
|
||||
developer.log('work job cancelled');
|
||||
}
|
||||
|
||||
|
67
lib/type/search_api/itunes_podcast.dart
Normal file
67
lib/type/search_api/itunes_podcast.dart
Normal file
@ -0,0 +1,67 @@
|
||||
import 'package:intl/intl.dart';
|
||||
import 'package:json_annotation/json_annotation.dart';
|
||||
|
||||
import 'searchpodcast.dart';
|
||||
|
||||
part 'itunes_podcast.g.dart';
|
||||
|
||||
@JsonSerializable()
|
||||
class ItunesSearchResult<P> {
|
||||
@_ConvertP()
|
||||
final List<P> results;
|
||||
final int resultCount;
|
||||
ItunesSearchResult({this.resultCount, this.results});
|
||||
|
||||
factory ItunesSearchResult.fromJson(Map<String, dynamic> json) =>
|
||||
_$ItunesSearchResultFromJson<P>(json);
|
||||
Map<String, dynamic> toJson() => _$ItunesSearchResultToJson(this);
|
||||
}
|
||||
|
||||
class _ConvertP<P> implements JsonConverter<P, Object> {
|
||||
const _ConvertP();
|
||||
@override
|
||||
P fromJson(Object json) {
|
||||
return ItunesPodcast.fromJson(json) as P;
|
||||
}
|
||||
|
||||
@override
|
||||
Object toJson(P object) {
|
||||
return object;
|
||||
}
|
||||
}
|
||||
|
||||
@JsonSerializable()
|
||||
class ItunesPodcast {
|
||||
final String artistName;
|
||||
final String collectionName;
|
||||
final String feedUrl;
|
||||
final String artworkUrl600;
|
||||
final String releaseDate;
|
||||
final int collectionId;
|
||||
|
||||
ItunesPodcast(
|
||||
{this.artistName,
|
||||
this.collectionName,
|
||||
this.feedUrl,
|
||||
this.artworkUrl600,
|
||||
this.releaseDate,
|
||||
this.collectionId});
|
||||
|
||||
factory ItunesPodcast.fromJson(Map<String, dynamic> json) =>
|
||||
_$ItunesPodcastFromJson(json);
|
||||
Map<String, dynamic> toJson() => _$ItunesPodcastToJson(this);
|
||||
|
||||
int get latestPubDate => DateFormat('YYYY-MM-DDTHH:MM:SSZ', 'en_US')
|
||||
.parse(releaseDate)
|
||||
.millisecondsSinceEpoch;
|
||||
OnlinePodcast get toOnlinePodcast => OnlinePodcast(
|
||||
earliestPubDate: 0,
|
||||
title: collectionName,
|
||||
count: 0,
|
||||
description: '',
|
||||
image: artworkUrl600,
|
||||
latestPubDate: latestPubDate,
|
||||
rss: feedUrl,
|
||||
publisher: artistName,
|
||||
id: collectionId.toString());
|
||||
}
|
41
lib/type/search_api/itunes_podcast.g.dart
Normal file
41
lib/type/search_api/itunes_podcast.g.dart
Normal file
@ -0,0 +1,41 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'itunes_podcast.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
ItunesSearchResult<P> _$ItunesSearchResultFromJson<P>(
|
||||
Map<String, dynamic> json) {
|
||||
return ItunesSearchResult<P>(
|
||||
resultCount: json['resultCount'] as int,
|
||||
results: (json['results'] as List)?.map(_ConvertP<P>().fromJson)?.toList(),
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$ItunesSearchResultToJson<P>(
|
||||
ItunesSearchResult<P> instance) =>
|
||||
<String, dynamic>{
|
||||
'results': instance.results?.map(_ConvertP<P>().toJson)?.toList(),
|
||||
'resultCount': instance.resultCount,
|
||||
};
|
||||
|
||||
ItunesPodcast _$ItunesPodcastFromJson(Map<String, dynamic> json) {
|
||||
return ItunesPodcast(
|
||||
artistName: json['artistName'] as String,
|
||||
collectionName: json['collectionName'] as String,
|
||||
feedUrl: json['feedUrl'] as String,
|
||||
artworkUrl600: json['artworkUrl600'] as String,
|
||||
releaseDate: json['releaseDate'] as String,
|
||||
);
|
||||
}
|
||||
|
||||
Map<String, dynamic> _$ItunesPodcastToJson(ItunesPodcast instance) =>
|
||||
<String, dynamic>{
|
||||
'artistName': instance.artistName,
|
||||
'collectionName': instance.collectionName,
|
||||
'feedUrl': instance.feedUrl,
|
||||
'artworkUrl600': instance.artworkUrl600,
|
||||
'releaseDate': instance.releaseDate,
|
||||
};
|
@ -924,9 +924,10 @@ class IconPainter extends StatelessWidget {
|
||||
|
||||
/// A dot just a dot.
|
||||
class DotIndicator extends StatelessWidget {
|
||||
DotIndicator({this.radius = 8, Key key})
|
||||
DotIndicator({this.radius = 8, this.color, Key key})
|
||||
: assert(radius > 0),
|
||||
super(key: key);
|
||||
final Color color;
|
||||
final double radius;
|
||||
|
||||
@override
|
||||
@ -934,8 +935,8 @@ class DotIndicator extends StatelessWidget {
|
||||
return Container(
|
||||
width: radius,
|
||||
height: radius,
|
||||
decoration:
|
||||
BoxDecoration(shape: BoxShape.circle, color: context.accentColor));
|
||||
decoration: BoxDecoration(
|
||||
shape: BoxShape.circle, color: color ?? context.accentColor));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -29,6 +29,8 @@ extension IntExtension on int {
|
||||
var date = DateTime.fromMillisecondsSinceEpoch(this, isUtc: true);
|
||||
var difference = DateTime.now().toUtc().difference(date);
|
||||
if (difference.inMinutes < 30) {
|
||||
return s.minsAgo(difference.inMinutes);
|
||||
} else if (difference.inMinutes < 60) {
|
||||
return s.hoursAgo(0);
|
||||
} else if (difference.inHours < 24) {
|
||||
return s.hoursAgo(difference.inHours);
|
||||
|
@ -33,6 +33,7 @@ Future generalDialog(BuildContext context,
|
||||
|
||||
Future generalSheet(BuildContext context, {Widget child, String title}) async =>
|
||||
await showModalBottomSheet(
|
||||
useRootNavigator: true,
|
||||
isScrollControlled: true,
|
||||
shape: RoundedRectangleBorder(
|
||||
borderRadius: BorderRadius.only(
|
||||
@ -41,21 +42,21 @@ Future generalSheet(BuildContext context, {Widget child, String title}) async =>
|
||||
elevation: 2,
|
||||
context: context,
|
||||
builder: (context) {
|
||||
final statusHeight = MediaQuery.of(context).padding.top;
|
||||
return SafeArea(
|
||||
child: SingleChildScrollView(
|
||||
physics: NeverScrollableScrollPhysics(),
|
||||
child: ConstrainedBox(
|
||||
constraints:
|
||||
BoxConstraints(maxHeight: context.height - statusHeight - 80),
|
||||
child: Column(
|
||||
mainAxisSize: MainAxisSize.min,
|
||||
children: [
|
||||
Padding(
|
||||
padding: EdgeInsets.only(top: 10.0, bottom: 2.0),
|
||||
child: Container(
|
||||
height: 4,
|
||||
width: 25,
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(2.0),
|
||||
color: context.primaryColorDark),
|
||||
),
|
||||
Container(
|
||||
height: 4,
|
||||
width: 25,
|
||||
margin: EdgeInsets.only(top: 10.0, bottom: 2.0),
|
||||
decoration: BoxDecoration(
|
||||
borderRadius: BorderRadius.circular(2.0),
|
||||
color: context.primaryColorDark),
|
||||
),
|
||||
Padding(
|
||||
padding: EdgeInsets.only(
|
||||
@ -69,7 +70,7 @@ Future generalSheet(BuildContext context, {Widget child, String title}) async =>
|
||||
),
|
||||
),
|
||||
Divider(height: 1),
|
||||
child,
|
||||
Flexible(child: SingleChildScrollView(child: child)),
|
||||
],
|
||||
),
|
||||
),
|
||||
|
15
pubspec.yaml
15
pubspec.yaml
@ -13,16 +13,19 @@ dependencies:
|
||||
sdk: flutter
|
||||
auto_animated: ^2.1.0
|
||||
audio_session: ^0.0.7
|
||||
cached_network_image: ^2.3.1
|
||||
cached_network_image: ^2.3.2+1
|
||||
color_thief_flutter: ^1.0.2
|
||||
cookie_jar: ^1.0.0
|
||||
cupertino_icons: ^1.0.0
|
||||
connectivity: ^0.4.9
|
||||
device_info: ^0.4.2+7
|
||||
dio: ^3.0.10
|
||||
dio_cookie_manager: ^1.0.0
|
||||
extended_nested_scroll_view: ^1.0.1
|
||||
effective_dart: ^1.2.4
|
||||
equatable: ^1.2.3
|
||||
equatable: ^1.2.5
|
||||
feature_discovery: ^0.10.0
|
||||
file_picker: ^1.12.0
|
||||
file_picker: ^2.0.0
|
||||
flutter_html: ^0.11.1
|
||||
flutter_downloader: ^1.5.0
|
||||
fluttertoast: ^4.0.0
|
||||
@ -37,15 +40,15 @@ dependencies:
|
||||
intl: ^0.16.1
|
||||
json_serializable: ^3.4.1
|
||||
json_annotation: ^3.0.1
|
||||
path_provider: ^1.6.14
|
||||
path_provider: ^1.6.16
|
||||
permission_handler: ^5.0.1
|
||||
provider: ^4.3.2
|
||||
rxdart: ^0.24.1
|
||||
sqflite: ^1.3.1
|
||||
shared_preferences: ^0.5.10
|
||||
tuple: ^1.0.3
|
||||
url_launcher: ^5.5.3
|
||||
uuid: ^2.2.0
|
||||
url_launcher: ^5.6.0
|
||||
uuid: ^2.2.2
|
||||
xml: ^4.2.0
|
||||
workmanager: ^0.2.3
|
||||
wc_flutter_share: ^0.2.2
|
||||
|
Loading…
x
Reference in New Issue
Block a user