From 5160cd65c50c0d0ee91f377141d671a6a025310c Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 21 Nov 2022 09:55:42 +0100 Subject: [PATCH 01/61] Fix issue #503 - Wrong URL displayed when long pressing a link that has been transformed --- .../android/client/entities/api/Status.java | 4 -- .../app/fedilab/android/helper/Helper.java | 37 ++++++++++++++++--- .../android/helper/SpannableHelper.java | 22 +---------- 3 files changed, 33 insertions(+), 30 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Status.java b/app/src/main/java/app/fedilab/android/client/entities/api/Status.java index f996b9a4..e3c75305 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/Status.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Status.java @@ -127,11 +127,7 @@ public class Status implements Serializable, Cloneable { public synchronized Spannable getSpanContent(Context context, WeakReference viewWeakReference) { return SpannableHelper.convert(context, content, this, null, null, true, viewWeakReference); } - //Some extra spannable element - They will be filled automatically when fetching the status - public Spannable getSpanContentNitter() { - return SpannableHelper.convertNitter(content); - } public synchronized Spannable getSpanSpoiler(Context context, WeakReference viewWeakReference) { return SpannableHelper.convert(context, spoiler_text, this, null, null, true, viewWeakReference); diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java index cd295eb4..7a34d668 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -745,39 +745,56 @@ public class Helper { */ public static String transformURL(Context context, String url) { SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); - Matcher matcher = Helper.nitterPattern.matcher(url); + Matcher matcher; boolean nitter = Helper.getSharedValue(context, context.getString(R.string.SET_NITTER)); if (nitter) { + matcher = Helper.nitterPattern.matcher(url); if (matcher.find()) { final String nitter_directory = matcher.group(2); String nitterHost = sharedpreferences.getString(context.getString(R.string.SET_NITTER_HOST), context.getString(R.string.DEFAULT_NITTER_HOST)).toLowerCase(); + if (nitterHost.trim().isEmpty()) { + nitterHost = context.getString(R.string.DEFAULT_NITTER_HOST); + } return "https://" + nitterHost + nitter_directory; } } - matcher = Helper.bibliogramPattern.matcher(url); + boolean bibliogram = Helper.getSharedValue(context, context.getString(R.string.SET_BIBLIOGRAM)); + if (bibliogram) { + matcher = Helper.bibliogramPattern.matcher(url); if (matcher.find()) { final String bibliogram_directory = matcher.group(2); String bibliogramHost = sharedpreferences.getString(context.getString(R.string.SET_BIBLIOGRAM_HOST), context.getString(R.string.DEFAULT_BIBLIOGRAM_HOST)).toLowerCase(); + if (bibliogramHost.trim().isEmpty()) { + bibliogramHost = context.getString(R.string.DEFAULT_BIBLIOGRAM_HOST); + } return "https://" + bibliogramHost + bibliogram_directory; } } - matcher = Helper.libredditPattern.matcher(url); + boolean libreddit = Helper.getSharedValue(context, context.getString(R.string.SET_LIBREDDIT)); if (libreddit) { + matcher = Helper.libredditPattern.matcher(url); if (matcher.find()) { final String libreddit_directory = matcher.group(3); String libreddit_host = sharedpreferences.getString(context.getString(R.string.SET_LIBREDDIT_HOST), context.getString(R.string.DEFAULT_LIBREDDIT_HOST)).toLowerCase(); + if (libreddit_host.trim().isEmpty()) { + libreddit_host = context.getString(R.string.DEFAULT_LIBREDDIT_HOST); + } return "https://" + libreddit_host + "/" + libreddit_directory; } } - matcher = Helper.youtubePattern.matcher(url); + boolean invidious = Helper.getSharedValue(context, context.getString(R.string.SET_INVIDIOUS)); if (invidious) { + matcher = Helper.youtubePattern.matcher(url); if (matcher.find()) { final String youtubeId = matcher.group(3); String invidiousHost = sharedpreferences.getString(context.getString(R.string.SET_INVIDIOUS_HOST), context.getString(R.string.DEFAULT_INVIDIOUS_HOST)).toLowerCase(); + if (invidiousHost.trim().isEmpty()) { + invidiousHost = context.getString(R.string.DEFAULT_INVIDIOUS_HOST); + } if (matcher.group(2) != null && Objects.equals(matcher.group(2), "youtu.be")) { return "https://" + invidiousHost + "/watch?v=" + youtubeId + "&local=true"; } else { @@ -785,9 +802,10 @@ public class Helper { } } } - matcher = Helper.mediumPattern.matcher(url); + boolean medium = Helper.getSharedValue(context, context.getString(R.string.REPLACE_MEDIUM)); if (medium) { + matcher = Helper.mediumPattern.matcher(url); if (matcher.find()) { String path = matcher.group(2); String user = matcher.group(1); @@ -795,12 +813,16 @@ public class Helper { path = user + "/" + path; } String mediumReplaceHost = sharedpreferences.getString(context.getString(R.string.REPLACE_MEDIUM_HOST), context.getString(R.string.DEFAULT_REPLACE_MEDIUM_HOST)).toLowerCase(); + if (mediumReplaceHost.trim().isEmpty()) { + mediumReplaceHost = context.getString(R.string.DEFAULT_REPLACE_MEDIUM_HOST); + } return "https://" + mediumReplaceHost + "/" + path; } } - matcher = Helper.wikipediaPattern.matcher(url); + boolean wikipedia = Helper.getSharedValue(context, context.getString(R.string.REPLACE_WIKIPEDIA)); if (wikipedia) { + matcher = Helper.wikipediaPattern.matcher(url); if (matcher.find()) { String subdomain = matcher.group(1); String path = matcher.group(2); @@ -810,6 +832,9 @@ public class Helper { lang = (path.contains("?")) ? TextUtils.htmlEncode("&") : "?"; lang = lang + "lang=" + subdomain; } + if (wikipediaReplaceHost.trim().isEmpty()) { + wikipediaReplaceHost = context.getString(R.string.DEFAULT_REPLACE_WIKIPEDIA_HOST); + } return "https://" + wikipediaReplaceHost + "/" + path + lang; } } diff --git a/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java b/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java index df8c628a..cdf65612 100644 --- a/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java @@ -249,8 +249,8 @@ public class SpannableHelper { dialogBuilder.setView(popupLinksBinding.getRoot()); AlertDialog alertDialog = dialogBuilder.create(); alertDialog.show(); - String finalURl = url; - String uniqueUrl = url.endsWith("…") ? url : url + "…"; + String finalURl = newURL; + String uniqueUrl = newURL.endsWith("…") ? newURL : newURL + "…"; if (urlDetails.containsValue(uniqueUrl)) { finalURl = Helper.getKeyByValue(urlDetails, uniqueUrl); } @@ -872,24 +872,6 @@ public class SpannableHelper { } } - /** - * Convert HTML content to text. Also, it handles click on link - * This needs to be run asynchronously - * - * @param text String - text to convert, it can be content, spoiler, poll items, etc. - * @return Spannable string - */ - public static Spannable convertNitter(String text) { - SpannableString initialContent; - if (text == null) { - return null; - } - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - initialContent = new SpannableString(Html.fromHtml(text, Html.FROM_HTML_MODE_LEGACY)); - else - initialContent = new SpannableString(Html.fromHtml(text)); - return initialContent; - } /** From e8018a6560b8b76afede5f7b4527f4b0a2d52078 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 21 Nov 2022 10:19:51 +0100 Subject: [PATCH 02/61] Fix issue #501 - Disabling "attach media when sharing" not honored --- app/src/main/java/app/fedilab/android/BaseMainActivity.java | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/fedilab/android/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/BaseMainActivity.java index 4682d631..386a8ed6 100644 --- a/app/src/main/java/app/fedilab/android/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/BaseMainActivity.java @@ -949,7 +949,9 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt runOnUiThread(() -> { Bundle b = new Bundle(); b.putString(Helper.ARG_SHARE_URL, url[0]); - b.putString(Helper.ARG_SHARE_URL_MEDIA, finalImage); + if (fetchSharedMedia) { + b.putString(Helper.ARG_SHARE_URL_MEDIA, finalImage); + } b.putString(Helper.ARG_SHARE_TITLE, finalTitle); b.putString(Helper.ARG_SHARE_DESCRIPTION, finalDescription); b.putString(Helper.ARG_SHARE_SUBJECT, sharedSubject); From 495bc70ffbfed5e83abe9b88fde973b7276b5333 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 21 Nov 2022 11:11:14 +0100 Subject: [PATCH 03/61] Fix a crash when writing an unsupported domain --- .../app/fedilab/android/viewmodel/mastodon/AppsVM.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AppsVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AppsVM.java index 26445ae7..7a77d610 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AppsVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AppsVM.java @@ -57,7 +57,7 @@ public class AppsVM extends AndroidViewModel { super(application); } - private MastodonAppsService init(String instance) { + private MastodonAppsService init(String instance) throws IllegalArgumentException { Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").create(); Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://" + instance + "/api/v1/") @@ -81,7 +81,13 @@ public class AppsVM extends AndroidViewModel { String scopes, String website) { appMutableLiveData = new MutableLiveData<>(); - MastodonAppsService mastodonAppsService = init(instance); + MastodonAppsService mastodonAppsService; + try { + mastodonAppsService = init(instance); + } catch (IllegalArgumentException e) { + appMutableLiveData.setValue(null); + return appMutableLiveData; + } new Thread(() -> { App app = null; Call appCall = mastodonAppsService.createApp(client_name, redirect_uris, scopes, website); From bbf3ce3a7959b4ad023c476201e7c13981932c61 Mon Sep 17 00:00:00 2001 From: ButterflyOfFire Date: Mon, 21 Nov 2022 12:29:24 +0100 Subject: [PATCH 04/61] Translated using Weblate (French) Currently translated at 99.5% (884 of 888 strings) Co-authored-by: ButterflyOfFire Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/ Translation: Fedilab/Strings --- app/src/main/res/values-fr/strings.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 61d843bf..364d5891 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -420,9 +420,9 @@ Image enregistrée avec succès ! Échec de l\'enregistrement de l\'image Ajouter un élément de sondage - Mettre la conversation en sourdine + Masquer la conversation Enlever la sourdine de la discussion - La conversation n\'est plus mise en sourdine ! + La conversation n’est plus masquée ! La conversation est mise en sourdine Général Régional From ae3cca3a2dd4ffdc230cab834f4d21e80cb3d798 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?C=C3=A1rlisson=20Galdino?= Date: Mon, 21 Nov 2022 12:29:25 +0100 Subject: [PATCH 05/61] Translated using Weblate (Portuguese) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 75.4% (670 of 888 strings) Co-authored-by: Cárlisson Galdino Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/pt/ Translation: Fedilab/Strings --- app/src/main/res/values-pt/strings.xml | 125 +++++++++++++++++++------ 1 file changed, 95 insertions(+), 30 deletions(-) diff --git a/app/src/main/res/values-pt/strings.xml b/app/src/main/res/values-pt/strings.xml index b710463b..661f0daa 100644 --- a/app/src/main/res/values-pt/strings.xml +++ b/app/src/main/res/values-pt/strings.xml @@ -17,19 +17,19 @@ Senha E-mail Contas - Toots + Mensagens Tags Salvar Instância Instância: mastodon.social Usando a conta %1$s agora Adicionar conta - O conteúdo do toot foi copiado para a área de transferência - O link do toot foi copiado para a área de transferência + O conteúdo da mensagem foi copiado para a área de transferência + O link da mensagem foi copiado para a área de transferência Câmera Excluir tudo Agendar - Tamanho dos textos e ícones + Tamanho dos textos Próximo Anterior Abrir com @@ -57,17 +57,17 @@ Seguidores pendentes Configurações Mandar um e-mail - Toots agendados + Mensagens agendadas As informações abaixo podem refletir incompletamente o perfil do usuário. Inserir emoji O aplicativo não achou emojis personalizados no momento. Tem a certeza que quer sair @%1$s@%2$s? - Sem toots - Favoritar toot? - Desfavoritar toot? - Dar boost? - Desfazer boost? + Sem mensagens para mostrar + Favoritar essa mensagem\? + Desfavoritar essa mensagem\? + Dar boost\? + Desfazer boost\? Silenciar Bloquear Denunciar @@ -105,25 +105,29 @@ %d d %d segundo + %d segundos %d segundos %d minuto + %d minutos %d minutos %d hora + %d horas %d horas %d dia + %d dias %d dias Ocorreu um erro ao selecionar a mídia! Remover mídia? - Toot vazio! - Toot enviado! + Sua mensagem está vazia! + Mensagem enviada! Conteúdo sensível? Sem rascunhos! Escolha uma conta @@ -143,14 +147,15 @@ Sem conta Sem seguidores pendentes - Toots \n %1$s + Mensagens +\n %1$s Seguindo \n %1$s Seguidores \n %1$s Recusar - Sem toots agendados! - Excluir toot agendado? - Toot agendado! + Sem mensagens agendadas! + Excluir mensagem agendada\? + Mensagem agendada! A data de agendamento deve ser após o horário atual! O tempo de silêncio deve ser maior do que um minuto. @@ -176,10 +181,10 @@ Silêncio desativado! Você seguiu a conta! Você deixou de seguir a conta! - Toot compartilhado! + Mensagem compartilhada! Boost desfeito! - Toot favoritado! - Toot desfavoritado! + Mensagem adicionada aos favoritos! + A mensagem foi removida dos seus favoritos! Oops! Ocorreu um erro! Ocorreu um erro! A instância não retornou um código de autorização! Parece que o domínio da instância não é válido! @@ -188,7 +193,7 @@ A ação não pode ser feita ou não é suportada Ocorreu um erro na tradução! - Número de toots por vez + Número de mensagens por vez Desativar GIF Notificar quando alguém te seguir Notificar quando alguém der boost nos seus toots @@ -200,7 +205,7 @@ Mostrar diálogo antes de favoritar Notificar? Silenciar notificações - Segundos para expirar a prévia de mídia sensível, 0 para desativar. + Segundos para expirar a prévia de mídia sensível, 0 para desativar Tempo limite da descrição de ficheiros multimédia (segundos, 0 significa desligado) Compartilhamento externo personalizado Seu link de compartilhamento externo… @@ -267,7 +272,7 @@ Porta Login Senha - Adicionar detalhes do toot ao compartilhar + Adicionar detalhes da mensagem ao compartilhar Apoie o aplicativo no Liberapay Há um erro na expressão regular! Nenhuma timeline foi encontrada nesta instância! @@ -286,9 +291,9 @@ Timelines públicas Notificações Conversas - Serão correspondidas independente de maiúsculas ou minúsculas no texto ou no aviso de conteúdo de um toot + Serão correspondidas independente de maiúsculas ou minúsculas no texto ou no aviso de conteúdo de uma mensagem Apagar em vez de ocultar - Os toots filtrados desaparecerão irreversivelmente, mesmo se o filtro for removido mais tarde + As mensagens filtradas desaparecerão irreversivelmente, mesmo se o filtro for removido mais tarde Quando a palavra-chave ou frase só tem alfanuméricos, ela será aplicada somente se combinar com a palavra inteira Palavra inteira Contextos do filtro @@ -303,7 +308,7 @@ Novo favorito Nova menção Sondagem terminada - Backup de Toots + Backup de Mensagens Novas publicações Baixar mídia Selecionar toque @@ -315,11 +320,11 @@ Instância Peertube Usar Emoji One Informação - Mostrar pré-visualizações em todos os toots + Mostrar pré-visualizações em todas as mensagens O nome de utilizador foi copiado para a área de transferência! Mudar o idioma - Cortar toots longos - Limitar toots por \'x\' linhas. Zero significa desativar. + Cortar mensagens longas + Limitar mensagens por \'x\' linhas. Zero significa desativar. Mostrar mais Mostrar menos A tag já existe! @@ -365,8 +370,8 @@ Categoria Descrição Compartilhar - Toots (Servidor) - Toots (Disp) + Mensagens (Servidor) + Mensagens (Dispositivo) Timelines Interface Contatos @@ -570,4 +575,64 @@ No distributors found! Necessita de um distribuidor para receber notificações instantâneas. \nVai encontrar mais detalhes em %1$s.\n\nPode desactivar as mensagens instantâneas nas configurações para ignorar esta mensagem. Select a distributor + Sons de notificação + Usar um frontend alternativo para Twitter + Twitter + Domínio do frontend para Twitter + Instagram + Usar um frontend alternativo para Instagram + Domínio do frontend para Instagram + Personalizado + A instância não parece ser válida! + Recebeu boost de + Remover status + Publicando mensagem… + Escolha o tipo de notificação + Tipo de notificação + Escolha um tema + Domínio do frontend para YouTube + Usar um frontend alternativo para o Reddit + Continuar + Outro + Ex.: Conteúdo Sensível + Está inoperante! + versão: %s +\n%s users - %s status + Em atividade há: %,.2f %% + Tamanho dos ícones + A mensagem foi adicionada aos seus favoritos! + A mensagem foi removida dos seus favoritos! + Número de notificações por vez + YouTube + Usar um frontend alternativo para o YouTube + Parar de gravar + Relatório %1$s + Esconder conteúdo < + Visibilidade padrão das mensagens: + Número de contas por vez + Esse campo não pode ser vazio! + Domínio do frontend para Reddit + Está ativo! + Base do tema + Durante esse momento + Desabilitar notificações + Permite criar seu tema personalizado + Mais Ações + Escolha se a base do tema será dark ou light + Escolha um tema que foi criado por colaboradores + Temas de colaboradores + Personalizar timelines + Tipos de notificação para mostrar + Reddit + As configurações foram importadas com sucesso + Verificado em: %s + Mostrar conteúdo > + Música + As configurações foram exportadas + Configurações foram exportadas com sucesso + Favoritada por + Apenas seguidores + Diga-nos o que está havendo com este post + Adicionar status + Enviando mensagem %d/%d \ No newline at end of file From e8ab16747de01cef9b27543f9d1a1d510d78a664 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 21 Nov 2022 14:09:10 +0100 Subject: [PATCH 06/61] Fix issue #500 - Crash due to proxy - Disable DNS check --- app/src/main/java/app/fedilab/android/helper/Helper.java | 2 +- .../fedilab/android/ui/fragment/login/FragmentLoginMain.java | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java index 7a34d668..5277dbb7 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -1190,7 +1190,7 @@ public class Helper { return null; } Proxy proxy = new Proxy(type == 0 ? Proxy.Type.HTTP : Proxy.Type.SOCKS, - new InetSocketAddress(hostVal, portVal)); + InetSocketAddress.createUnresolved(hostVal, portVal)); Authenticator.setDefault(new Authenticator() { @Override protected PasswordAuthentication getPasswordAuthentication() { diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/login/FragmentLoginMain.java b/app/src/main/java/app/fedilab/android/ui/fragment/login/FragmentLoginMain.java index c193b1aa..6ec7e0da 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/login/FragmentLoginMain.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/login/FragmentLoginMain.java @@ -75,7 +75,7 @@ public class FragmentLoginMain extends Fragment { binding = FragmentLoginMainBinding.inflate(inflater, container, false); View root = binding.getRoot(); - + InstanceSocialVM instanceSocialVM = new ViewModelProvider(FragmentLoginMain.this).get(InstanceSocialVM.class); binding.menuIcon.setOnClickListener(this::showMenu); binding.loginInstance.setOnItemClickListener((parent, view, position, id) -> oldSearch = parent.getItemAtPosition(position).toString().trim()); binding.loginInstance.addTextChangedListener(new TextWatcher() { @@ -101,7 +101,7 @@ public class FragmentLoginMain extends Fragment { } if (oldSearch == null || !oldSearch.equals(s.toString().trim())) { searchInstanceRunning = true; - InstanceSocialVM instanceSocialVM = new ViewModelProvider(FragmentLoginMain.this).get(InstanceSocialVM.class); + instanceSocialVM.getInstances(query).observe(requireActivity(), instanceSocialList -> { binding.loginInstance.setAdapter(null); if (instanceSocialList.instances.isEmpty()) { From 03f0ac4be39465eb05bd5a3c3078d56f99efb858 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 21 Nov 2022 14:23:54 +0100 Subject: [PATCH 07/61] Fix issue #498 - Keep searched word in search field --- .../fedilab/android/activities/SearchResultTabActivity.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/app/fedilab/android/activities/SearchResultTabActivity.java b/app/src/main/java/app/fedilab/android/activities/SearchResultTabActivity.java index bb8c6771..90742677 100644 --- a/app/src/main/java/app/fedilab/android/activities/SearchResultTabActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/SearchResultTabActivity.java @@ -122,6 +122,9 @@ public class SearchResultTabActivity extends BaseActivity { inflater.inflate(R.menu.menu_search, menu); SearchManager searchManager = (SearchManager) getSystemService(Context.SEARCH_SERVICE); SearchView searchView = (SearchView) menu.findItem(R.id.action_search).getActionView(); + if (search != null) { + searchView.setQuery(search, false); + } searchView.setSearchableInfo(searchManager.getSearchableInfo(getComponentName())); searchView.setIconifiedByDefault(false); searchView.setOnQueryTextListener(new SearchView.OnQueryTextListener() { From 59a21008f1cbc91e328c012fa1ca221aca8608a2 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 21 Nov 2022 14:46:53 +0100 Subject: [PATCH 08/61] Fix issue #497 - Filter view is not syncing after edition --- .../java/app/fedilab/android/activities/FilterActivity.java | 5 +++-- .../java/app/fedilab/android/ui/drawer/FilterAdapter.java | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/activities/FilterActivity.java b/app/src/main/java/app/fedilab/android/activities/FilterActivity.java index 91d3688c..474d89a7 100644 --- a/app/src/main/java/app/fedilab/android/activities/FilterActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/FilterActivity.java @@ -106,7 +106,6 @@ public class FilterActivity extends BaseActivity implements FilterAdapter.Delete }); - if (filter != null) { filterParams.filter_action = filter.filter_action; @@ -166,7 +165,9 @@ public class FilterActivity extends BaseActivity implements FilterAdapter.Delete popupAddFilterBinding.lvKeywords.setLayoutManager(new LinearLayoutManager(context)); popupAddFilterBinding.addKeyword.setOnClickListener(v -> { - filterParams.keywords.add(new Filter.KeywordsParams()); + Filter.KeywordsParams keywordsParams = new Filter.KeywordsParams(); + keywordsParams.whole_word = true; + filterParams.keywords.add(keywordsParams); keywordAdapter.notifyItemInserted(filterParams.keywords.size() - 1); }); diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/FilterAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/FilterAdapter.java index e7f8bf28..88c59199 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/FilterAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/FilterAdapter.java @@ -81,6 +81,9 @@ public class FilterAdapter extends RecyclerView.Adapter Date: Mon, 21 Nov 2022 17:43:29 +0100 Subject: [PATCH 09/61] Add classes and logic --- app/src/main/AndroidManifest.xml | 6 +- .../activities/SuggestionActivity.java | 64 ++++++++++++ .../endpoints/MastodonAccountsService.java | 3 +- .../client/entities/api/Suggestion.java | 27 +++++ .../client/entities/api/Suggestions.java | 22 +++++ .../android/client/entities/app/Timeline.java | 2 + .../timeline/FragmentMastodonAccount.java | 8 ++ .../viewmodel/mastodon/AccountsVM.java | 36 ++++--- .../viewmodel/mastodon/SuggestionVM.java | 98 +++++++++++++++++++ .../main/res/layout/activity_suggestions.xml | 31 ++++++ app/src/main/res/values/strings.xml | 1 + 11 files changed, 282 insertions(+), 16 deletions(-) create mode 100644 app/src/main/java/app/fedilab/android/activities/SuggestionActivity.java create mode 100644 app/src/main/java/app/fedilab/android/client/entities/api/Suggestion.java create mode 100644 app/src/main/java/app/fedilab/android/client/entities/api/Suggestions.java create mode 100644 app/src/main/java/app/fedilab/android/viewmodel/mastodon/SuggestionVM.java create mode 100644 app/src/main/res/layout/activity_suggestions.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8a5a34ba..17bec2ac 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -243,7 +243,11 @@ android:configChanges="keyboardHidden|orientation|screenSize" android:label="@string/action_about" android:theme="@style/AppThemeBar" /> - + . */ + +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.view.MenuItem; + +import androidx.core.content.ContextCompat; + +import org.jetbrains.annotations.NotNull; + +import app.fedilab.android.R; +import app.fedilab.android.client.entities.app.Timeline; +import app.fedilab.android.databinding.ActivitySuggestionsBinding; +import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.ThemeHelper; +import app.fedilab.android.ui.fragment.timeline.FragmentMastodonAccount; + + +public class SuggestionActivity extends BaseActivity { + + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ThemeHelper.applyThemeBar(this); + app.fedilab.android.databinding.ActivitySuggestionsBinding binding = ActivitySuggestionsBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + + + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary))); + } + + Bundle bundle = new Bundle(); + bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.ACCOUNT_SUGGESTION); + Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_tags, new FragmentMastodonAccount(), bundle, null, null); + } + + + @Override + public boolean onOptionsItemSelected(@NotNull MenuItem item) { + if (item.getItemId() == android.R.id.home) { + finish(); + return true; + } + return super.onOptionsItemSelected(item); + } + +} diff --git a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java index b215079b..499988c2 100644 --- a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java +++ b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java @@ -25,6 +25,7 @@ import app.fedilab.android.client.entities.api.Preferences; import app.fedilab.android.client.entities.api.RelationShip; import app.fedilab.android.client.entities.api.Report; import app.fedilab.android.client.entities.api.Status; +import app.fedilab.android.client.entities.api.Suggestion; import app.fedilab.android.client.entities.api.Tag; import app.fedilab.android.client.entities.api.Token; import okhttp3.MultipartBody; @@ -391,7 +392,7 @@ public interface MastodonAccountsService { //Get user suggestions @GET("suggestions") - Call> getSuggestions( + Call> getSuggestions( @Header("Authorization") String token, @Query("limit") String limit ); diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Suggestion.java b/app/src/main/java/app/fedilab/android/client/entities/api/Suggestion.java new file mode 100644 index 00000000..d60128dd --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Suggestion.java @@ -0,0 +1,27 @@ +package app.fedilab.android.client.entities.api; + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; + +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + +public class Suggestion implements Serializable { + @SerializedName("account") + public Account account; + @SerializedName("source") + public String source; +} diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Suggestions.java b/app/src/main/java/app/fedilab/android/client/entities/api/Suggestions.java new file mode 100644 index 00000000..1b29d7f6 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Suggestions.java @@ -0,0 +1,22 @@ +package app.fedilab.android.client.entities.api; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + +import java.util.List; + +public class Suggestions { + public Pagination pagination = new Pagination(); + public List suggestions; +} diff --git a/app/src/main/java/app/fedilab/android/client/entities/app/Timeline.java b/app/src/main/java/app/fedilab/android/client/entities/app/Timeline.java index d5d8ebf1..a195daaa 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/app/Timeline.java +++ b/app/src/main/java/app/fedilab/android/client/entities/app/Timeline.java @@ -378,6 +378,8 @@ public class Timeline { TREND_TAG("TREND_TAG"), @SerializedName("TREND_MESSAGE") TREND_MESSAGE("TREND_MESSAGE"), + @SerializedName("ACCOUNT_SUGGESTION") + ACCOUNT_SUGGESTION("ACCOUNT_SUGGESTION"), @SerializedName("PUBLIC_TREND_MESSAGE") TREND_MESSAGE_PUBLIC("TREND_MESSAGE_PUBLIC"), @SerializedName("STATUS_HISTORY") diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonAccount.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonAccount.java index 0d7f9694..f2e5fd4e 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonAccount.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonAccount.java @@ -143,6 +143,14 @@ public class FragmentMastodonAccount extends Fragment { accountsVM.getBlocks(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, String.valueOf(MastodonHelper.accountsPerCall(requireActivity())), max_id, null) .observe(getViewLifecycleOwner(), this::initializeAccountCommonView); } + } else if (timelineType == Timeline.TimeLineEnum.ACCOUNT_SUGGESTION) { + if (firstLoad) { + accountsVM.getSuggestions(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null) + .observe(getViewLifecycleOwner(), this::initializeAccountCommonView); + } else { + accountsVM.getSuggestions(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, max_id) + .observe(getViewLifecycleOwner(), this::initializeAccountCommonView); + } } } diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java index 037da464..627f6f5d 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java @@ -26,9 +26,6 @@ import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - import java.util.LinkedHashMap; import java.util.List; import java.util.concurrent.TimeUnit; @@ -50,6 +47,8 @@ import app.fedilab.android.client.entities.api.Report; import app.fedilab.android.client.entities.api.Source; import app.fedilab.android.client.entities.api.Status; import app.fedilab.android.client.entities.api.Statuses; +import app.fedilab.android.client.entities.api.Suggestion; +import app.fedilab.android.client.entities.api.Suggestions; import app.fedilab.android.client.entities.api.Tag; import app.fedilab.android.client.entities.api.Token; import app.fedilab.android.client.entities.app.StatusCache; @@ -75,6 +74,7 @@ public class AccountsVM extends AndroidViewModel { private MutableLiveData accountMutableLiveData; private MutableLiveData> accountListMutableLiveData; + private MutableLiveData suggestionsMutableLiveData; private MutableLiveData statusesMutableLiveData; private MutableLiveData accountsMutableLiveData; private MutableLiveData> statusListMutableLiveData; @@ -97,7 +97,6 @@ public class AccountsVM extends AndroidViewModel { } private MastodonAccountsService init(String instance) { - Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").create(); Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://" + instance + "/api/v1/") .addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder())) @@ -106,6 +105,15 @@ public class AccountsVM extends AndroidViewModel { return retrofit.create(MastodonAccountsService.class); } + private MastodonAccountsService initv2(String instance) { + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("https://" + instance + "/api/v2/") + .addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder())) + .client(okHttpClient) + .build(); + return retrofit.create(MastodonAccountsService.class); + } + /** * Get connected account * @@ -1393,28 +1401,28 @@ public class AccountsVM extends AndroidViewModel { * @param limit Maximum number of results to return. Defaults to 40. * @return {@link LiveData} containing a {@link List} of {@link Account}s */ - public LiveData> getSuggestions(@NonNull String instance, String token, String limit) { - accountListMutableLiveData = new MutableLiveData<>(); - MastodonAccountsService mastodonAccountsService = init(instance); + public LiveData getSuggestions(@NonNull String instance, String token, String limit) { + suggestionsMutableLiveData = new MutableLiveData<>(); + MastodonAccountsService mastodonAccountsService = initv2(instance); new Thread(() -> { - List accountList = null; - Call> suggestionsCall = mastodonAccountsService.getSuggestions(token, limit); + Call> suggestionsCall = mastodonAccountsService.getSuggestions(token, limit); + Suggestions suggestions = new Suggestions(); if (suggestionsCall != null) { try { - Response> suggestionsResponse = suggestionsCall.execute(); + Response> suggestionsResponse = suggestionsCall.execute(); if (suggestionsResponse.isSuccessful()) { - accountList = suggestionsResponse.body(); + suggestions.pagination = MastodonHelper.getOffSetPagination(suggestionsResponse.headers()); + suggestions.suggestions = suggestionsResponse.body(); } } catch (Exception e) { e.printStackTrace(); } } Handler mainHandler = new Handler(Looper.getMainLooper()); - List finalAccountList = accountList; - Runnable myRunnable = () -> accountListMutableLiveData.setValue(finalAccountList); + Runnable myRunnable = () -> suggestionsMutableLiveData.setValue(suggestions); mainHandler.post(myRunnable); }).start(); - return accountListMutableLiveData; + return suggestionsMutableLiveData; } /** diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/SuggestionVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/SuggestionVM.java new file mode 100644 index 00000000..04a0ac13 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/SuggestionVM.java @@ -0,0 +1,98 @@ +package app.fedilab.android.viewmodel.mastodon; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + + +import android.app.Application; +import android.os.Handler; +import android.os.Looper; + +import androidx.annotation.NonNull; +import androidx.lifecycle.AndroidViewModel; +import androidx.lifecycle.LiveData; +import androidx.lifecycle.MutableLiveData; + +import java.util.List; +import java.util.concurrent.TimeUnit; + +import app.fedilab.android.client.endpoints.MastodonFiltersService; +import app.fedilab.android.client.endpoints.MastodonTimelinesService; +import app.fedilab.android.client.entities.api.Filter; +import app.fedilab.android.client.entities.api.Status; +import app.fedilab.android.client.entities.api.Statuses; +import app.fedilab.android.client.entities.app.Timeline; +import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.MastodonHelper; +import app.fedilab.android.helper.TimelineHelper; +import okhttp3.OkHttpClient; +import retrofit2.Call; +import retrofit2.Response; +import retrofit2.Retrofit; +import retrofit2.converter.gson.GsonConverterFactory; + +public class SuggestionVM extends AndroidViewModel { + + + final OkHttpClient okHttpClient = new OkHttpClient.Builder() + .readTimeout(60, TimeUnit.SECONDS) + .connectTimeout(60, TimeUnit.SECONDS) + .callTimeout(60, TimeUnit.SECONDS) + .proxy(Helper.getProxy(getApplication().getApplicationContext())) + .build(); + + + private MutableLiveData filterMutableLiveData; + private MutableLiveData> filterListMutableLiveData; + + public SuggestionVM(@NonNull Application application) { + super(application); + } + + private MastodonFiltersService initV2(String instance) { + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("https://" + instance + "/api/v2/") + // .addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder())) + .addConverterFactory(GsonConverterFactory.create()) + .client(okHttpClient) + .build(); + return retrofit.create(MastodonFiltersService.class); + } + + + public LiveData getSuggestions(String token, @NonNull String instance, String max_id, Integer limit) { + MastodonTimelinesService mastodonTimelinesService = initV2(instance); + statusesMutableLiveData = new MutableLiveData<>(); + new Thread(() -> { + Call> publicTlCall = mastodonTimelinesService.getStatusTrends(token, max_id, limit); + Statuses statuses = new Statuses(); + if (publicTlCall != null) { + try { + Response> publicTlResponse = publicTlCall.execute(); + if (publicTlResponse.isSuccessful()) { + List statusList = publicTlResponse.body(); + statuses.statuses = TimelineHelper.filterStatus(getApplication().getApplicationContext(), statusList, Timeline.TimeLineEnum.TREND_MESSAGE); + statuses.pagination = MastodonHelper.getOffSetPagination(publicTlResponse.headers()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> statusesMutableLiveData.setValue(statuses); + mainHandler.post(myRunnable); + }).start(); + return statusesMutableLiveData; + } +} diff --git a/app/src/main/res/layout/activity_suggestions.xml b/app/src/main/res/layout/activity_suggestions.xml new file mode 100644 index 00000000..74fb10e7 --- /dev/null +++ b/app/src/main/res/layout/activity_suggestions.xml @@ -0,0 +1,31 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 502ec56f..558d5b55 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1904,4 +1904,5 @@ Delete timeline Submitted a report Signed up + Suggestions \ No newline at end of file From 020d218df94ca780e3750008d02921452106a140 Mon Sep 17 00:00:00 2001 From: Thomas Date: Mon, 21 Nov 2022 19:06:03 +0100 Subject: [PATCH 10/61] add suggestions --- .../app/fedilab/android/BaseMainActivity.java | 4 + .../activities/SuggestionActivity.java | 9 +- .../android/ui/drawer/SuggestionAdapter.java | 140 +++++++++++++++ .../timeline/FragmentMastodonAccount.java | 8 - .../timeline/FragmentMastodonSuggestion.java | 161 ++++++++++++++++++ .../viewmodel/mastodon/SuggestionVM.java | 98 ----------- .../ic_baseline_account_circle_24.xml | 2 +- .../main/res/layout/activity_suggestions.xml | 2 +- app/src/main/res/layout/drawer_suggestion.xml | 109 ++++++++++++ .../main/res/menu/activity_main_drawer.xml | 6 +- app/src/main/res/values/strings.xml | 1 + 11 files changed, 425 insertions(+), 115 deletions(-) create mode 100644 app/src/main/java/app/fedilab/android/ui/drawer/SuggestionAdapter.java create mode 100644 app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonSuggestion.java delete mode 100644 app/src/main/java/app/fedilab/android/viewmodel/mastodon/SuggestionVM.java create mode 100644 app/src/main/res/layout/drawer_suggestion.xml diff --git a/app/src/main/java/app/fedilab/android/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/BaseMainActivity.java index 386a8ed6..3bc50a74 100644 --- a/app/src/main/java/app/fedilab/android/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/BaseMainActivity.java @@ -117,6 +117,7 @@ import app.fedilab.android.activities.ReorderTimelinesActivity; import app.fedilab.android.activities.ScheduledActivity; import app.fedilab.android.activities.SearchResultTabActivity; import app.fedilab.android.activities.SettingsActivity; +import app.fedilab.android.activities.SuggestionActivity; import app.fedilab.android.activities.TrendsActivity; import app.fedilab.android.broadcastreceiver.NetworkStateReceiver; import app.fedilab.android.client.entities.api.Emoji; @@ -391,6 +392,9 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt } else if (id == R.id.nav_trends) { Intent intent = new Intent(this, TrendsActivity.class); startActivity(intent); + } else if (id == R.id.nav_suggestions) { + Intent intent = new Intent(this, SuggestionActivity.class); + startActivity(intent); } else if (id == R.id.nav_cache) { Intent intent = new Intent(BaseMainActivity.this, CacheActivity.class); startActivity(intent); diff --git a/app/src/main/java/app/fedilab/android/activities/SuggestionActivity.java b/app/src/main/java/app/fedilab/android/activities/SuggestionActivity.java index e0a31d57..b9175901 100644 --- a/app/src/main/java/app/fedilab/android/activities/SuggestionActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/SuggestionActivity.java @@ -23,11 +23,10 @@ import androidx.core.content.ContextCompat; import org.jetbrains.annotations.NotNull; import app.fedilab.android.R; -import app.fedilab.android.client.entities.app.Timeline; import app.fedilab.android.databinding.ActivitySuggestionsBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.ThemeHelper; -import app.fedilab.android.ui.fragment.timeline.FragmentMastodonAccount; +import app.fedilab.android.ui.fragment.timeline.FragmentMastodonSuggestion; public class SuggestionActivity extends BaseActivity { @@ -37,7 +36,7 @@ public class SuggestionActivity extends BaseActivity { protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); ThemeHelper.applyThemeBar(this); - app.fedilab.android.databinding.ActivitySuggestionsBinding binding = ActivitySuggestionsBinding.inflate(getLayoutInflater()); + ActivitySuggestionsBinding binding = ActivitySuggestionsBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); @@ -46,9 +45,7 @@ public class SuggestionActivity extends BaseActivity { getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary))); } - Bundle bundle = new Bundle(); - bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, Timeline.TimeLineEnum.ACCOUNT_SUGGESTION); - Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_tags, new FragmentMastodonAccount(), bundle, null, null); + Helper.addFragment(getSupportFragmentManager(), R.id.nav_host_fragment_suggestions, new FragmentMastodonSuggestion(), null, null, null); } diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/SuggestionAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/SuggestionAdapter.java new file mode 100644 index 00000000..c5b6918b --- /dev/null +++ b/app/src/main/java/app/fedilab/android/ui/drawer/SuggestionAdapter.java @@ -0,0 +1,140 @@ +package app.fedilab.android.ui.drawer; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + + +import android.app.Activity; +import android.content.Context; +import android.content.Intent; +import android.content.res.ColorStateList; +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; + +import androidx.annotation.NonNull; +import androidx.core.app.ActivityOptionsCompat; +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelStoreOwner; +import androidx.recyclerview.widget.RecyclerView; + +import java.lang.ref.WeakReference; +import java.util.List; + +import app.fedilab.android.BaseMainActivity; +import app.fedilab.android.R; +import app.fedilab.android.activities.ProfileActivity; +import app.fedilab.android.client.entities.api.Account; +import app.fedilab.android.client.entities.api.Suggestion; +import app.fedilab.android.databinding.DrawerSuggestionBinding; +import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.MastodonHelper; +import app.fedilab.android.viewmodel.mastodon.AccountsVM; + + +public class SuggestionAdapter extends RecyclerView.Adapter { + + private final List suggestionList; + private Context context; + + public SuggestionAdapter(List suggestionList) { + this.suggestionList = suggestionList; + } + + + public int getCount() { + return suggestionList.size(); + } + + public Suggestion getItem(int position) { + return suggestionList.get(position); + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + this.context = parent.getContext(); + DrawerSuggestionBinding itemBinding = DrawerSuggestionBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); + return new SuggestionViewHolder(itemBinding); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { + Account account = suggestionList.get(position).account; + SuggestionViewHolder holder = (SuggestionViewHolder) viewHolder; + MastodonHelper.loadPPMastodon(holder.binding.avatar, account); + + + AccountsVM accountsVM = new ViewModelProvider((ViewModelStoreOwner) context).get(AccountsVM.class); + + holder.binding.avatar.setOnClickListener(v -> { + Intent intent = new Intent(context, ProfileActivity.class); + Bundle b = new Bundle(); + b.putSerializable(Helper.ARG_ACCOUNT, account); + intent.putExtras(b); + ActivityOptionsCompat options = ActivityOptionsCompat + .makeSceneTransitionAnimation((Activity) context, holder.binding.avatar, context.getString(R.string.activity_porfile_pp)); + // start the new activity + context.startActivity(intent, options.toBundle()); + }); + holder.binding.followAction.setIconResource(R.drawable.ic_baseline_person_add_24); + holder.binding.followAction.setBackgroundTintList(ColorStateList.valueOf(ContextCompat.getColor(context, R.color.cyanea_accent_dark_reference))); + holder.binding.displayName.setText( + account.getSpanDisplayName(context, + new WeakReference<>(holder.binding.displayName)), + TextView.BufferType.SPANNABLE); + holder.binding.username.setText(String.format("@%s", account.acct)); + holder.binding.bio.setText( + account.getSpanNote(context, + new WeakReference<>(holder.binding.bio)), + TextView.BufferType.SPANNABLE); + + holder.binding.followAction.setEnabled(false); + holder.binding.followAction.setOnClickListener(v -> { + suggestionList.remove(position); + notifyItemRemoved(position); + accountsVM.follow(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, true, false); + }); + holder.binding.notInterested.setOnClickListener(view -> { + suggestionList.remove(position); + notifyItemRemoved(position); + accountsVM.removeSuggestion(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id); + }); + //TODO, remove when supported + holder.binding.notInterested.setVisibility(View.GONE); + } + + public long getItemId(int position) { + return position; + } + + @Override + public int getItemCount() { + return suggestionList.size(); + } + + + public static class SuggestionViewHolder extends RecyclerView.ViewHolder { + DrawerSuggestionBinding binding; + + SuggestionViewHolder(DrawerSuggestionBinding itemView) { + super(itemView.getRoot()); + binding = itemView; + } + } + +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonAccount.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonAccount.java index f2e5fd4e..0d7f9694 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonAccount.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonAccount.java @@ -143,14 +143,6 @@ public class FragmentMastodonAccount extends Fragment { accountsVM.getBlocks(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, String.valueOf(MastodonHelper.accountsPerCall(requireActivity())), max_id, null) .observe(getViewLifecycleOwner(), this::initializeAccountCommonView); } - } else if (timelineType == Timeline.TimeLineEnum.ACCOUNT_SUGGESTION) { - if (firstLoad) { - accountsVM.getSuggestions(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null) - .observe(getViewLifecycleOwner(), this::initializeAccountCommonView); - } else { - accountsVM.getSuggestions(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, max_id) - .observe(getViewLifecycleOwner(), this::initializeAccountCommonView); - } } } diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonSuggestion.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonSuggestion.java new file mode 100644 index 00000000..417876cd --- /dev/null +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonSuggestion.java @@ -0,0 +1,161 @@ +package app.fedilab.android.ui.fragment.timeline; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + +import app.fedilab.android.BaseMainActivity; +import app.fedilab.android.R; +import app.fedilab.android.client.entities.api.Suggestion; +import app.fedilab.android.client.entities.api.Suggestions; +import app.fedilab.android.databinding.FragmentPaginationBinding; +import app.fedilab.android.helper.ThemeHelper; +import app.fedilab.android.ui.drawer.SuggestionAdapter; +import app.fedilab.android.viewmodel.mastodon.AccountsVM; + +public class FragmentMastodonSuggestion extends Fragment { + + + private FragmentPaginationBinding binding; + private AccountsVM accountsVM; + private boolean flagLoading; + private List suggestions; + private String max_id; + private SuggestionAdapter suggestionAdapter; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + flagLoading = false; + binding = FragmentPaginationBinding.inflate(inflater, container, false); + binding.getRoot().setBackgroundColor(ThemeHelper.getBackgroundColor(requireActivity())); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + int c1 = getResources().getColor(R.color.cyanea_accent_reference); + binding.swipeContainer.setProgressBackgroundColorSchemeColor(getResources().getColor(R.color.cyanea_primary_reference)); + binding.swipeContainer.setColorSchemeColors( + c1, c1, c1 + ); + binding.loader.setVisibility(View.VISIBLE); + binding.recyclerView.setVisibility(View.GONE); + accountsVM = new ViewModelProvider(FragmentMastodonSuggestion.this).get(AccountsVM.class); + max_id = null; + accountsVM.getSuggestions(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null) + .observe(getViewLifecycleOwner(), this::initializeAccountCommonView); + } + + + public void scrollToTop() { + binding.recyclerView.setAdapter(suggestionAdapter); + } + + /** + * Intialize the view for Suggestions + * + * @param suggestions {@link Suggestions} + */ + private void initializeAccountCommonView(final Suggestions suggestions) { + flagLoading = false; + if (binding == null || !isAdded() || getActivity() == null) { + return; + } + binding.loader.setVisibility(View.GONE); + binding.noAction.setVisibility(View.GONE); + binding.swipeContainer.setRefreshing(false); + binding.swipeContainer.setOnRefreshListener(() -> { + binding.swipeContainer.setRefreshing(true); + flagLoading = false; + max_id = null; + accountsVM.getSuggestions(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null) + .observe(getViewLifecycleOwner(), this::initializeAccountCommonView); + }); + if (suggestions == null || suggestions.suggestions == null || suggestions.suggestions.size() == 0) { + binding.noAction.setVisibility(View.VISIBLE); + binding.noActionText.setText(R.string.no_accounts); + return; + } + binding.recyclerView.setVisibility(View.VISIBLE); + + this.suggestions = suggestions.suggestions; + suggestionAdapter = new SuggestionAdapter(this.suggestions); + flagLoading = suggestions.pagination.max_id == null; + LinearLayoutManager mLayoutManager = new LinearLayoutManager(requireActivity()); + binding.recyclerView.setLayoutManager(mLayoutManager); + binding.recyclerView.setAdapter(suggestionAdapter); + max_id = suggestions.pagination.max_id; + binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + int firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition(); + if (dy > 0) { + int visibleItemCount = mLayoutManager.getChildCount(); + int totalItemCount = mLayoutManager.getItemCount(); + if (firstVisibleItem + visibleItemCount == totalItemCount) { + if (!flagLoading) { + flagLoading = true; + binding.loadingNextElements.setVisibility(View.VISIBLE); + accountsVM.getSuggestions(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, max_id) + .observe(getViewLifecycleOwner(), suggestionsPaginated -> { + dealWithPagination(suggestionsPaginated); + }); + } + } else { + binding.loadingNextElements.setVisibility(View.GONE); + } + } + + } + }); + } + + + /** + * Update view and pagination when scrolling down + * + * @param suggestions_fetched Suggestions + */ + private void dealWithPagination(Suggestions suggestions_fetched) { + flagLoading = false; + if (binding == null || !isAdded() || getActivity() == null) { + return; + } + binding.loadingNextElements.setVisibility(View.GONE); + if (this.suggestions != null && suggestions_fetched != null && suggestions_fetched.suggestions != null) { + flagLoading = suggestions_fetched.pagination.max_id == null; + int startId = this.suggestions.size(); + this.suggestions.addAll(suggestions_fetched.suggestions); + max_id = suggestions_fetched.pagination.max_id; + suggestionAdapter.notifyItemRangeInserted(startId, suggestions_fetched.suggestions.size()); + } else { + flagLoading = true; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/SuggestionVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/SuggestionVM.java deleted file mode 100644 index 04a0ac13..00000000 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/SuggestionVM.java +++ /dev/null @@ -1,98 +0,0 @@ -package app.fedilab.android.viewmodel.mastodon; -/* Copyright 2022 Thomas Schneider - * - * This file is a part of Fedilab - * - * This program is free software; you can redistribute it and/or modify it under the terms of the - * GNU General Public License as published by the Free Software Foundation; either version 3 of the - * License, or (at your option) any later version. - * - * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even - * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General - * Public License for more details. - * - * You should have received a copy of the GNU General Public License along with Fedilab; if not, - * see . */ - - -import android.app.Application; -import android.os.Handler; -import android.os.Looper; - -import androidx.annotation.NonNull; -import androidx.lifecycle.AndroidViewModel; -import androidx.lifecycle.LiveData; -import androidx.lifecycle.MutableLiveData; - -import java.util.List; -import java.util.concurrent.TimeUnit; - -import app.fedilab.android.client.endpoints.MastodonFiltersService; -import app.fedilab.android.client.endpoints.MastodonTimelinesService; -import app.fedilab.android.client.entities.api.Filter; -import app.fedilab.android.client.entities.api.Status; -import app.fedilab.android.client.entities.api.Statuses; -import app.fedilab.android.client.entities.app.Timeline; -import app.fedilab.android.helper.Helper; -import app.fedilab.android.helper.MastodonHelper; -import app.fedilab.android.helper.TimelineHelper; -import okhttp3.OkHttpClient; -import retrofit2.Call; -import retrofit2.Response; -import retrofit2.Retrofit; -import retrofit2.converter.gson.GsonConverterFactory; - -public class SuggestionVM extends AndroidViewModel { - - - final OkHttpClient okHttpClient = new OkHttpClient.Builder() - .readTimeout(60, TimeUnit.SECONDS) - .connectTimeout(60, TimeUnit.SECONDS) - .callTimeout(60, TimeUnit.SECONDS) - .proxy(Helper.getProxy(getApplication().getApplicationContext())) - .build(); - - - private MutableLiveData filterMutableLiveData; - private MutableLiveData> filterListMutableLiveData; - - public SuggestionVM(@NonNull Application application) { - super(application); - } - - private MastodonFiltersService initV2(String instance) { - Retrofit retrofit = new Retrofit.Builder() - .baseUrl("https://" + instance + "/api/v2/") - // .addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder())) - .addConverterFactory(GsonConverterFactory.create()) - .client(okHttpClient) - .build(); - return retrofit.create(MastodonFiltersService.class); - } - - - public LiveData getSuggestions(String token, @NonNull String instance, String max_id, Integer limit) { - MastodonTimelinesService mastodonTimelinesService = initV2(instance); - statusesMutableLiveData = new MutableLiveData<>(); - new Thread(() -> { - Call> publicTlCall = mastodonTimelinesService.getStatusTrends(token, max_id, limit); - Statuses statuses = new Statuses(); - if (publicTlCall != null) { - try { - Response> publicTlResponse = publicTlCall.execute(); - if (publicTlResponse.isSuccessful()) { - List statusList = publicTlResponse.body(); - statuses.statuses = TimelineHelper.filterStatus(getApplication().getApplicationContext(), statusList, Timeline.TimeLineEnum.TREND_MESSAGE); - statuses.pagination = MastodonHelper.getOffSetPagination(publicTlResponse.headers()); - } - } catch (Exception e) { - e.printStackTrace(); - } - } - Handler mainHandler = new Handler(Looper.getMainLooper()); - Runnable myRunnable = () -> statusesMutableLiveData.setValue(statuses); - mainHandler.post(myRunnable); - }).start(); - return statusesMutableLiveData; - } -} diff --git a/app/src/main/res/drawable/ic_baseline_account_circle_24.xml b/app/src/main/res/drawable/ic_baseline_account_circle_24.xml index f9110996..0b3fa82b 100644 --- a/app/src/main/res/drawable/ic_baseline_account_circle_24.xml +++ b/app/src/main/res/drawable/ic_baseline_account_circle_24.xml @@ -1,7 +1,7 @@ + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/menu/activity_main_drawer.xml b/app/src/main/res/menu/activity_main_drawer.xml index c82203cc..63e6efef 100644 --- a/app/src/main/res/menu/activity_main_drawer.xml +++ b/app/src/main/res/menu/activity_main_drawer.xml @@ -58,7 +58,11 @@ android:icon="@drawable/ic_baseline_trending_up_24" android:title="@string/trending" android:visible="true" /> - + Submitted a report Signed up Suggestions + Not interested \ No newline at end of file From 59472e7f6785e2bc03d315654147b7e76d60a224 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 22 Nov 2022 09:33:55 +0100 Subject: [PATCH 11/61] Fix db calls --- .../client/entities/app/StatusCache.java | 132 +++--------------- .../viewmodel/mastodon/NotificationsVM.java | 74 +++++----- .../viewmodel/mastodon/TimelinesVM.java | 68 ++++----- 3 files changed, 87 insertions(+), 187 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java b/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java index 655dbc1e..e4020657 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java +++ b/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java @@ -24,18 +24,13 @@ import com.google.gson.annotations.SerializedName; import java.util.ArrayList; import java.util.Calendar; -import java.util.Collections; import java.util.Date; import java.util.List; import app.fedilab.android.activities.MainActivity; import app.fedilab.android.client.entities.api.Conversation; -import app.fedilab.android.client.entities.api.Conversations; import app.fedilab.android.client.entities.api.Notification; -import app.fedilab.android.client.entities.api.Notifications; -import app.fedilab.android.client.entities.api.Pagination; import app.fedilab.android.client.entities.api.Status; -import app.fedilab.android.client.entities.api.Statuses; import app.fedilab.android.exception.DBException; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastodonHelper; @@ -266,36 +261,21 @@ public class StatusCache { if (db == null) { throw new DBException("db is null. Wrong initialization."); } - Cursor mCount = db.rawQuery("select count(*) from " + Sqlite.TABLE_STATUS_CACHE + String query = "select count(*) from " + Sqlite.TABLE_STATUS_CACHE + " where " + Sqlite.COL_STATUS_ID + " = '" + statusCache.status_id + "'" + " AND " + Sqlite.COL_INSTANCE + " = '" + statusCache.instance + "'" - + " AND " + Sqlite.COL_USER_ID + "= '" + statusCache.user_id + "'", null); + + " AND " + Sqlite.COL_USER_ID + "= '" + statusCache.user_id + "'"; + if (statusCache.type != null) { + query += " AND " + Sqlite.COL_TYPE + " = '" + statusCache.type.getValue() + "'"; + } + + Cursor mCount = db.rawQuery(query, null); mCount.moveToFirst(); int count = mCount.getInt(0); mCount.close(); return (count > 0); } - /** - * Check if a status exists in db - * - * @param status Status {@link Status} - * @return boolean - StatusCache exists - * @throws DBException Exception - */ - public boolean statusExist(Status status) throws DBException { - if (db == null) { - throw new DBException("db is null. Wrong initialization."); - } - Cursor mCount = db.rawQuery("select count(*) from " + Sqlite.TABLE_STATUS_CACHE - + " where " + Sqlite.COL_STATUS_ID + " = '" + status.id + "'" - + " AND " + Sqlite.COL_INSTANCE + " = '" + MainActivity.currentInstance + "'" - + " AND " + Sqlite.COL_USER_ID + "= '" + MainActivity.currentUserID + "'", null); - mCount.moveToFirst(); - int count = mCount.getInt(0); - mCount.close(); - return (count > 0); - } /** * Insert a status in db @@ -313,7 +293,9 @@ public class StatusCache { values.put(Sqlite.COL_INSTANCE, statusCache.instance); values.put(Sqlite.COL_SLUG, slug); values.put(Sqlite.COL_STATUS_ID, statusCache.status_id); - values.put(Sqlite.COL_TYPE, statusCache.type.getValue()); + if (type != null) { + values.put(Sqlite.COL_TYPE, statusCache.type.getValue()); + } if (statusCache.status != null) { values.put(Sqlite.COL_STATUS, mastodonStatusToStringStorage(statusCache.status)); } @@ -345,8 +327,6 @@ public class StatusCache { throw new DBException("db is null. Wrong initialization."); } ContentValues values = new ContentValues(); - values.put(Sqlite.COL_USER_ID, statusCache.user_id); - values.put(Sqlite.COL_STATUS_ID, statusCache.status_id); if (statusCache.status != null) { values.put(Sqlite.COL_STATUS, mastodonStatusToStringStorage(statusCache.status)); } @@ -360,8 +340,8 @@ public class StatusCache { //Inserts token try { return db.update(Sqlite.TABLE_STATUS_CACHE, - values, Sqlite.COL_STATUS_ID + " = ? AND " + Sqlite.COL_INSTANCE + " =?", - new String[]{statusCache.status_id, statusCache.instance}); + values, Sqlite.COL_STATUS_ID + " = ? AND " + Sqlite.COL_INSTANCE + " =? AND " + Sqlite.COL_TYPE + " =? AND " + Sqlite.COL_USER_ID + " =?", + new String[]{statusCache.status_id, statusCache.instance, statusCache.type != null ? statusCache.type.getValue() : "", statusCache.user_id}); } catch (Exception e) { e.printStackTrace(); return -1; @@ -543,10 +523,10 @@ public class StatusCache { * @param user_id String - us * @param max_id String - status having max id * @param min_id String - status having min id - * @return Statuses + * @return List * @throws DBException - throws a db exception */ - public Notifications getNotifications(List exclude_type, String instance, String user_id, String max_id, String min_id, String since_id) throws DBException { + public List getNotifications(List exclude_type, String instance, String user_id, String max_id, String min_id, String since_id) throws DBException { if (db == null) { throw new DBException("db is null. Wrong initialization."); } @@ -573,7 +553,7 @@ public class StatusCache { } try { Cursor c = db.query(Sqlite.TABLE_STATUS_CACHE, null, selection, null, Sqlite.COL_STATUS_ID, null, Sqlite.COL_STATUS_ID + order, limit); - return createNotificationReply(cursorToListOfNotifications(c)); + return cursorToListOfNotifications(c); } catch (Exception e) { e.printStackTrace(); return null; @@ -588,10 +568,10 @@ public class StatusCache { * @param user_id String - us * @param max_id String - status having max id * @param min_id String - status having min id - * @return Statuses + * @return List * @throws DBException - throws a db exception */ - public Conversations getConversations(String instance, String user_id, String max_id, String min_id, String since_id) throws DBException { + public List getConversations(String instance, String user_id, String max_id, String min_id, String since_id) throws DBException { if (db == null) { throw new DBException("db is null. Wrong initialization."); } @@ -607,10 +587,9 @@ public class StatusCache { selection += "AND " + Sqlite.COL_STATUS_ID + " > '" + since_id + "' "; limit = null; } - try { Cursor c = db.query(Sqlite.TABLE_STATUS_CACHE, null, selection, null, Sqlite.COL_STATUS_ID, null, Sqlite.COL_STATUS_ID + order, limit); - return createConversationReply(cursorToListOfConversations(c)); + return cursorToListOfConversations(c); } catch (Exception e) { e.printStackTrace(); return null; @@ -625,10 +604,10 @@ public class StatusCache { * @param user_id String - us * @param max_id String - status having max id * @param min_id String - status having min id - * @return Statuses + * @return List * @throws DBException - throws a db exception */ - public Statuses geStatuses(String slug, String instance, String user_id, String max_id, String min_id, String since_id) throws DBException { + public List geStatuses(String slug, String instance, String user_id, String max_id, String min_id, String since_id) throws DBException { if (db == null) { throw new DBException("db is null. Wrong initialization."); } @@ -646,7 +625,7 @@ public class StatusCache { } try { Cursor c = db.query(Sqlite.TABLE_STATUS_CACHE, null, selection, null, Sqlite.COL_STATUS_ID, null, Sqlite.COL_STATUS_ID + order, limit); - return createStatusReply(cursorToListOfStatuses(c)); + return cursorToListOfStatuses(c); } catch (Exception e) { e.printStackTrace(); return null; @@ -769,75 +748,6 @@ public class StatusCache { return conversationList; } - /** - * Create a reply from db in the same way than API call - * - * @param notificationList List - * @return Notifications (with pagination) - */ - private Notifications createNotificationReply(List notificationList) { - Notifications notifications = new Notifications(); - notifications.notifications = notificationList; - Pagination pagination = new Pagination(); - if (notificationList != null && notificationList.size() > 0) { - //Status list is inverted, it happens for min_id due to ASC ordering - if (Helper.compareTo(notificationList.get(0).id, notificationList.get(notificationList.size() - 1).id) < 0) { - Collections.reverse(notificationList); - notifications.notifications = notificationList; - } - pagination.max_id = notificationList.get(0).id; - pagination.min_id = notificationList.get(notificationList.size() - 1).id; - } - notifications.pagination = pagination; - return notifications; - } - - - /** - * Create a reply from db in the same way than API call - * - * @param conversationList List - * @return Conversations (with pagination) - */ - private Conversations createConversationReply(List conversationList) { - Conversations conversations = new Conversations(); - conversations.conversations = conversationList; - Pagination pagination = new Pagination(); - if (conversationList != null && conversationList.size() > 0) { - //Status list is inverted, it happens for min_id due to ASC ordering - if (Helper.compareTo(conversationList.get(0).id, conversationList.get(conversationList.size() - 1).id) < 0) { - Collections.reverse(conversationList); - conversations.conversations = conversationList; - } - pagination.max_id = conversationList.get(0).id; - pagination.min_id = conversationList.get(conversationList.size() - 1).id; - } - conversations.pagination = pagination; - return conversations; - } - - /** - * Create a reply from db in the same way than API call - * - * @param statusList List - * @return Statuses (with pagination) - */ - private Statuses createStatusReply(List statusList) { - Statuses statuses = new Statuses(); - statuses.statuses = statusList; - Pagination pagination = new Pagination(); - if (statusList != null && statusList.size() > 0) { - //Status list is inverted, it happens for min_id due to ASC ordering - if (Helper.compareTo(statusList.get(0).id, statusList.get(statusList.size() - 1).id) < 0) { - Collections.reverse(statusList); - statuses.statuses = statusList; - } - pagination.max_id = statusList.get(0).id; - pagination.min_id = statusList.get(statusList.size() - 1).id; - } - statuses.pagination = pagination; - return statuses; - } /** * Read cursor and hydrate without closing it diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/NotificationsVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/NotificationsVM.java index e58e4bd3..16271d4e 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/NotificationsVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/NotificationsVM.java @@ -112,24 +112,24 @@ public class NotificationsVM extends AndroidViewModel { if (notificationsResponse.isSuccessful()) { List notFiltered = notificationsResponse.body(); notifications.notifications = TimelineHelper.filterNotification(getApplication().getApplicationContext(), notFiltered); - addFetchMoreNotifications(notifications.notifications, notificationList, timelineParams); notifications.pagination = MastodonHelper.getPagination(notificationsResponse.headers()); - if (notifications.notifications != null && notifications.notifications.size() > 0) { addFetchMoreNotifications(notifications.notifications, notificationList, timelineParams); - for (Notification notification : notifications.notifications) { - StatusCache statusCacheDAO = new StatusCache(getApplication().getApplicationContext()); - StatusCache statusCache = new StatusCache(); - statusCache.instance = timelineParams.instance; - statusCache.user_id = timelineParams.userId; - statusCache.notification = notification; - statusCache.slug = notification.type; - statusCache.type = Timeline.TimeLineEnum.NOTIFICATION; - statusCache.status_id = notification.id; - try { - statusCacheDAO.insertOrUpdate(statusCache, notification.type); - } catch (DBException e) { - e.printStackTrace(); + if (notFiltered != null && notFiltered.size() > 0) { + for (Notification notification : notFiltered) { + StatusCache statusCacheDAO = new StatusCache(getApplication().getApplicationContext()); + StatusCache statusCache = new StatusCache(); + statusCache.instance = timelineParams.instance; + statusCache.user_id = timelineParams.userId; + statusCache.notification = notification; + statusCache.slug = notification.type; + statusCache.type = Timeline.TimeLineEnum.NOTIFICATION; + statusCache.status_id = notification.id; + try { + statusCacheDAO.insertOrUpdate(statusCache, notification.type); + } catch (DBException e) { + e.printStackTrace(); + } } } } @@ -146,41 +146,39 @@ public class NotificationsVM extends AndroidViewModel { return notificationsMutableLiveData; } - public LiveData getNotificationCache(List notificationList, TimelinesVM.TimelineParams timelineParams) { + public LiveData getNotificationCache(List timelineNotification, TimelinesVM.TimelineParams timelineParams) { notificationsMutableLiveData = new MutableLiveData<>(); new Thread(() -> { StatusCache statusCacheDAO = new StatusCache(getApplication().getApplicationContext()); - Notifications notifications = null; + Notifications notifications = new Notifications(); + List notificationsDb; try { - notifications = statusCacheDAO.getNotifications(timelineParams.excludeType, timelineParams.instance, timelineParams.userId, timelineParams.maxId, timelineParams.minId, timelineParams.sinceId); - if (notifications != null) { - if (notifications.notifications != null && notifications.notifications.size() > 0) { - if (notificationList != null) { - List notPresentNotifications = new ArrayList<>(); - for (Notification notification : notifications.notifications) { - if (!notificationList.contains(notification)) { - notification.cached = true; - notPresentNotifications.add(notification); - } + notificationsDb = statusCacheDAO.getNotifications(timelineParams.excludeType, timelineParams.instance, timelineParams.userId, timelineParams.maxId, timelineParams.minId, timelineParams.sinceId); + if (notificationsDb != null && notificationsDb.size() > 0) { + if (timelineNotification != null) { + List notPresentNotifications = new ArrayList<>(); + for (Notification notification : notificationsDb) { + if (!timelineNotification.contains(notification)) { + notification.cached = true; + notPresentNotifications.add(notification); } - //Only not already present statuses are added - notifications.notifications = notPresentNotifications; - } - TimelineHelper.filterNotification(getApplication().getApplicationContext(), notifications.notifications); - if (notifications.notifications.size() > 0) { - addFetchMoreNotifications(notifications.notifications, notificationList, timelineParams); - notifications.pagination = new Pagination(); - notifications.pagination.min_id = notifications.notifications.get(0).id; - notifications.pagination.max_id = notifications.notifications.get(notifications.notifications.size() - 1).id; } + //Only not already present statuses are added + notificationsDb = notPresentNotifications; + } + notifications.notifications = TimelineHelper.filterNotification(getApplication().getApplicationContext(), notificationsDb); + if (notifications.notifications.size() > 0) { + addFetchMoreNotifications(notifications.notifications, timelineNotification, timelineParams); + notifications.pagination = new Pagination(); + notifications.pagination.min_id = notifications.notifications.get(0).id; + notifications.pagination.max_id = notifications.notifications.get(notifications.notifications.size() - 1).id; } } } catch (DBException e) { e.printStackTrace(); } Handler mainHandler = new Handler(Looper.getMainLooper()); - Notifications finalNotifications = notifications; - Runnable myRunnable = () -> notificationsMutableLiveData.setValue(finalNotifications); + Runnable myRunnable = () -> notificationsMutableLiveData.setValue(notifications); mainHandler.post(myRunnable); }).start(); return notificationsMutableLiveData; diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java index b3fff957..79fc7669 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java @@ -463,21 +463,21 @@ public class TimelinesVM extends AndroidViewModel { StatusCache statusCacheDAO = new StatusCache(getApplication().getApplicationContext()); Statuses statuses = new Statuses(); try { - Statuses statusesDb = statusCacheDAO.geStatuses(timelineParams.slug, timelineParams.instance, timelineParams.userId, timelineParams.maxId, timelineParams.minId, timelineParams.sinceId); - if (statusesDb != null && statusesDb.statuses != null && statusesDb.statuses.size() > 0) { + List statusesDb = statusCacheDAO.geStatuses(timelineParams.slug, timelineParams.instance, timelineParams.userId, timelineParams.maxId, timelineParams.minId, timelineParams.sinceId); + if (statusesDb != null && statusesDb.size() > 0) { if (timelineStatuses != null) { List notPresentStatuses = new ArrayList<>(); - for (Status status : statusesDb.statuses) { + for (Status status : statusesDb) { if (!timelineStatuses.contains(status)) { status.cached = true; notPresentStatuses.add(status); } } //Only not already present statuses are added - statusesDb.statuses = notPresentStatuses; + statusesDb = notPresentStatuses; } - statuses.statuses = TimelineHelper.filterStatus(getApplication().getApplicationContext(), statusesDb.statuses, timelineParams.type); - if (statuses.statuses != null && statuses.statuses.size() > 0) { + statuses.statuses = TimelineHelper.filterStatus(getApplication().getApplicationContext(), statusesDb, timelineParams.type); + if (statuses.statuses.size() > 0) { addFetchMore(statuses.statuses, timelineStatuses, timelineParams); statuses.pagination = new Pagination(); statuses.pagination.min_id = statuses.statuses.get(0).id; @@ -528,21 +528,16 @@ public class TimelinesVM extends AndroidViewModel { conversationListMutableLiveData = new MutableLiveData<>(); MastodonTimelinesService mastodonTimelinesService = init(timelineParams.instance); new Thread(() -> { - Conversations conversations = null; + Conversations conversations = new Conversations(); Call> conversationsCall = mastodonTimelinesService.getConversations(timelineParams.token, timelineParams.maxId, timelineParams.sinceId, timelineParams.minId, timelineParams.limit); if (conversationsCall != null) { - conversations = new Conversations(); try { Response> conversationsResponse = conversationsCall.execute(); if (conversationsResponse.isSuccessful()) { - List conversationList = conversationsResponse.body(); - conversations.conversations = conversationList; + conversations.conversations = conversationsResponse.body(); conversations.pagination = MastodonHelper.getPagination(conversationsResponse.headers()); - - if (conversationList != null && conversationList.size() > 0) { - - addFetchMoreConversation(conversationList, conversationsTimeline, timelineParams); - + if (conversations.conversations != null && conversations.conversations.size() > 0) { + addFetchMoreConversation(conversations.conversations, conversationsTimeline, timelineParams); for (Conversation conversation : conversations.conversations) { StatusCache statusCacheDAO = new StatusCache(getApplication().getApplicationContext()); StatusCache statusCache = new StatusCache(); @@ -564,8 +559,7 @@ public class TimelinesVM extends AndroidViewModel { } } Handler mainHandler = new Handler(Looper.getMainLooper()); - Conversations finalConversations = conversations; - Runnable myRunnable = () -> conversationListMutableLiveData.setValue(finalConversations); + Runnable myRunnable = () -> conversationListMutableLiveData.setValue(conversations); mainHandler.post(myRunnable); }).start(); @@ -577,36 +571,34 @@ public class TimelinesVM extends AndroidViewModel { conversationListMutableLiveData = new MutableLiveData<>(); new Thread(() -> { StatusCache statusCacheDAO = new StatusCache(getApplication().getApplicationContext()); - Conversations conversations = null; + Conversations conversations = new Conversations(); try { - conversations = statusCacheDAO.getConversations(timelineParams.instance, timelineParams.userId, timelineParams.maxId, timelineParams.minId, timelineParams.sinceId); - if (conversations != null) { - if (conversations.conversations != null && conversations.conversations.size() > 0) { - if (timelineConversations != null) { - List notPresentConversations = new ArrayList<>(); - for (Conversation conversation : conversations.conversations) { - if (!timelineConversations.contains(conversation)) { - conversation.cached = true; - timelineConversations.add(conversation); - } + List conversationsDb = statusCacheDAO.getConversations(timelineParams.instance, timelineParams.userId, timelineParams.maxId, timelineParams.minId, timelineParams.sinceId); + if (conversationsDb != null && conversationsDb.size() > 0) { + if (timelineConversations != null) { + List notPresentConversations = new ArrayList<>(); + for (Conversation conversation : conversationsDb) { + if (!timelineConversations.contains(conversation)) { + conversation.cached = true; + timelineConversations.add(conversation); } - //Only not already present statuses are added - conversations.conversations = notPresentConversations; - } - if (conversations.conversations.size() > 0) { - addFetchMoreConversation(conversations.conversations, timelineConversations, timelineParams); - conversations.pagination = new Pagination(); - conversations.pagination.min_id = conversations.conversations.get(0).id; - conversations.pagination.max_id = conversations.conversations.get(conversations.conversations.size() - 1).id; } + //Only not already present statuses are added + conversationsDb = notPresentConversations; + } + conversations.conversations = conversationsDb; + if (conversations.conversations.size() > 0) { + addFetchMoreConversation(conversations.conversations, timelineConversations, timelineParams); + conversations.pagination = new Pagination(); + conversations.pagination.min_id = conversations.conversations.get(0).id; + conversations.pagination.max_id = conversations.conversations.get(conversations.conversations.size() - 1).id; } } } catch (DBException e) { e.printStackTrace(); } Handler mainHandler = new Handler(Looper.getMainLooper()); - Conversations finalConversations = conversations; - Runnable myRunnable = () -> conversationListMutableLiveData.setValue(finalConversations); + Runnable myRunnable = () -> conversationListMutableLiveData.setValue(conversations); mainHandler.post(myRunnable); }).start(); return conversationListMutableLiveData; From f647c308a2d67b265f89f0821033168782c34405 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 22 Nov 2022 09:41:10 +0100 Subject: [PATCH 12/61] Fix a crash if app is killed --- .../java/app/fedilab/android/activities/CacheActivity.java | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/fedilab/android/activities/CacheActivity.java b/app/src/main/java/app/fedilab/android/activities/CacheActivity.java index 124c7800..19cc67d4 100644 --- a/app/src/main/java/app/fedilab/android/activities/CacheActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/CacheActivity.java @@ -143,7 +143,11 @@ public class CacheActivity extends BaseActivity { dialogRestart.dismiss(); Helper.restart(CacheActivity.this); }); - restartBuilder.create().show(); + AlertDialog alertDialog = restartBuilder.create(); + if (!isFinishing()) { + alertDialog.show(); + } + })); dialog.dismiss(); }); From 1b8304d230aa84ecd6cc424101e2543ce1ffc848 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 22 Nov 2022 09:44:58 +0100 Subject: [PATCH 13/61] Fix #515 - set resolve to false when searching accounts --- .../main/java/app/fedilab/android/ui/drawer/ComposeAdapter.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/ComposeAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/ComposeAdapter.java index d2b2d639..1fb1e7a7 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/ComposeAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/ComposeAdapter.java @@ -619,7 +619,7 @@ public class ComposeAdapter extends RecyclerView.Adapter { + accountsVM.searchAccounts(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, searchGroup, 5, false, false).observe((LifecycleOwner) context, accounts -> { if (accounts == null) { return; } From 0bcb4e0fca1bddd66047bcf1ec266beaf9444d1a Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 22 Nov 2022 11:03:43 +0100 Subject: [PATCH 14/61] Fix #515 - set resolve to false when searching accounts --- .../android/client/entities/app/StatusCache.java | 2 +- app/src/main/res/layout/activity_actions.xml | 16 ++++++++++++++++ app/src/main/res/values/strings.xml | 1 + 3 files changed, 18 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java b/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java index e4020657..61868bd3 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java +++ b/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java @@ -293,7 +293,7 @@ public class StatusCache { values.put(Sqlite.COL_INSTANCE, statusCache.instance); values.put(Sqlite.COL_SLUG, slug); values.put(Sqlite.COL_STATUS_ID, statusCache.status_id); - if (type != null) { + if (statusCache.type != null) { values.put(Sqlite.COL_TYPE, statusCache.type.getValue()); } if (statusCache.status != null) { diff --git a/app/src/main/res/layout/activity_actions.xml b/app/src/main/res/layout/activity_actions.xml index 88ce2245..052ae1c6 100644 --- a/app/src/main/res/layout/activity_actions.xml +++ b/app/src/main/res/layout/activity_actions.xml @@ -71,6 +71,22 @@ app:iconTint="@color/cyanea_accent_dark_reference" app:strokeColor="@color/cyanea_accent_dark_reference" /> + + + Signed up Suggestions Not interested + Blocked domains \ No newline at end of file From 28e10ddc82ca4de41d9f62221179bb9c2d560330 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 22 Nov 2022 12:32:16 +0100 Subject: [PATCH 15/61] Fix pagination from db --- .../android/client/entities/app/StatusCache.java | 11 +++++------ .../app/fedilab/android/helper/TimelineHelper.java | 1 - 2 files changed, 5 insertions(+), 7 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java b/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java index 61868bd3..62672258 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java +++ b/app/src/main/java/app/fedilab/android/client/entities/app/StatusCache.java @@ -242,7 +242,6 @@ public class StatusCache { } Cursor mCount = db.rawQuery("select count(*) from " + Sqlite.TABLE_STATUS_CACHE + " where " + Sqlite.COL_TYPE + " != '" + Timeline.TimeLineEnum.HOME.getValue() + "'" - + " AND " + Sqlite.COL_INSTANCE + " = '" + baseAccount.instance + "'" + " AND " + Sqlite.COL_USER_ID + "= '" + baseAccount.user_id + "'", null); mCount.moveToFirst(); int count = mCount.getInt(0); @@ -465,8 +464,8 @@ public class StatusCache { } try { return db.delete(Sqlite.TABLE_STATUS_CACHE, - Sqlite.COL_TYPE + " != ? AND " + Sqlite.COL_USER_ID + " = ? AND " + Sqlite.COL_INSTANCE + " =?", - new String[]{Timeline.TimeLineEnum.HOME.getValue(), account.user_id, account.instance}); + Sqlite.COL_TYPE + " != ? AND " + Sqlite.COL_USER_ID + " = ?", + new String[]{Timeline.TimeLineEnum.HOME.getValue(), account.user_id}); } catch (Exception e) { e.printStackTrace(); return -1; @@ -552,7 +551,7 @@ public class StatusCache { selection += "AND " + Sqlite.COL_SLUG + " NOT IN (" + exclude + ") "; } try { - Cursor c = db.query(Sqlite.TABLE_STATUS_CACHE, null, selection, null, Sqlite.COL_STATUS_ID, null, Sqlite.COL_STATUS_ID + order, limit); + Cursor c = db.query(Sqlite.TABLE_STATUS_CACHE, null, selection, null, Sqlite.COL_STATUS_ID, null, Sqlite.COL_STATUS_ID + " + 0 " + order, limit); return cursorToListOfNotifications(c); } catch (Exception e) { e.printStackTrace(); @@ -588,7 +587,7 @@ public class StatusCache { limit = null; } try { - Cursor c = db.query(Sqlite.TABLE_STATUS_CACHE, null, selection, null, Sqlite.COL_STATUS_ID, null, Sqlite.COL_STATUS_ID + order, limit); + Cursor c = db.query(Sqlite.TABLE_STATUS_CACHE, null, selection, null, Sqlite.COL_STATUS_ID, null, Sqlite.COL_STATUS_ID + " + 0 " + order, limit); return cursorToListOfConversations(c); } catch (Exception e) { e.printStackTrace(); @@ -624,7 +623,7 @@ public class StatusCache { limit = null; } try { - Cursor c = db.query(Sqlite.TABLE_STATUS_CACHE, null, selection, null, Sqlite.COL_STATUS_ID, null, Sqlite.COL_STATUS_ID + order, limit); + Cursor c = db.query(Sqlite.TABLE_STATUS_CACHE, null, selection, null, Sqlite.COL_STATUS_ID, null, Sqlite.COL_STATUS_ID + " + 0 " + order, limit); return cursorToListOfStatuses(c); } catch (Exception e) { e.printStackTrace(); diff --git a/app/src/main/java/app/fedilab/android/helper/TimelineHelper.java b/app/src/main/java/app/fedilab/android/helper/TimelineHelper.java index 3dd0704e..02ce2b2f 100644 --- a/app/src/main/java/app/fedilab/android/helper/TimelineHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/TimelineHelper.java @@ -177,7 +177,6 @@ public class TimelineHelper { public static List filterNotification(Context context, List notifications) { //A security to make sure filters have been fetched before displaying messages List notificationToRemove = new ArrayList<>(); - if (!BaseMainActivity.filterFetched) { try { FiltersVM filtersVM = new ViewModelProvider((ViewModelStoreOwner) context).get(FiltersVM.class); From 5f1cd065661e7868b7b96eaf707eb6fed919505a Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 22 Nov 2022 14:50:21 +0100 Subject: [PATCH 16/61] Fix issue #513 - Add the ability to check blocked domains and to unblock them. --- .../android/activities/ActionActivity.java | 18 ++ .../endpoints/MastodonAccountsService.java | 2 +- .../android/client/entities/api/Domains.java | 22 ++ .../android/client/entities/app/Timeline.java | 2 + .../android/ui/drawer/DomainBlockAdapter.java | 95 ++++++++ .../timeline/FragmentMastodonDomainBlock.java | 214 ++++++++++++++++++ .../viewmodel/mastodon/AccountsVM.java | 18 +- .../main/res/layout/drawer_domain_block.xml | 53 +++++ app/src/main/res/values/strings.xml | 3 + 9 files changed, 418 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/app/fedilab/android/client/entities/api/Domains.java create mode 100644 app/src/main/java/app/fedilab/android/ui/drawer/DomainBlockAdapter.java create mode 100644 app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonDomainBlock.java create mode 100644 app/src/main/res/layout/drawer_domain_block.xml diff --git a/app/src/main/java/app/fedilab/android/activities/ActionActivity.java b/app/src/main/java/app/fedilab/android/activities/ActionActivity.java index 7ff2e7b4..0088f7c3 100644 --- a/app/src/main/java/app/fedilab/android/activities/ActionActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/ActionActivity.java @@ -29,6 +29,7 @@ import app.fedilab.android.databinding.ActivityActionsBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.ThemeHelper; import app.fedilab.android.ui.fragment.timeline.FragmentMastodonAccount; +import app.fedilab.android.ui.fragment.timeline.FragmentMastodonDomainBlock; import app.fedilab.android.ui.fragment.timeline.FragmentMastodonTimeline; public class ActionActivity extends BaseActivity { @@ -37,6 +38,7 @@ public class ActionActivity extends BaseActivity { private boolean canGoBack; private FragmentMastodonTimeline fragmentMastodonTimeline; private FragmentMastodonAccount fragmentMastodonAccount; + private FragmentMastodonDomainBlock fragmentMastodonDomainBlock; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -54,6 +56,7 @@ public class ActionActivity extends BaseActivity { binding.bookmarks.setOnClickListener(v -> displayTimeline(Timeline.TimeLineEnum.BOOKMARK_TIMELINE)); binding.muted.setOnClickListener(v -> displayTimeline(Timeline.TimeLineEnum.MUTED_TIMELINE)); binding.blocked.setOnClickListener(v -> displayTimeline(Timeline.TimeLineEnum.BLOCKED_TIMELINE)); + binding.domainBlock.setOnClickListener(v -> displayTimeline(Timeline.TimeLineEnum.BLOCKED_DOMAIN_TIMELINE)); } private void displayTimeline(Timeline.TimeLineEnum type) { @@ -73,6 +76,15 @@ public class ActionActivity extends BaseActivity { fragmentTransaction.commit(); }); + } else if (type == Timeline.TimeLineEnum.BLOCKED_DOMAIN_TIMELINE) { + ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> { + fragmentMastodonDomainBlock = new FragmentMastodonDomainBlock(); + FragmentManager fragmentManager = getSupportFragmentManager(); + FragmentTransaction fragmentTransaction = + fragmentManager.beginTransaction(); + fragmentTransaction.replace(R.id.fragment_container, fragmentMastodonDomainBlock); + fragmentTransaction.commit(); + }); } else { ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> { @@ -102,6 +114,9 @@ public class ActionActivity extends BaseActivity { case BOOKMARK_TIMELINE: setTitle(R.string.bookmarks); break; + case BLOCKED_DOMAIN_TIMELINE: + setTitle(R.string.blocked_domains); + break; } } @@ -116,6 +131,9 @@ public class ActionActivity extends BaseActivity { if (fragmentMastodonAccount != null) { fragmentMastodonAccount.onDestroyView(); } + if (fragmentMastodonDomainBlock != null) { + fragmentMastodonDomainBlock.onDestroyView(); + } }); setTitle(R.string.interactions); } else { diff --git a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java index 499988c2..a2ec0d5f 100644 --- a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java +++ b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAccountsService.java @@ -316,7 +316,7 @@ public interface MastodonAccountsService { @DELETE("domain_blocks") Call removeDomainBlocks( @Header("Authorization") String token, - @Field("domain") String domain + @Query("domain") String domain ); diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/Domains.java b/app/src/main/java/app/fedilab/android/client/entities/api/Domains.java new file mode 100644 index 00000000..cbcb63c3 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/entities/api/Domains.java @@ -0,0 +1,22 @@ +package app.fedilab.android.client.entities.api; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + +import java.util.List; + +public class Domains { + public Pagination pagination = new Pagination(); + public List domains; +} diff --git a/app/src/main/java/app/fedilab/android/client/entities/app/Timeline.java b/app/src/main/java/app/fedilab/android/client/entities/app/Timeline.java index a195daaa..0f5f6046 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/app/Timeline.java +++ b/app/src/main/java/app/fedilab/android/client/entities/app/Timeline.java @@ -390,6 +390,8 @@ public class Timeline { MUTED_TIMELINE("MUTED_TIMELINE"), @SerializedName("BOOKMARK_TIMELINE") BOOKMARK_TIMELINE("BOOKMARK_TIMELINE"), + @SerializedName("BLOCKED_DOMAIN_TIMELINE") + BLOCKED_DOMAIN_TIMELINE("BLOCKED_DOMAIN_TIMELINE"), @SerializedName("BLOCKED_TIMELINE") BLOCKED_TIMELINE("BLOCKED_TIMELINE"), @SerializedName("FAVOURITE_TIMELINE") diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/DomainBlockAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/DomainBlockAdapter.java new file mode 100644 index 00000000..a539f5e3 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/ui/drawer/DomainBlockAdapter.java @@ -0,0 +1,95 @@ +package app.fedilab.android.ui.drawer; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; +import androidx.lifecycle.ViewModelProvider; +import androidx.lifecycle.ViewModelStoreOwner; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.List; + +import app.fedilab.android.R; +import app.fedilab.android.activities.MainActivity; +import app.fedilab.android.databinding.DrawerDomainBlockBinding; +import app.fedilab.android.helper.Helper; +import app.fedilab.android.viewmodel.mastodon.AccountsVM; + + +public class DomainBlockAdapter extends RecyclerView.Adapter { + private final List domainList; + private Context context; + + public DomainBlockAdapter(List domainList) { + this.domainList = domainList; + } + + public int getCount() { + return domainList.size(); + } + + public String getItem(int position) { + return domainList.get(position); + } + + @NonNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { + context = parent.getContext(); + DrawerDomainBlockBinding itemBinding = DrawerDomainBlockBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); + return new DomainBlockViewHolder(itemBinding); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { + String domain = domainList.get(position); + DomainBlockViewHolder holder = (DomainBlockViewHolder) viewHolder; + holder.binding.domainName.setText(domain); + AccountsVM accountsVM = new ViewModelProvider((ViewModelStoreOwner) context).get(AccountsVM.class); + holder.binding.unblockDomain.setOnClickListener(v -> { + AlertDialog.Builder alt_bld = new AlertDialog.Builder(context, Helper.dialogStyle()); + alt_bld.setMessage(context.getString(R.string.unblock_domain_confirm, domain)); + alt_bld.setPositiveButton(R.string.yes, (dialog, id) -> { + accountsVM.removeDomainBlocks(MainActivity.currentInstance, MainActivity.currentToken, domain); + domainList.remove(position); + notifyItemRemoved(position); + dialog.dismiss(); + }); + alt_bld.setNegativeButton(R.string.cancel, (dialog, id) -> dialog.dismiss()); + AlertDialog alert = alt_bld.create(); + alert.show(); + }); + } + + @Override + public int getItemCount() { + return domainList.size(); + } + + + public static class DomainBlockViewHolder extends RecyclerView.ViewHolder { + DrawerDomainBlockBinding binding; + + DomainBlockViewHolder(DrawerDomainBlockBinding itemView) { + super(itemView.getRoot()); + binding = itemView; + } + } +} diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonDomainBlock.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonDomainBlock.java new file mode 100644 index 00000000..65209c2f --- /dev/null +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonDomainBlock.java @@ -0,0 +1,214 @@ +package app.fedilab.android.ui.fragment.timeline; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.List; + +import app.fedilab.android.R; +import app.fedilab.android.activities.MainActivity; +import app.fedilab.android.client.entities.api.Domains; +import app.fedilab.android.databinding.FragmentPaginationBinding; +import app.fedilab.android.helper.ThemeHelper; +import app.fedilab.android.ui.drawer.DomainBlockAdapter; +import app.fedilab.android.viewmodel.mastodon.AccountsVM; + + +public class FragmentMastodonDomainBlock extends Fragment { + + + private FragmentPaginationBinding binding; + private DomainBlockAdapter domainBlockAdapter; + private AccountsVM accountsVM; + private List domainList; + private boolean flagLoading; + private String max_id; + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + + binding = FragmentPaginationBinding.inflate(inflater, container, false); + binding.getRoot().setBackgroundColor(ThemeHelper.getBackgroundColor(requireActivity())); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + int c1 = getResources().getColor(R.color.cyanea_accent_reference); + binding.swipeContainer.setProgressBackgroundColorSchemeColor(getResources().getColor(R.color.cyanea_primary_reference)); + binding.swipeContainer.setColorSchemeColors( + c1, c1, c1 + ); + binding.loader.setVisibility(View.VISIBLE); + binding.recyclerView.setVisibility(View.GONE); + accountsVM = new ViewModelProvider(FragmentMastodonDomainBlock.this).get(AccountsVM.class); + flagLoading = false; + max_id = null; + domainList = new ArrayList<>(); + + binding.swipeContainer.setOnRefreshListener(() -> { + binding.swipeContainer.setRefreshing(true); + flagLoading = false; + max_id = null; + int size = domainList.size(); + domainList.clear(); + domainList = new ArrayList<>(); + domainBlockAdapter.notifyItemRangeRemoved(0, size); + router(); + }); + domainBlockAdapter = new DomainBlockAdapter(domainList); + LinearLayoutManager mLayoutManager = new LinearLayoutManager(requireActivity()); + binding.recyclerView.setLayoutManager(mLayoutManager); + binding.recyclerView.setAdapter(domainBlockAdapter); + binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + int firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition(); + if (dy > 0) { + int visibleItemCount = mLayoutManager.getChildCount(); + int totalItemCount = mLayoutManager.getItemCount(); + if (firstVisibleItem + visibleItemCount == totalItemCount) { + if (!flagLoading) { + flagLoading = true; + binding.loadingNextElements.setVisibility(View.VISIBLE); + router(); + } + } else { + binding.loadingNextElements.setVisibility(View.GONE); + } + } + + } + }); + router(); + } + + /** + * Router for timelines + */ + private void router() { + + if (max_id == null) { + accountsVM.getDomainBlocks(MainActivity.currentInstance, MainActivity.currentToken, null, null, null) + .observe(getViewLifecycleOwner(), this::initializeTagCommonView); + } else { + accountsVM.getDomainBlocks(MainActivity.currentInstance, MainActivity.currentToken, null, max_id, null) + .observe(getViewLifecycleOwner(), this::dealWithPagination); + } + } + + public void scrollToTop() { + binding.recyclerView.setAdapter(domainBlockAdapter); + } + + /** + * Intialize the view for domains + * + * @param domains List of {@link String} + */ + private void initializeTagCommonView(final Domains domains) { + flagLoading = false; + if (binding == null || !isAdded() || getActivity() == null) { + return; + } + binding.loader.setVisibility(View.GONE); + binding.noAction.setVisibility(View.GONE); + binding.swipeContainer.setRefreshing(false); + binding.swipeContainer.setOnRefreshListener(() -> { + binding.swipeContainer.setRefreshing(true); + flagLoading = false; + max_id = null; + router(); + }); + if (domains == null || domains.domains == null || domains.domains.size() == 0) { + binding.noAction.setVisibility(View.VISIBLE); + binding.noActionText.setText(R.string.no_accounts); + return; + } + binding.recyclerView.setVisibility(View.VISIBLE); + if (domainBlockAdapter != null && this.domainList != null) { + int size = this.domainList.size(); + this.domainList.clear(); + this.domainList = new ArrayList<>(); + domainBlockAdapter.notifyItemRangeRemoved(0, size); + } + + this.domainList = domains.domains; + domainBlockAdapter = new DomainBlockAdapter(this.domainList); + flagLoading = domains.pagination.max_id == null; + LinearLayoutManager mLayoutManager = new LinearLayoutManager(requireActivity()); + binding.recyclerView.setLayoutManager(mLayoutManager); + binding.recyclerView.setAdapter(domainBlockAdapter); + + max_id = domains.pagination.max_id; + binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + int firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition(); + if (dy > 0) { + int visibleItemCount = mLayoutManager.getChildCount(); + int totalItemCount = mLayoutManager.getItemCount(); + if (firstVisibleItem + visibleItemCount == totalItemCount) { + if (!flagLoading) { + flagLoading = true; + binding.loadingNextElements.setVisibility(View.VISIBLE); + router(); + } + } else { + binding.loadingNextElements.setVisibility(View.GONE); + } + } + + } + }); + } + + + private void dealWithPagination(Domains fetched_domains) { + flagLoading = false; + if (binding == null || !isAdded() || getActivity() == null) { + return; + } + binding.loadingNextElements.setVisibility(View.GONE); + if (domainList != null && fetched_domains != null && fetched_domains.domains != null) { + flagLoading = fetched_domains.pagination.max_id == null; + int startId = 0; + //There are some domains present in the timeline + if (domainList.size() > 0) { + startId = domainList.size(); + } + int position = domainList.size(); + domainList.addAll(fetched_domains.domains); + max_id = fetched_domains.pagination.max_id; + domainBlockAdapter.notifyItemRangeInserted(startId, fetched_domains.domains.size()); + } else { + flagLoading = true; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java index 627f6f5d..f3ba0615 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AccountsVM.java @@ -35,6 +35,7 @@ import app.fedilab.android.activities.MainActivity; import app.fedilab.android.client.endpoints.MastodonAccountsService; import app.fedilab.android.client.entities.api.Account; import app.fedilab.android.client.entities.api.Accounts; +import app.fedilab.android.client.entities.api.Domains; import app.fedilab.android.client.entities.api.FeaturedTag; import app.fedilab.android.client.entities.api.Field; import app.fedilab.android.client.entities.api.Filter; @@ -89,7 +90,7 @@ public class AccountsVM extends AndroidViewModel { private MutableLiveData> tagListMutableLiveData; private MutableLiveData preferencesMutableLiveData; private MutableLiveData tokenMutableLiveData; - private MutableLiveData> stringListMutableLiveData; + private MutableLiveData domainsMutableLiveData; private MutableLiveData reportMutableLiveData; public AccountsVM(@NonNull Application application) { @@ -1029,11 +1030,12 @@ public class AccountsVM extends AndroidViewModel { * View domains the user has blocked. * * @param limit Maximum number of results. Defaults to 40. - * @return {@link LiveData} containing a {@link List} of {@link String}s + * @return {@link LiveData} containing {@link Domains} */ - public LiveData> getDomainBlocks(@NonNull String instance, String token, String limit, String maxId, String sinceId) { - stringListMutableLiveData = new MutableLiveData<>(); + public LiveData getDomainBlocks(@NonNull String instance, String token, String limit, String maxId, String sinceId) { + domainsMutableLiveData = new MutableLiveData<>(); MastodonAccountsService mastodonAccountsService = init(instance); + Domains domains = new Domains(); new Thread(() -> { List stringList = null; Call> getDomainBlocksCall = mastodonAccountsService.getDomainBlocks(token, limit, maxId, sinceId); @@ -1041,18 +1043,18 @@ public class AccountsVM extends AndroidViewModel { try { Response> getDomainBlocksResponse = getDomainBlocksCall.execute(); if (getDomainBlocksResponse.isSuccessful()) { - stringList = getDomainBlocksResponse.body(); + domains.domains = getDomainBlocksResponse.body(); + domains.pagination = MastodonHelper.getPagination(getDomainBlocksResponse.headers()); } } catch (Exception e) { e.printStackTrace(); } } Handler mainHandler = new Handler(Looper.getMainLooper()); - List finalStringList = stringList; - Runnable myRunnable = () -> stringListMutableLiveData.setValue(finalStringList); + Runnable myRunnable = () -> domainsMutableLiveData.setValue(domains); mainHandler.post(myRunnable); }).start(); - return stringListMutableLiveData; + return domainsMutableLiveData; } /** diff --git a/app/src/main/res/layout/drawer_domain_block.xml b/app/src/main/res/layout/drawer_domain_block.xml new file mode 100644 index 00000000..0207a1a3 --- /dev/null +++ b/app/src/main/res/layout/drawer_domain_block.xml @@ -0,0 +1,53 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c332f3bf..9bf26a2f 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1907,4 +1907,7 @@ Suggestions Not interested Blocked domains + Unblock domain + You have not blocked domains + Are you sure to unblock %1$s? \ No newline at end of file From c729b091e8be873a7540f3ffa19d7b6e4100abd2 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 22 Nov 2022 15:04:04 +0100 Subject: [PATCH 17/61] Fix issue #512 - Drafts removed when no changes after opening them --- .../android/activities/ComposeActivity.java | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/activities/ComposeActivity.java b/app/src/main/java/app/fedilab/android/activities/ComposeActivity.java index 8db5bb45..78e2eb78 100644 --- a/app/src/main/java/app/fedilab/android/activities/ComposeActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/ComposeActivity.java @@ -110,6 +110,7 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana private StatusDraft statusDraft; private ComposeAdapter composeAdapter; private boolean promptSaveDraft; + private boolean restoredDraft; private final BroadcastReceiver imageReceiver = new BroadcastReceiver() { @@ -235,12 +236,14 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana AlertDialog alert = alt_bld.create(); alert.show(); } else { - try { - new StatusDraft(ComposeActivity.this).removeDraft(statusDraft); - finish(); - } catch (DBException e) { - e.printStackTrace(); + if (!restoredDraft) { + try { + new StatusDraft(ComposeActivity.this).removeDraft(statusDraft); + } catch (DBException e) { + e.printStackTrace(); + } } + finish(); } } else { finish(); @@ -446,6 +449,7 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana setContentView(binding.getRoot()); setSupportActionBar(binding.toolbar); promptSaveDraft = false; + restoredDraft = false; ActionBar actionBar = getSupportActionBar(); //Remove title if (actionBar != null) { @@ -576,6 +580,7 @@ public class ComposeActivity extends BaseActivity implements ComposeAdapter.Mana } }); } else if (statusDraft != null) {//Restore a draft with all messages + restoredDraft = true; if (statusDraft.statusReplyList != null) { statusList.addAll(statusDraft.statusReplyList); binding.recyclerView.addItemDecoration(new DividerDecorationSimple(ComposeActivity.this, statusList)); From e8f4fe981abd75091a247a84d87eab640a610507 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xos=C3=A9=20M?= Date: Tue, 22 Nov 2022 14:50:59 +0100 Subject: [PATCH 18/61] Translated using Weblate (Galician) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 89.6% (799 of 891 strings) Translated using Weblate (Galician) Currently translated at 82.6% (736 of 890 strings) Co-authored-by: Xosé M Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/ Translation: Fedilab/Strings --- app/src/main/res/values-gl/strings.xml | 280 +++++++++++++++++++++---- 1 file changed, 236 insertions(+), 44 deletions(-) diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index ad756220..508648c5 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -17,19 +17,19 @@ Contrasinal Correo-e Contas - Toots + Mensaxes Etiquetas Gardar Instancia Instancia: mastodon.social A funcionar coa conta %1$s Engadir unha conta - Copiouse ao portapapeis o contido do toot - Copiouse o URL do toot ao portapapéis + Copiose ao portapapeis o contido da mensaxe + Copiouse o URL da mensaxe ao portapapéis Cámara Elimnar todo Programar - Tamaño do texto e iconas + Tamaño do texto Seguinte Anterior Abrir con @@ -50,24 +50,24 @@ Traducir Inicio - Liña temporal local + Cronoloxía local Usuarias acaladas Usuarias bloqueadas Notificacións Solicitudes de seguimento Axustes Enviar un correo-e - Toots programados + Mensaxes programadas A información inferior sobre a usuaria podería estar incompleta. Incrustar emoji Polo momento a app non permite emojis personalizados. Tes a certeza de querer desconectar @%1$s@%2$s? - Sen toot que mostrar - Engadir este toot aos seus favoritos? - Eliminar este toot dos seus favoritos? - Promover este toot? - Deixar de promover o toot? + Non hai mensaxes a mostrar + Engadir esta publicación a favoritos\? + Eliminar esta mensaxe dos favoritos\? + Promover esta mensaxe\? + Retirar promoción da mensaxe\? Acalar Bloquear Informar @@ -122,8 +122,8 @@ Algo fallou ao engadir os medios! Eliminar este elemento? - O seu toot está baldeiro! - O toot foi enviado! + A mensaxe está baleira! + Enviaches a mensaxe! Contido sensible? Sen borradores! Escolla unha conta @@ -143,14 +143,15 @@ Sen conta que mostrar Sen petición de seguimento - Toots \n %1$s + Mensaxes +\n %1$s Seguindo \n %1$s Seguidoras \n %1$s Rexeitar - Sen toots programados! - Borrar o toot programado? - O toot foi programado! + Non hai mensaxes programadas! + Eliminar a mensaxe programada\? + A mensaxe foi programada! A data programada debe ser posterior a hora actual! O tempo de silencio debe ser maior de un minuto. @@ -176,10 +177,10 @@ A conta xa non está acalada! A conta foi seguida! A conta xa non está seguida! - O toot foi promovido! - O toot xa non está promovido! - Este toot foi engadido aos seus favoritos! - Este toot eliminouse dos favoritos! + A mensaxe foi promovida! + Retirácheslle a promoción á mensaxe! + A mensaxe foi engadida ás favoritas! + A mensaxe foi eliminada das favoritas! Vaia! Algo fallou! Algo fallou! A instancia non devolveu o código de autorización! O dominio da instancia non semella ser válido! @@ -188,7 +189,7 @@ Non se poden realizar accións Algo fallou ao traducir! - Número de toots por carga + Número de mensaxes por carga Desactivar avatares GIF Notificar cando alguén a segue Notificar cando alguén promove un dos seus toots @@ -267,10 +268,10 @@ Porto Conectar Contrasinal - Engadir detalles do toot ao compartir + Engadir detalles da mensaxe ao compartir Axude a app en Liberapay Hai un fallo na expresión regular! - Non se atoparon liñas temporais en esta instancia! + Non se atoparon cronoloxías nesta instancia! Seguir instancia Xa segue a esta instancia! Asociados @@ -282,13 +283,13 @@ Filtros Sen filtros que mostrar. Pode crear un pulsando no botón \"+\". Palabra chave ou frase - Liña temporal de inicio + Cronoloxía de inicio Liñas de tempo públicas Notificacións Conversas - Farase coincidir sen importar se é texto agochado ou con aviso de contido na mensaxe + Farase coincidir sen importar se é texto ten maiúsculas ou con aviso de contido na mensaxe Desbotar en lugar de ocultar - Os toots filtrados desaparecerán irreversiblemente, incluso si despois elimina o filtro + As mensaxes filtradas desaparecerán irreversiblemente, incluso se despois eliminas o filtro Cando a palabra chave é só alfanumérica, só se aplicará si coincide a palabra completa Palabra completa Filtrar contextos @@ -303,23 +304,25 @@ Novo favorito Nova mención Rematou a sondaxe - Respaldo de Toots + Copia de Apoio de mensaxes Novas publicacións Descarga de medios Escoller Tono Activar tramo horario - Desexa bloquear a %s\? + Queres bloquear a %s\? +\n +\nNon verás ningún contido dese dominio en ningunha cronoloxía pública nin nas notificacións. As túas seguidoras nese dominio serán eliminadas. Bloquear dominio O dominio foi bloqueado Obtendo estado remoto Instancia Peertube Utilizar Emoji One Información - Mostrar vista previa en todos os toots + Mostrar vista previa en tódalas mensaxes Copiouse o id de conta ao portapapeis! Cambiar o idioma - Cortar en varios os toots longos - Repartir os toots superiores a \'x\' liñas. Cero significa desactivado. + Recortar mensaxes longas + Cortar as mensaxes superiores a \'x\' liñas. Cero significa desactivado. Mostrar máis Mostrar menos A etiqueta xa existe! @@ -365,9 +368,9 @@ Categoría Descrición Compartir - Toots (Servidor) - Toots (Dispositivo) - Liñas temporais + Mensaxes (Servidor) + Mensaxes (Dispositivo) + Cronoloxías Interface Contactos Algo fallou ao seleccionar o ficheiro de respaldo! @@ -377,7 +380,7 @@ peticións http bloqueadas pola aplicación Lista de peticións bloquedas Enviar - Filtrar liña temporal con etiquetas + Filtrar cronoloxía con etiquetas Sen etiquetas Obter metadatos se o URL os comparte desde outras apps @@ -388,16 +391,16 @@ finaliza en %s Votar Rematou a sondaxe na que participou - Rematou unha sondaxe na que tooteou + Rematou unha enquisa que publicaches Categorías - Mover liña temporal - Agochar liña temporal - Ordear liñas temporais + Mover cronoloxía + Agochar cronoloxía + Xestionar cronoloxías Lista eliminada de xeito permanente Eliminouse o seguimento da instancia Eliminouse a etiqueta fixada Desfacer - As liñas temporais principais só poden ocultarse! + As cronoloxías principais só poden ocultarse! Marcar os medios sempre como sensibles Instancia GNU Incluír etiquetas nas respostas @@ -467,7 +470,7 @@ A aplicación precisa acceso a gravación de audio Mensaxe de voz Durante o tempo marcado, a app enviará notificacións. Pode revertir (silenciar) esta marxe coa roda da dereita. - Non se recortarán as vistas previas en liñas temporais + Non se recortarán as vistas previas nas cronoloxías Inserta automáticamente un salto de liña tras a mención para por en maiúscula a primeira letra Permitir as creadoras de contido compartir estados desde as súas fontes RSS Redactar @@ -518,12 +521,12 @@ Cambiar a cor do nome de usuaria enriba das mensaxes Cambiar a cor da cabeceira das mensaxes repetidas Publicacións - Cor de fondo das publicacións en liñas temporais + Cor de fondo das publicacións nas cronoloxías Restablecer cores Premendo aquí restableces os valores por omisión Restablecer Iconas - Cor das iconas inferiores nas liñas temporais + Cor das iconas inferiores nas cronoloxías Logo da instancia Editar perfil Tomar decisión @@ -570,4 +573,193 @@ Non se atopan distribuidores! Precisas un distribuidor para recibir notificacións push.\nAtoparás máis detalles en %1$s.\n\nTamén podes desactivar as notificacións push nos axustes para ignorar esa mensaxe. Elixe un distribuidor + Twitter + Usar interface alternativa para Twitter + Dominio da interface para Twitter + Instagram + Usar unha interface alternativa para Instagram + Dominio da interface para Instagram + Reddit + Funciona! + O problema non axeita a ningunha das categorías + Outra cousa + Acalar a %1$s + Bloquear a %1$s + Engadiuse a mensaxe aos marcadores! + Confirma a retirada do seguimento + Elixe se a base do decorado debe ser clara ou escura + Menú barra superior + Outro + Engadir estado + Conta tipo bot + Base do decorado + Permitir crear o teu propio decorado + Decorados da comunidade + Tipo de enquisa: + Elixe un decorado que foi creado por persoas da comunidade + Duración da enquisa: + Tamaño das iconas + Visibilidade por defecto para as mensaxes: + Retirouse a mensaxe dos marcadores! + Enviando mensaxe %d/%d + Cres que non segue determinada regra + Mencións + Favoritas + Tes a certeza de querer eliminar tódalas notificacións\? Non ten volta atrás. + Interaccións + Bloqueado + Gardar cambios + Agochar contido < + Deter gravación + Denuncia de %1$s + Cóntanos cal é o problema coa publicación + Elixe a mellor coincidencia + Ligazóns maliciosas, engano, ou respostas repetitivas + Non é algo que queira ver + É spam + Hai algunha publicación que apoie esta denuncia\? + Que regra se incumpriu\? + Promocións + Resultados da enquisa + Actualizacións das persoas + Segue + Engadir filtro + Engadir Campo + Desbloqueado + Programado + Conta non descubrible + Eliminar campo + Elixe un decorado + Máis accións + Número de contas por carga + Número de notificacións por carga + Música + O campo non pode estar baleiro! + YouTube + Usar interface alternativa para YouTube + Domino da interface para YouTube + Usar interface alternativa para Reddit + Dominio da interface para Reddit + Tipo de notificacións + Elixe o tipo de notificacións + Desactivar notificacións + Menú inferior + Última actividade + Personalizar cronoloxías + Enviouse a mensaxe! + Tipos de notificación a mostrar + Mostar sempre o botón do marcador + Mostrar + Importáronse correctamente os axustes + Exportáronse os axustes + Exportáronse correctamente os axustes + Non semella ser unha instancia válida! + Promovida por + Favorecida por + Só para seguidoras + Ex.: Contido sensible + Publicando mensaxe… + Non funciona! + Continuar + Personalizar + Uptime: %,.2f %% + Mostrar contido > + Non me gusta + Viola as regras do servidor + Non queres ver isto\? + Aquí tes opcións para controlar o que ves en Mastodon: + Deixa de seguir a %1$s + Estás seguindo esta conta. Para non ver as súas publicacións na cronoloxía de inicio, deixa de seguila. + Non verás as súas publicacións. Poderá seguirte a ti e ver as túas publicacións e non saberá que a tes acalada. + Non verás as súas publicacións. Non poderá ver as túas publicacións nin seguirte. Poderá comprobar que a tes bloqueada. + Elixe todo o que aplique + version: %s +\n %s usuarias - %s estados + Comprobado o: %s + A conta está noutro servidor. Enviamos alí unha copia anónimizada da denuncia\? + Enviar a %1$s + Hai algo máis que debamos saber\? + Comentarios adicionais + Únete ao fediverso + Enviouse a denuncia! + Non tes unha conta\? + Ola! Convidámoste a unirte ao Fediverso. + \"Mastodon non é un sitio web único como Twitter ou Facebook, é unha rede de miles de comunidades xestionadas por diferentes organizacións e persoas que proporciona unha experiencia do internet social conxunta.\" + Limpar tódalas notificacións + Marcar tódalas notificacións como lidas + Mostrar tódalas categorías + O nome da lista non é válido! + Non hai contas nesta lista! + Tes a certeza de querer eliminar este campo\? + Actualizouse o perfil! + Son moderadora + Localización + "Tamén a favoreceron: " + Tamén a promocionaron: + Durante este marco temporal + Son das notificacións + Eliminar estado + Cronoloxías nunha lista + Ao activalas, tódalas cronoloxías fixadas monstraranse nun menú despregable + Ao activala, a app só terá unha única barra para as cronoloxías + Mensaxes na caché para Inicio + Obter máis mensaxes… + Anuncio - %1$s - %2$s + Limpar caché + Abrir mensaxe orixinal + As conoloxías irán á caché para que a aplicación sexa máis rápida. + Responder + Opcións de presentación + A app non puido obter un token + Dominio + Establece o número máx. de caracteres + Notas da publicación + Non se puido subir o multimedia! + Establecer a demora entre obtencións + Mostra contadores nas mensaxes + Cargar miniaturas multimedia + Mostrar multimedia + Mensaxe na caché + Obter notificacións + Barra única + Usar caché + Mostrar contadores + Abrir borrador + Tempo entre notificacións + Estado + Resolta + Oval + A miña instancia + Baleirar caché + Mensaxes na caché para outras cronoloxías + Mensaxes gardadas en borradores + A miña app + Tes a certeza de saír sen gardar a imaxe\? + Toca aquí para actualizar a enquisa + Aprobado + Tamaño dos ficheiros da caché + Aprobar + Orixe da conta denunciada + Máis recente + Forma + Modo Borrar + Tes a certeza de querer baleirar a caché\? Se tes borradores que inclúen multimedia, estes perderanse. + A miña conta + Montrará icona con contador para as novas mensaxes nas cronoloxías da lapela + Cargar axustes exportados + Xestor de notificacións + Importar axustes + Mostrar cronoloxías + Permiso non outorgado! + Agrupar notificacións + Ao activarlo, a app xuntará tódalas notificacións relacionadas + Lembrar posición nas cronoloxías + Equipo + Filtro + Rectángulo + Usar o idioma por defecto no sistema + Obter notificacións cada: + Exportar axustes + Liña + Idioma das mensaxes \ No newline at end of file From 969a35bac2f15544f5ef92da61842e138f0ce268 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Tue, 22 Nov 2022 14:51:00 +0100 Subject: [PATCH 19/61] Translated using Weblate (Turkish) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (891 of 891 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (890 of 890 strings) Co-authored-by: Oğuz Ersen Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/ Translation: Fedilab/Strings --- app/src/main/res/values-tr/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 4fbc3c96..fb022287 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -857,4 +857,7 @@ Zaman çizelgesini sil Rapor gönderildi Kaydoldu + Öneriler + İlgilenmiyor + Engellenen etki alanları \ No newline at end of file From 047086f5e50ba5a3232be5c75c0f4d8ee931dd0e Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 22 Nov 2022 15:27:13 +0100 Subject: [PATCH 20/61] Fix issue #510 - Add support for gemini links --- .../app/fedilab/android/helper/Helper.java | 2 ++ .../android/helper/SpannableHelper.java | 33 +++++++++++++++++++ 2 files changed, 35 insertions(+) diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java index 5277dbb7..7e45b21f 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -334,6 +334,8 @@ public class Helper { public static final Pattern bibliogramPattern = Pattern.compile("(m\\.|www\\.)?instagram.com(/p/[\\w-/]+)"); public static final Pattern libredditPattern = Pattern.compile("(www\\.|m\\.)?(reddit\\.com|preview\\.redd\\.it|i\\.redd\\.it|redd\\.it)/(((?!([\"'<])).)*)"); public static final Pattern ouichesPattern = Pattern.compile("https?://ouich\\.es/tag/(\\w+)"); + + public static final Pattern geminiPattern = Pattern.compile("(gemini://.*)\\b"); public static final Pattern xmppPattern = Pattern.compile("xmpp:[-a-zA-Z0-9+$&@#/%?=~_|!:,.;]*[-a-zA-Z0-9+&@#/%=~_|]"); public static final Pattern peertubePattern = Pattern.compile("(https?://([\\da-z.-]+\\.[a-z.]{2,10}))/videos/watch/(\\w{8}-\\w{4}-\\w{4}-\\w{4}-\\w{12})$"); public static final Pattern mediumPattern = Pattern.compile("([\\w@-]*)?\\.?medium.com/@?([/\\w-]+)"); diff --git a/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java b/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java index cdf65612..6418f1dc 100644 --- a/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java @@ -147,6 +147,7 @@ public class SpannableHelper { linkify(context, content, urlDetails); linkifyURL(context, content, urlDetails); emails(context, content); + gemini(context, content); replaceQuoteSpans(context, content); } else { content = new SpannableStringBuilder(text); @@ -668,6 +669,38 @@ public class SpannableHelper { } } + private static void gemini(Context context, Spannable content) { + // --- For all patterns defined in Helper class --- + Pattern pattern = Helper.geminiPattern; + Matcher matcher = pattern.matcher(content); + while (matcher.find()) { + int matchStart = matcher.start(); + int matchEnd = matcher.end(); + String geminiLink = content.toString().substring(matchStart, matchEnd); + if (matchStart >= 0 && matchEnd <= content.toString().length() && matchEnd >= matchStart) { + ClickableSpan[] clickableSpans = content.getSpans(matchStart, matchEnd, ClickableSpan.class); + if (clickableSpans != null) { + for (ClickableSpan clickableSpan : clickableSpans) { + content.removeSpan(clickableSpan); + } + } + content.removeSpan(clickableSpans); + content.setSpan(new ClickableSpan() { + @Override + public void onClick(@NonNull View textView) { + Helper.openBrowser(context, geminiLink); + } + + @Override + public void updateDrawState(@NonNull TextPaint ds) { + super.updateDrawState(ds); + ds.setUnderlineText(false); + ds.setColor(linkColor); + } + }, matchStart, matchEnd, Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + } + } + } private static void emails(Context context, Spannable content) { // --- For all patterns defined in Helper class --- From 9d4a576bc1d7cfd9d3165bfe67c3aedb7cfe1b93 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 22 Nov 2022 16:42:05 +0100 Subject: [PATCH 21/61] Fix automatically hide after x seconds --- .../java/app/fedilab/android/ui/drawer/StatusAdapter.java | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java index bc36b5ef..acfd5c03 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java @@ -1240,8 +1240,10 @@ public class StatusAdapter extends RecyclerView.Adapter layoutMediaBinding.media.setOnClickListener(v -> { if (statusToDeal.isMediaObfuscated && mediaObfuscated(statusToDeal) && !expand_media) { statusToDeal.isMediaObfuscated = false; - adapter.notifyItemChanged(holder.getBindingAdapterPosition()); + int position = holder.getBindingAdapterPosition(); + adapter.notifyItemChanged(position); final int timeout = sharedpreferences.getInt(context.getString(R.string.SET_NSFW_TIMEOUT), 5); + if (timeout > 0) { new CountDownTimer((timeout * 1000L), 1000) { public void onTick(long millisUntilFinished) { @@ -1249,7 +1251,7 @@ public class StatusAdapter extends RecyclerView.Adapter public void onFinish() { status.isMediaObfuscated = true; - adapter.notifyItemChanged(holder.getBindingAdapterPosition()); + adapter.notifyItemChanged(position); } }.start(); } From 900e78f5e876f9ec5eb2aca916814f51d54be0d5 Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 22 Nov 2022 17:14:00 +0100 Subject: [PATCH 22/61] Fix issue #514 - Add about and privacy links into About the instance. --- .../android/activities/InstanceActivity.java | 22 ++++++++ app/src/main/res/layout/activity_instance.xml | 51 ++++++++++++++----- app/src/main/res/values/strings.xml | 1 + 3 files changed, 61 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/activities/InstanceActivity.java b/app/src/main/java/app/fedilab/android/activities/InstanceActivity.java index 9f515608..0f8e3a91 100644 --- a/app/src/main/java/app/fedilab/android/activities/InstanceActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/InstanceActivity.java @@ -22,12 +22,17 @@ import android.net.Uri; import android.os.Build; import android.os.Bundle; import android.text.Html; +import android.text.SpannableString; +import android.text.Spanned; +import android.text.style.ForegroundColorSpan; +import android.text.style.UnderlineSpan; import android.view.MenuItem; import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import androidx.core.content.ContextCompat; import androidx.lifecycle.ViewModelProvider; import androidx.preference.PreferenceManager; @@ -39,6 +44,7 @@ import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; import app.fedilab.android.client.entities.api.Instance; import app.fedilab.android.databinding.ActivityInstanceBinding; +import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.ThemeHelper; import app.fedilab.android.viewmodel.mastodon.InstancesVM; @@ -61,6 +67,22 @@ public class InstanceActivity extends BaseActivity { if (getSupportActionBar() != null) getSupportActionBar().hide(); SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(InstanceActivity.this); + + + final SpannableString contentAbout = new SpannableString(getString(R.string.action_about_instance)); + contentAbout.setSpan(new UnderlineSpan(), 0, contentAbout.length(), 0); + contentAbout.setSpan(new ForegroundColorSpan(ContextCompat.getColor(InstanceActivity.this, R.color.cyanea_accent_reference)), 0, contentAbout.length(), + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + binding.tos.setText(contentAbout); + + final SpannableString contentPrivacy = new SpannableString(getString(R.string.action_privacy_policy)); + contentPrivacy.setSpan(new UnderlineSpan(), 0, contentPrivacy.length(), 0); + contentPrivacy.setSpan(new ForegroundColorSpan(ContextCompat.getColor(InstanceActivity.this, R.color.cyanea_accent_reference)), 0, contentPrivacy.length(), + Spanned.SPAN_INCLUSIVE_EXCLUSIVE); + binding.privacy.setText(contentPrivacy); + + binding.tos.setOnClickListener(v -> Helper.openBrowser(InstanceActivity.this, "https://" + MainActivity.currentInstance + "/about")); + binding.privacy.setOnClickListener(v -> Helper.openBrowser(InstanceActivity.this, "https://" + MainActivity.currentInstance + "/privacy-policy")); binding.close.setOnClickListener( view -> { diff --git a/app/src/main/res/layout/activity_instance.xml b/app/src/main/res/layout/activity_instance.xml index d33a01dc..d0a4aa4c 100644 --- a/app/src/main/res/layout/activity_instance.xml +++ b/app/src/main/res/layout/activity_instance.xml @@ -23,7 +23,7 @@ @@ -36,7 +36,7 @@ @@ -118,7 +118,7 @@ - + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9bf26a2f..0eec9af9 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1910,4 +1910,5 @@ Unblock domain You have not blocked domains Are you sure to unblock %1$s? + Privacy policy \ No newline at end of file From 48c9f1e48c07eec1beae2597fa5d257bff9b19ac Mon Sep 17 00:00:00 2001 From: Thomas Date: Tue, 22 Nov 2022 18:14:16 +0100 Subject: [PATCH 23/61] Release 3.7.5 --- app/build.gradle | 4 ++-- app/src/main/assets/release_notes/notes.json | 5 +++++ .../fastlane/metadata/android/en/changelogs/43.txt | 13 +++++++++++++ 3 files changed, 20 insertions(+), 2 deletions(-) create mode 100644 src/fdroid/fastlane/metadata/android/en/changelogs/43.txt diff --git a/app/build.gradle b/app/build.gradle index 0b28e4e7..9b970279 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { defaultConfig { minSdk 21 targetSdk 31 - versionCode 431 - versionName "3.7.4" + versionCode 432 + versionName "3.7.5" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } flavorDimensions "default" diff --git a/app/src/main/assets/release_notes/notes.json b/app/src/main/assets/release_notes/notes.json index 9d9f8e1f..8c0b3433 100644 --- a/app/src/main/assets/release_notes/notes.json +++ b/app/src/main/assets/release_notes/notes.json @@ -1,4 +1,9 @@ [ + { + "version": "3.7.5", + "code": "432", + "note": "Added:\n- List of blocked domains (allow to unblock)\n- Support gemini links\n- Suggested followers\n\nChanged:\n- Allow search term to be edited\n\nFixed:\n- Drafts deleted with no warning\n- App crashes when proxy is set\n- Filter not synced after being edited\n- Some crashes" + }, { "version": "3.7.4", "code": "431", diff --git a/src/fdroid/fastlane/metadata/android/en/changelogs/43.txt b/src/fdroid/fastlane/metadata/android/en/changelogs/43.txt new file mode 100644 index 00000000..d9a059a9 --- /dev/null +++ b/src/fdroid/fastlane/metadata/android/en/changelogs/43.txt @@ -0,0 +1,13 @@ +Added: +- List of blocked domains (allow to unblock) +- Support gemini links +- Suggested followers + +Changed: +- Allow search term to be edited + +Fixed: +- Drafts deleted with no warning +- App crashes when proxy is set +- Filter not synced after being edited +- Some crashes \ No newline at end of file From a14bebc7215c91bc1ad31a57c6a6048126dbecb4 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Nov 2022 09:08:08 +0100 Subject: [PATCH 24/61] Release 3.7.5 --- .../fastlane/metadata/android/en/changelogs/{43.txt => 432.txt} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename src/fdroid/fastlane/metadata/android/en/changelogs/{43.txt => 432.txt} (100%) diff --git a/src/fdroid/fastlane/metadata/android/en/changelogs/43.txt b/src/fdroid/fastlane/metadata/android/en/changelogs/432.txt similarity index 100% rename from src/fdroid/fastlane/metadata/android/en/changelogs/43.txt rename to src/fdroid/fastlane/metadata/android/en/changelogs/432.txt From a389681b864151faf55f78c0733c1231f7cca31b Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Nov 2022 09:09:28 +0100 Subject: [PATCH 25/61] Fix issue #518 - Remove serif font --- app/src/main/res/layout/activity_drafts.xml | 3 +-- app/src/main/res/layout/activity_filters.xml | 3 +-- app/src/main/res/layout/activity_followed_tags.xml | 1 - app/src/main/res/layout/activity_list.xml | 1 - app/src/main/res/layout/activity_status_info.xml | 3 +-- app/src/main/res/layout/fragment_pagination.xml | 3 +-- app/src/main/res/layout/fragment_scheduled.xml | 3 +-- 7 files changed, 5 insertions(+), 12 deletions(-) diff --git a/app/src/main/res/layout/activity_drafts.xml b/app/src/main/res/layout/activity_drafts.xml index 85563ae9..64a0f574 100644 --- a/app/src/main/res/layout/activity_drafts.xml +++ b/app/src/main/res/layout/activity_drafts.xml @@ -87,8 +87,7 @@ android:gravity="center" android:padding="10dp" android:text="@string/no_draft" - android:textSize="20sp" - android:typeface="serif" /> + android:textSize="20sp" /> diff --git a/app/src/main/res/layout/activity_filters.xml b/app/src/main/res/layout/activity_filters.xml index 0b151685..9640dae3 100644 --- a/app/src/main/res/layout/activity_filters.xml +++ b/app/src/main/res/layout/activity_filters.xml @@ -26,8 +26,7 @@ android:gravity="center" android:padding="10dp" android:text="@string/action_filters_empty_content" - android:textSize="20sp" - android:typeface="serif" /> + android:textSize="20sp" /> + android:textSize="20sp" /> diff --git a/app/src/main/res/layout/fragment_pagination.xml b/app/src/main/res/layout/fragment_pagination.xml index ad96c567..2cf598f4 100644 --- a/app/src/main/res/layout/fragment_pagination.xml +++ b/app/src/main/res/layout/fragment_pagination.xml @@ -55,8 +55,7 @@ android:gravity="center" android:padding="10dp" android:text="@string/no_status" - android:textSize="20sp" - android:typeface="serif" /> + android:textSize="20sp" /> diff --git a/app/src/main/res/layout/fragment_scheduled.xml b/app/src/main/res/layout/fragment_scheduled.xml index 4a6a804b..a107407b 100644 --- a/app/src/main/res/layout/fragment_scheduled.xml +++ b/app/src/main/res/layout/fragment_scheduled.xml @@ -46,8 +46,7 @@ android:gravity="center" android:padding="10dp" android:text="@string/no_scheduled_toots" - android:textSize="20sp" - android:typeface="serif" /> + android:textSize="20sp" /> From 86ce8f451df29d939a4615f9bdc27b7c607eb442 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Wed, 23 Nov 2022 09:08:26 +0100 Subject: [PATCH 26/61] Translated using Weblate (Turkish) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (895 of 895 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (894 of 894 strings) Co-authored-by: Oğuz Ersen Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/ Translation: Fedilab/Strings --- app/src/main/res/values-tr/strings.xml | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index fb022287..1c5bdabf 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -860,4 +860,8 @@ Öneriler İlgilenmiyor Engellenen etki alanları + Etki alanlarını engellemediniz + Etki alanının engelini kaldır + %1$s engellemesini kaldırmak istediğinizden emin misiniz\? + Gizlilik politikası \ No newline at end of file From 2b3590353a6a66a87265f18644e0130fc6749d05 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Luk=C3=A1=C5=A1=20Jel=C3=ADnek?= Date: Wed, 23 Nov 2022 09:08:27 +0100 Subject: [PATCH 27/61] Translated using Weblate (Czech) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 85.4% (765 of 895 strings) Co-authored-by: Lukáš Jelínek Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/cs/ Translation: Fedilab/Strings --- app/src/main/res/values-cs/strings.xml | 71 +++++++++++++++----------- 1 file changed, 41 insertions(+), 30 deletions(-) diff --git a/app/src/main/res/values-cs/strings.xml b/app/src/main/res/values-cs/strings.xml index 140f11a3..ad88ce9a 100644 --- a/app/src/main/res/values-cs/strings.xml +++ b/app/src/main/res/values-cs/strings.xml @@ -17,19 +17,19 @@ Heslo Email Účty - Tooty + Zprávy Štítky Uložit Instance Instance: mastodon.social Nyní používáte účet %1$s Přidat účet - Obsah tootu byl zkopírován do schránky - Adresa tootu byla zkopírována do schránky + Obsah zprávy byl zkopírován do schránky + Adresa zprávy byla zkopírována do schránky Fotoaparát Smazat všechno Naplánovat - Velikost textu a ikony + Velikost textu Další Předchozí Otevřít čím @@ -57,17 +57,17 @@ Žádost o sledování Nastavení Poslat e-mail - Naplánované tooty + Naplánované zprávy Níže uvedené informace mohou popisovat uživatelský profil neúplně. Vložit smajlík Aplikace prozatím nenačetla uživatelské smajlíky. Are you sure you want to logout @%1$s@%2$s? - Žádné tooty k zobrazení - Přidat tento toot k oblíbeným? - Odstranit tento toot z oblíbených? - Boostnout tento toot? - Zrušit boost? + Žádné zprávy k zobrazení + Přidat tuto zprávu k oblíbeným\? + Odstranit tuto zprávu z oblíbených\? + Boostnout tuto zprávu\? + Zrušit boost\? Ztlumit Blokovat Nahlásit @@ -130,8 +130,8 @@ Nastala chyba při výběru média! Smazat médium? - Váš toot je prázdný! - Toot byl odeslán! + Vaše zpráva je prázdná! + Zpráva byla odeslána! Citlivý obsah? Žádné koncepty! Vyberte účet @@ -151,14 +151,15 @@ Žádný účet k zobrazení Není požadavek ke sledování - Tooty \n %1$s + Zprávy +\n %1$s Sleduji \n %1$s Sledující \n %1$s Odmítnout - Žádné naplánované tooty k zobrazení! - Odstranit naplánovaný toot? - Toot byl naplánován! + Žádné naplánované zprávy k zobrazení! + Odstranit naplánovanou zprávu\? + Zpráva byla naplánována! Plánované datum musí být vyšší než aktuální hodina! Časový interval pro ztlumení musí být vyšší než jedna minuta. @@ -184,10 +185,10 @@ Účet není nadále ztlumen! Účet je sledován! Účet není nadále sledován! - Toot byl boostnut! - Toot již není boostnut! - Toot byl přidán k oblíbeným! - Toot byl odstraněn z oblíbených! + Zpráva byla boostnuta! + Zpráva již není boostnuta! + Zpráva byla přidána k oblíbeným! + Zpráva byla odstraněna z oblíbených! Oops! Došlo k chybě! Došlo k chybě! Instance nevrátila autorizační kód! Tato doména není platná! @@ -196,7 +197,7 @@ Nelze vykonat akci Při překladu došlo k chybě! - Počet tootů pro jedno nahrání + Počet zpráv pro jedno nahrání Zakázat GIF avatary Oznámení v případě sledování Oznámení v případě boostnutí vašeho tootu @@ -275,7 +276,7 @@ Port Přihlašovací jméno Heslo - Přidat podrobnosti tootu při sdílení + Přidat podrobnosti zprávy při sdílení Podpořit aplikaci na Liberapay Chyba v regulárním výrazu! Časová osa nenalezena na této instanci! @@ -296,7 +297,7 @@ Konverzace Velikost písmen ani varování o obsahu nebudou brána v potaz Zahodit místo skrytí - Filtrované tooty zmizí nezvratně i v případě, že je filtr později odstraněn + Filtrované zprávy zmizí nezvratně i v případě, že je filtr později odstraněn V případě, že klíčové slovo nebo fráze je pouze alfanumerické, filtr se uplatní pouze pokud odpovídá celému slovu Celé slovo Kontext filtru @@ -311,7 +312,7 @@ Nové oblíbení Nová zmínka Anketa skončila - Záloha tootů + Záloha zpráv New posts Stahování médií Vybrat tón @@ -323,11 +324,11 @@ Peertube instance Použít Emoji One Informace - Zobrazit náhled ve všech tootech + Zobrazit náhled ve všech zprávách Identifikátor účtu byl zkopírován do schránky! Změna jazyka - Ořezat dlouhé tooty - Ořezat tooty delší než \'x\' řádků. 0 znamená vypnuto. + Ořezat dlouhé zprávy + Ořezat zprávy delší než \'x\' řádků. 0 znamená vypnuto. Zobrazit více Zobrazit méně Štítek již existuje! @@ -373,8 +374,8 @@ Kategorie Popis Sdílet - Tooty (Server) - Tooty (zařízení) + Zprávy (Server) + Zprávy (Zařízení) Časové osi Rozhraní Kontakty @@ -400,7 +401,7 @@ Kategorie Přesunout časovou osu Skrýt časovou osu - Změnit pořadí časových os + Spravovat časové osy Seznam trvale smazán Sledovaná instance odstraněna Připnuté značky odstraněny @@ -725,4 +726,14 @@ Oprávnění nebylo uděleno! Opravdu chcete vymazat cache\? Pokud máte koncepty s médii, budou přiložená média ztracena. Časové osy v seznamu + Odesílání zprávy… + Odesílání zprávy %d/%d + Běží! + Neběží! + verze: %s +\n %s uživatelů - %s statusů + Odebrat status + Vybrat nejlepší shodu + Zkontrolováno: %s + Přidat status \ No newline at end of file From 14135ea5efb68759ddd60c523790f14634485d9b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xos=C3=A9=20M?= Date: Wed, 23 Nov 2022 09:08:27 +0100 Subject: [PATCH 28/61] Translated using Weblate (Galician) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (895 of 895 strings) Co-authored-by: Xosé M Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/ Translation: Fedilab/Strings --- app/src/main/res/values-gl/strings.xml | 116 +++++++++++++++++++++++-- 1 file changed, 107 insertions(+), 9 deletions(-) diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 508648c5..6fdd9ba0 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -4,7 +4,7 @@ Acerca da instancia Intimidade Caché - Desconectar + Pechar sesión Pechar Si @@ -61,7 +61,7 @@ A información inferior sobre a usuaria podería estar incompleta. Incrustar emoji Polo momento a app non permite emojis personalizados. - Tes a certeza de querer desconectar @%1$s@%2$s? + Tes a certeza de querer pechar a sesión @%1$s@%2$s\? Non hai mensaxes a mostrar Engadir esta publicación a favoritos\? @@ -266,7 +266,7 @@ Activar proxy? Host Porto - Conectar + Acceder Contrasinal Engadir detalles da mensaxe ao compartir Axude a app en Liberapay @@ -374,7 +374,7 @@ Interface Contactos Algo fallou ao seleccionar o ficheiro de respaldo! - Desconectar conta + Pechar sesión da conta Todo Copiar ligazón peticións http bloqueadas pola aplicación @@ -445,11 +445,13 @@ O contrasinal debe conter ao menos 8 caracteres O nome de usuaria debería ter só letras, números e guión baixo Conta creada! - Xa ten unha conta!\n\n - Lembre validar o correo-e nas seguintes48 horas.\n\n - Xa pode conectar coa súa conta escribindo %1$s no primeiro campo e pulsando Conectar.\n\n - Importante: se a súa instancia require validación, recibirá un correo unha vez sexa validada! - + Xa tes unha conta! +\n +\n Lembra validar o email nas seguintes 48 horas. +\n +\n Xa podes acceder coa túa conta escribindo %1$s no primeiro campo e premendo Acceder. +\n +\n Importante: se a túa instancia require validación, recibirás un correo unha vez sexa validada! ¿Gardar mensaxe en borradores? Administración Informes @@ -762,4 +764,100 @@ Exportar axustes Liña Idioma das mensaxes + Debes reiniciar a app para aplicar os cambios. + Sen confirmar + Marcar como resolta + A non pode atopar os datos remotos! + Enviou unha denuncia + Política de privacidade + Dominios bloqueados + Editada o %1$s + Desbloquear dominio + Non tes dominios bloqueados + Tes a certeza de desbloquear %1$s\? + Elixe un logo + Cambiar logo + Cambia o logo da app no teu dispositivo + Fixar mensaxe + Traducir mensaxes + Forzar a tradución a un idioma concreto. Elixe o primeiro valor para restablecer aos axustes do sistema + Creada o %1$s + Indentación máx. nos fíos + Respostas non listadas + Suxestións + Non interesada + Só aplica a respostas \"públicas\". Ao activalo, as respostas terán automáticamente visibilidade \"non listada\" no lugar de \"pública\" + A mensaxe xa non está fixada! + Fixouse a mensaxe + Eliminadas da caché as notificacións. + Estado do email + Estado da sesión + Asignarma + Retirar asignación + Conta con avisos + Retirada a suspensión da conta + Conta suspendida + Conta acalada + Denunciar + Desafixar mensaxe + Uniuse + IP recente + Permitir + Avisar + Notificar á usuaria por email + Aviso personalizado + Estados denunciados + Acalada + Usuaria + Moderadora + Administradora + Confirmada + Macar como non resolta + Conta rexeitada + Conta aprobada + Conta reactivada + Conta desactivada + Conta restablecida + Estado + Reiniciar a app\? + Reiniciar + Idiomas no selector + Permite reducir a lista de idiomas no selector ao escribir unha mensaxe. + Non segues ningún cancelo! + Non seguir cancelo + Queres deixar de seguir este cancelo\? + Non seguir + Seguir un cancelo + Escribe o cancelo a seguir + O nome do cancelo non é válido! + Cancelos seguidos + Seguir cancelo + Mostrar multimedia nas notificacións + Mostrarase o multimedia nas notificacións de RT e FAV + Editar lista + Perfís + A túa instancia non ten soporte para esta función! + Mira os temas en voga na instancia + Editou unha mensaxe + Actualizacións + Editar mensaxe + %1$s editou %2$s + Historial da mensaxe + Acción do filtro + Elixe que acción debe realizar o filtro ao atopar unha concordancia + Agochar cun aviso + Agochar completamente + Agochar o contido filtrado detrás dun aviso que menciona o título do filtro + Agochar completamente o contido filtrado, como se non tivese existido + Inicio e listas + Título + Palabra ou frase a filtrar + Eliminar palabra + Engadir palabra + Filtrado %1$s + Mostrar igualmente + Mostrar perfil remoto + A app non puido engadir a conta á lista! + Eliminar cronoloxía + Rexistrada \ No newline at end of file From 0e0fb6e13c964d0202c31cef3c8fd8b770385a88 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Nov 2022 10:52:09 +0100 Subject: [PATCH 29/61] Fix crashes from bug reports. --- .../app/fedilab/android/BaseMainActivity.java | 35 +++++++++++++------ .../android/activities/FilterActivity.java | 4 +++ .../app/fedilab/android/helper/Helper.java | 2 +- .../android/ui/drawer/FilterAdapter.java | 4 +-- .../android/ui/drawer/StatusAdapter.java | 18 ++++++++-- 5 files changed, 46 insertions(+), 17 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/BaseMainActivity.java index 3bc50a74..a353434e 100644 --- a/app/src/main/java/app/fedilab/android/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/BaseMainActivity.java @@ -586,7 +586,7 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt } Handler mainHandler = new Handler(Looper.getMainLooper()); Runnable myRunnable = () -> { - if (currentAccount == null) { + if (currentAccount == null || currentAccount.mastodon_account == null) { //It is not, the user is redirected to the login page Intent myIntent = new Intent(BaseMainActivity.this, LoginActivity.class); startActivity(myIntent); @@ -701,16 +701,18 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt new ViewModelProvider(BaseMainActivity.this).get(AccountsVM.class).getConnectedAccount(currentInstance, currentToken) .observe(BaseMainActivity.this, mastodonAccount -> { //Initialize static var - currentAccount.mastodon_account = mastodonAccount; - displayReleaseNotesIfNeeded(BaseMainActivity.this, false); - new Thread(() -> { - try { - //Update account in db - new Account(BaseMainActivity.this).insertOrUpdate(currentAccount); - } catch (DBException e) { - e.printStackTrace(); - } - }).start(); + if (mastodonAccount != null) { + currentAccount.mastodon_account = mastodonAccount; + displayReleaseNotesIfNeeded(BaseMainActivity.this, false); + new Thread(() -> { + try { + //Update account in db + new Account(BaseMainActivity.this).insertOrUpdate(currentAccount); + } catch (DBException e) { + e.printStackTrace(); + } + }).start(); + } }); }; @@ -1035,6 +1037,17 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt Matcher matcherLink = null; matcherLink = link.matcher(url); if (matcherLink.find()) { + if (currentAccount == null) { + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(BaseMainActivity.this); + if (currentToken == null || currentToken.trim().isEmpty()) { + currentToken = sharedpreferences.getString(Helper.PREF_USER_TOKEN, null); + } + try { + currentAccount = new Account(BaseMainActivity.this).getConnectedAccount(); + } catch (DBException e) { + e.printStackTrace(); + } + } if (matcherLink.group(3) != null && Objects.requireNonNull(matcherLink.group(3)).length() > 0) { //It's a toot CrossActionHelper.fetchRemoteStatus(BaseMainActivity.this, currentAccount, url, new CrossActionHelper.Callback() { @Override diff --git a/app/src/main/java/app/fedilab/android/activities/FilterActivity.java b/app/src/main/java/app/fedilab/android/activities/FilterActivity.java index 474d89a7..ed8d616a 100644 --- a/app/src/main/java/app/fedilab/android/activities/FilterActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/FilterActivity.java @@ -277,7 +277,11 @@ public class FilterActivity extends BaseActivity implements FilterAdapter.Delete binding.addFilter.setOnClickListener(v -> addEditFilter(FilterActivity.this, null, filter -> { if (filter != null) { + if (MainActivity.mainFilters == null) { + MainActivity.mainFilters = new ArrayList<>(); + } filterList.add(0, filter); + MainActivity.mainFilters.add(filter); if (filterAdapter != null) { filterAdapter.notifyItemInserted(0); } else { diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java index 7e45b21f..aee215dd 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -1029,7 +1029,7 @@ public class Helper { final Activity activity = (Activity) context; return !activity.isDestroyed() && !activity.isFinishing(); } - return true; + return false; } /** diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/FilterAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/FilterAdapter.java index 88c59199..954369d4 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/FilterAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/FilterAdapter.java @@ -78,14 +78,14 @@ public class FilterAdapter extends RecyclerView.Adapter FilterActivity.addEditFilter(context, filter, filter1 -> { - if (filter1 != null) { + if (filter1 != null && BaseMainActivity.mainFilters.size() > position) { BaseMainActivity.mainFilters.get(position).context = filter1.context; BaseMainActivity.mainFilters.get(position).expires_at = filter1.expires_at; BaseMainActivity.mainFilters.get(position).filter_action = filter1.filter_action; BaseMainActivity.mainFilters.get(position).keywords = filter1.keywords; BaseMainActivity.mainFilters.get(position).title = filter1.title; + filterAdapter.notifyItemChanged(position); } - filterAdapter.notifyItemChanged(position); })); holder.binding.deleteFilter.setOnClickListener(v -> { AlertDialog.Builder builder = new AlertDialog.Builder(context, Helper.dialogStyle()); diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java index acfd5c03..ddba4f6f 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java @@ -1611,7 +1611,11 @@ public class StatusAdapter extends RecyclerView.Adapter .observe((LifecycleOwner) context, poll -> { int i = 0; for (Poll.PollItem item : statusToDeal.poll.options) { - poll.options.get(i).span_title = item.span_title; + if (item.span_title != null) { + poll.options.get(i).span_title = item.span_title; + } else { + poll.options.get(i).span_title = new SpannableString(item.title); + } i++; } statusToDeal.poll = poll; @@ -1627,7 +1631,11 @@ public class StatusAdapter extends RecyclerView.Adapter if (poll != null) { int i = 0; for (Poll.PollItem item : statusToDeal.poll.options) { - poll.options.get(i).span_title = item.span_title; + if (item.span_title != null) { + poll.options.get(i).span_title = item.span_title; + } else { + poll.options.get(i).span_title = new SpannableString(item.title); + } i++; } statusToDeal.poll = poll; @@ -1642,7 +1650,11 @@ public class StatusAdapter extends RecyclerView.Adapter //Store span elements int i = 0; for (Poll.PollItem item : statusToDeal.poll.options) { - poll.options.get(i).span_title = item.span_title; + if (item.span_title != null) { + poll.options.get(i).span_title = item.span_title; + } else { + poll.options.get(i).span_title = new SpannableString(item.title); + } i++; } statusToDeal.poll = poll; From 80023219b320e366a1ab4770e8bf320058f4c553 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Nov 2022 12:13:24 +0100 Subject: [PATCH 30/61] Fix some crashes --- .../app/fedilab/android/BaseMainActivity.java | 4 ++-- .../android/activities/ProfileActivity.java | 2 +- .../app/fedilab/android/helper/Helper.java | 21 +++++++++---------- .../android/ui/drawer/ReorderTabAdapter.java | 6 ++++-- .../FragmentMastodonNotification.java | 3 +-- .../viewmodel/mastodon/TimelinesVM.java | 10 +++++---- 6 files changed, 24 insertions(+), 22 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/BaseMainActivity.java index a353434e..49d407c9 100644 --- a/app/src/main/java/app/fedilab/android/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/BaseMainActivity.java @@ -654,13 +654,13 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt regex_public = sharedpreferences.getString(getString(R.string.SET_FILTER_REGEX_PUBLIC) + currentUserID + currentInstance, null); show_art_nsfw = sharedpreferences.getBoolean(getString(R.string.SET_ART_WITH_NSFW) + currentUserID + currentInstance, false); binding.profilePicture.setOnClickListener(v -> binding.drawerLayout.openDrawer(GravityCompat.START)); - Helper.loadPP(binding.profilePicture, currentAccount); + Helper.loadPP(BaseMainActivity.this, binding.profilePicture, currentAccount); headerMainBinding.accountAcc.setText(String.format("%s@%s", currentAccount.mastodon_account.username, currentAccount.instance)); if (currentAccount.mastodon_account.display_name == null || currentAccount.mastodon_account.display_name.isEmpty()) { currentAccount.mastodon_account.display_name = currentAccount.mastodon_account.acct; } headerMainBinding.accountName.setText(currentAccount.mastodon_account.display_name); - Helper.loadPP(headerMainBinding.accountProfilePicture, currentAccount, false); + Helper.loadPP(BaseMainActivity.this, headerMainBinding.accountProfilePicture, currentAccount, false); MastodonHelper.loadProfileMediaMastodon(headerMainBinding.backgroundImage, currentAccount.mastodon_account, MastodonHelper.MediaAccountType.HEADER); /* * Some general data are loaded when the app starts such; diff --git a/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java b/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java index 4c5e37ad..e2b97c1c 100644 --- a/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java @@ -870,7 +870,7 @@ public class ProfileActivity extends BaseActivity { i++; } builderSingle.setMultiChoiceItems(listsArray, presentArray, (dialog, which, isChecked) -> { - if (!relationship.following) { + if (relationship == null || !relationship.following) { accountsVM.follow(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, account.id, true, false) .observe(ProfileActivity.this, newRelationShip -> { relationship = newRelationShip; diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java index aee215dd..df3b8c9f 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -1132,8 +1132,8 @@ public class Helper { * @param view ImageView - the view where the image will be loaded * @param account - {@link Account} */ - public static void loadPP(ImageView view, BaseAccount account) { - loadPP(view, account, false); + public static void loadPP(Activity activity, ImageView view, BaseAccount account) { + loadPP(activity, view, account, false); } /** @@ -1142,15 +1142,14 @@ public class Helper { * @param view ImageView - the view where the image will be loaded * @param account - {@link Account} */ - public static void loadPP(ImageView view, BaseAccount account, boolean crop) { - Context context = view.getContext(); - SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(context); - boolean disableGif = sharedpreferences.getBoolean(context.getString(R.string.SET_DISABLE_GIF), false); + public static void loadPP(Activity activity, ImageView view, BaseAccount account, boolean crop) { + SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(activity); + boolean disableGif = sharedpreferences.getBoolean(activity.getString(R.string.SET_DISABLE_GIF), false); String targetedUrl = disableGif ? account.mastodon_account.avatar_static : account.mastodon_account.avatar; - if (targetedUrl != null && Helper.isValidContextForGlide(context)) { + if (targetedUrl != null && Helper.isValidContextForGlide(activity)) { if (disableGif || (!targetedUrl.endsWith(".gif"))) { try { - RequestBuilder requestBuilder = Glide.with(context) + RequestBuilder requestBuilder = Glide.with(activity) .asDrawable() .load(targetedUrl) .thumbnail(0.1f); @@ -1161,7 +1160,7 @@ public class Helper { } catch (Exception ignored) { } } else { - RequestBuilder requestBuilder = Glide.with(context) + RequestBuilder requestBuilder = Glide.with(activity) .asGif() .load(targetedUrl) .thumbnail(0.1f); @@ -1170,8 +1169,8 @@ public class Helper { } requestBuilder.apply(new RequestOptions().transform(new CenterCrop(), new RoundedCorners(10))).into(view); } - } else if (Helper.isValidContextForGlide(context)) { - Glide.with(context) + } else if (Helper.isValidContextForGlide(activity)) { + Glide.with(activity) .asDrawable() .load(R.drawable.ic_person) .thumbnail(0.1f) diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/ReorderTabAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/ReorderTabAdapter.java index 4f073910..8f92f050 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/ReorderTabAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/ReorderTabAdapter.java @@ -170,8 +170,10 @@ public class ReorderTabAdapter extends RecyclerView.Adapter { if (item.type == Timeline.TimeLineEnum.TAG || item.type == Timeline.TimeLineEnum.REMOTE || item.type == Timeline.TimeLineEnum.LIST) { mUndoListener.onUndo(item, position); - pinned.pinnedTimelines.remove(position); - notifyItemRemoved(position); + if (position < pinned.pinnedTimelines.size()) { + pinned.pinnedTimelines.remove(position); + notifyItemRemoved(position); + } } }); } diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonNotification.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonNotification.java index f87a09cf..65bb4163 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonNotification.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentMastodonNotification.java @@ -622,8 +622,7 @@ public class FragmentMastodonNotification extends Fragment implements Notificati for (Notification notificationsAlreadyPresent : notificationList) { //We compare the date of each status and we only add status having a date greater than the another, it is inserted at this position //Pinned messages are ignored because their date can be older - //if (Helper.compareTo(notificationReceived.id, notificationsAlreadyPresent.id) > 0) { - if (notificationReceived.created_at.after(notificationsAlreadyPresent.created_at)) { + if (Helper.compareTo(notificationReceived.id, notificationsAlreadyPresent.id) > 0) { if (!notificationList.contains(notificationReceived)) { notificationList.add(position, notificationReceived); notificationAdapter.notifyItemInserted(position); diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java index 79fc7669..d94d1ad0 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/TimelinesVM.java @@ -661,22 +661,24 @@ public class TimelinesVM extends AndroidViewModel { public LiveData> getLists(@NonNull String instance, String token) { mastodonListListMutableLiveData = new MutableLiveData<>(); MastodonTimelinesService mastodonTimelinesService = init(instance); + List mastodonListList = new ArrayList<>(); new Thread(() -> { - List mastodonListList = null; Call> getListsCall = mastodonTimelinesService.getLists(token); if (getListsCall != null) { try { Response> getListsResponse = getListsCall.execute(); if (getListsResponse.isSuccessful()) { - mastodonListList = getListsResponse.body(); + List mastodonLists = getListsResponse.body(); + if (mastodonLists != null) { + mastodonListList.addAll(mastodonLists); + } } } catch (Exception e) { e.printStackTrace(); } } Handler mainHandler = new Handler(Looper.getMainLooper()); - List finalMastodonListList = mastodonListList; - Runnable myRunnable = () -> mastodonListListMutableLiveData.setValue(finalMastodonListList); + Runnable myRunnable = () -> mastodonListListMutableLiveData.setValue(mastodonListList); mainHandler.post(myRunnable); }).start(); return mastodonListListMutableLiveData; From 4f8377d9d9c2afec81c417a0e0134b298f94d246 Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Nov 2022 15:42:43 +0100 Subject: [PATCH 31/61] Fix issue #524 - List cannot be removed from "Manage timelines" --- .../activities/ReorderTimelinesActivity.java | 53 +------------ .../SimpleItemTouchHelperCallback.java | 2 +- .../ui/drawer/ReorderBottomMenuAdapter.java | 4 - .../android/ui/drawer/ReorderTabAdapter.java | 74 ++++++++++++++----- app/src/main/res/values/strings.xml | 3 + 5 files changed, 61 insertions(+), 75 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/activities/ReorderTimelinesActivity.java b/app/src/main/java/app/fedilab/android/activities/ReorderTimelinesActivity.java index 2a9ff539..dc1eb145 100644 --- a/app/src/main/java/app/fedilab/android/activities/ReorderTimelinesActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/ReorderTimelinesActivity.java @@ -22,7 +22,6 @@ import android.content.Intent; import android.content.SharedPreferences; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; -import android.os.Handler; import android.text.Editable; import android.text.TextWatcher; import android.view.Menu; @@ -43,8 +42,6 @@ import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import com.google.android.material.snackbar.Snackbar; - import java.io.IOException; import java.util.ArrayList; import java.util.concurrent.TimeUnit; @@ -62,7 +59,6 @@ import app.fedilab.android.exception.DBException; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.ThemeHelper; import app.fedilab.android.helper.itemtouchhelper.OnStartDragListener; -import app.fedilab.android.helper.itemtouchhelper.OnUndoListener; import app.fedilab.android.helper.itemtouchhelper.SimpleItemTouchHelperCallback; import app.fedilab.android.ui.drawer.ReorderBottomMenuAdapter; import app.fedilab.android.ui.drawer.ReorderTabAdapter; @@ -78,7 +74,7 @@ import okhttp3.RequestBody; import okhttp3.Response; -public class ReorderTimelinesActivity extends BaseActivity implements OnStartDragListener, OnUndoListener { +public class ReorderTimelinesActivity extends BaseActivity implements OnStartDragListener { private ItemTouchHelper touchHelper; @@ -132,7 +128,7 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra update = false; } sortPositionAsc(this.pinned.pinnedTimelines); - reorderTabAdapter = new ReorderTabAdapter(this.pinned, ReorderTimelinesActivity.this, ReorderTimelinesActivity.this); + reorderTabAdapter = new ReorderTabAdapter(this.pinned, ReorderTimelinesActivity.this); ItemTouchHelper.Callback callback = new SimpleItemTouchHelperCallback(reorderTabAdapter); touchHelper = new ItemTouchHelper(callback); @@ -393,51 +389,6 @@ public class ReorderTimelinesActivity extends BaseActivity implements OnStartDra } - @Override - public void onUndo(PinnedTimeline pinnedTimeline, int position) { - - String text = ""; - switch (pinnedTimeline.type) { - case TAG: - text = getString(R.string.reorder_tag_removed); - break; - case REMOTE: - text = getString(R.string.reorder_instance_removed); - break; - case LIST: - text = getString(R.string.reorder_list_deleted); - break; - } - - - Runnable runnable = () -> { - //change position of pinned that are after the removed item - for (int i = pinnedTimeline.position + 1; i < pinned.pinnedTimelines.size(); i++) { - pinned.pinnedTimelines.get(i).position -= 1; - } - pinned.pinnedTimelines.remove(pinnedTimeline); - reorderTabAdapter.notifyItemRemoved(position); - try { - new Pinned(ReorderTimelinesActivity.this).updatePinned(pinned); - changes = true; - } catch (DBException e) { - e.printStackTrace(); - } - }; - Handler handler = new Handler(); - handler.postDelayed(runnable, 4000); - Snackbar.make(binding.getRoot(), text, 4000) - .setAction(getString(R.string.undo), view -> { - pinned.pinnedTimelines.add(position, pinnedTimeline); - reorderTabAdapter.notifyItemInserted(position); - handler.removeCallbacks(runnable); - }) - .setTextColor(ThemeHelper.getAttColor(ReorderTimelinesActivity.this, R.attr.mTextColor)) - .setActionTextColor(ContextCompat.getColor(ReorderTimelinesActivity.this, R.color.cyanea_accent_reference)) - .setBackgroundTint(ContextCompat.getColor(ReorderTimelinesActivity.this, R.color.cyanea_primary_dark_reference)) - .show(); - } - @Override public void onStop() { super.onStop(); diff --git a/app/src/main/java/app/fedilab/android/helper/itemtouchhelper/SimpleItemTouchHelperCallback.java b/app/src/main/java/app/fedilab/android/helper/itemtouchhelper/SimpleItemTouchHelperCallback.java index f929d9d0..3c42ab28 100644 --- a/app/src/main/java/app/fedilab/android/helper/itemtouchhelper/SimpleItemTouchHelperCallback.java +++ b/app/src/main/java/app/fedilab/android/helper/itemtouchhelper/SimpleItemTouchHelperCallback.java @@ -64,7 +64,7 @@ public class SimpleItemTouchHelperCallback extends ItemTouchHelper.Callback { return makeMovementFlags(dragFlags, swipeFlags); } else { final int dragFlags = ItemTouchHelper.UP | ItemTouchHelper.DOWN; - final int swipeFlags = ItemTouchHelper.START | ItemTouchHelper.END; + final int swipeFlags = 0; return makeMovementFlags(dragFlags, swipeFlags); } } diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/ReorderBottomMenuAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/ReorderBottomMenuAdapter.java index 5d0e4dd0..052a5c67 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/ReorderBottomMenuAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/ReorderBottomMenuAdapter.java @@ -20,7 +20,6 @@ import android.content.Context; import android.view.LayoutInflater; import android.view.MotionEvent; import android.view.ViewGroup; -import android.widget.Toast; import androidx.recyclerview.widget.RecyclerView; @@ -36,7 +35,6 @@ import app.fedilab.android.exception.DBException; import app.fedilab.android.helper.itemtouchhelper.ItemTouchHelperAdapter; import app.fedilab.android.helper.itemtouchhelper.ItemTouchHelperViewHolder; import app.fedilab.android.helper.itemtouchhelper.OnStartDragListener; -import es.dmoral.toasty.Toasty; /** @@ -130,8 +128,6 @@ public class ReorderBottomMenuAdapter extends RecyclerView.Adapter implements ItemTouchHelperAdapter { private final OnStartDragListener mDragStartListener; - private final OnUndoListener mUndoListener; private final Pinned pinned; private Context context; - public ReorderTabAdapter(Pinned pinned, OnStartDragListener dragStartListener, OnUndoListener undoListener) { + public ReorderTabAdapter(Pinned pinned, OnStartDragListener dragStartListener) { this.mDragStartListener = dragStartListener; - this.mUndoListener = undoListener; this.pinned = pinned; } @@ -169,26 +170,61 @@ public class ReorderTabAdapter extends RecyclerView.Adapter { if (item.type == Timeline.TimeLineEnum.TAG || item.type == Timeline.TimeLineEnum.REMOTE || item.type == Timeline.TimeLineEnum.LIST) { - mUndoListener.onUndo(item, position); - if (position < pinned.pinnedTimelines.size()) { - pinned.pinnedTimelines.remove(position); - notifyItemRemoved(position); + AlertDialog.Builder alt_bld = new AlertDialog.Builder(context, Helper.dialogStyle()); + String title = ""; + String message = ""; + alt_bld.setTitle(R.string.action_lists_delete); + alt_bld.setMessage(R.string.action_lists_confirm_delete); + switch (item.type) { + case TAG: + case REMOTE: + title = context.getString(R.string.action_pinned_delete); + message = context.getString(R.string.unpin_timeline_description); + break; + case LIST: + title = context.getString(R.string.action_lists_delete); + message = context.getString(R.string.action_lists_confirm_delete); + break; } + alt_bld.setTitle(title); + alt_bld.setMessage(message); + + alt_bld.setPositiveButton(R.string.delete, (dialog, id) -> { + //change position of pinned that are after the removed item + if (position < pinned.pinnedTimelines.size()) { + for (int i = item.position + 1; i < pinned.pinnedTimelines.size(); i++) { + pinned.pinnedTimelines.get(i).position -= 1; + } + pinned.pinnedTimelines.remove(position); + notifyItemRemoved(position); + notifyItemChanged(position, pinned.pinnedTimelines.size() - position); + try { + new Pinned(context).updatePinned(pinned); + } catch (DBException e) { + e.printStackTrace(); + } + } + + if (item.type == Timeline.TimeLineEnum.LIST) { + TimelinesVM timelinesVM = new ViewModelProvider((ViewModelStoreOwner) context).get(TimelinesVM.class); + timelinesVM.deleteList(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, item.mastodonList.id); + } + + + ((ReorderTimelinesActivity) context).setChanges(true); + dialog.dismiss(); + + }); + alt_bld.setNegativeButton(R.string.cancel, (dialog, id) -> dialog.dismiss()); + AlertDialog alert = alt_bld.create(); + alert.show(); + } }); } @Override public void onItemDismiss(int position) { - PinnedTimeline item = pinned.pinnedTimelines.get(position); - if (item.type == Timeline.TimeLineEnum.TAG || item.type == Timeline.TimeLineEnum.REMOTE || item.type == Timeline.TimeLineEnum.LIST) { - mUndoListener.onUndo(item, position); - pinned.pinnedTimelines.remove(position); - notifyItemRemoved(position); - } else { - notifyItemChanged(position); - Toasty.info(context, context.getString(R.string.warning_main_timeline), Toast.LENGTH_SHORT).show(); - } } @Override diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0eec9af9..9d8b5255 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1911,4 +1911,7 @@ You have not blocked domains Are you sure to unblock %1$s? Privacy policy + Remove pinned timeline? + Are you sure to unpin that timeline? + Delete the pinned timelines? \ No newline at end of file From 9c728f24c3e54a43121553db687d14bf2cca3b6b Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Nov 2022 15:49:31 +0100 Subject: [PATCH 32/61] Fix error message when adding the first list --- .../fedilab/android/activities/MastodonListActivity.java | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/fedilab/android/activities/MastodonListActivity.java b/app/src/main/java/app/fedilab/android/activities/MastodonListActivity.java index f4cd975b..ba64cd17 100644 --- a/app/src/main/java/app/fedilab/android/activities/MastodonListActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/MastodonListActivity.java @@ -279,8 +279,15 @@ public class MastodonListActivity extends BaseActivity implements MastodonListAd if (mastodonListList == null) { mastodonListList = new ArrayList<>(); } - if (newMastodonList != null && mastodonListAdapter != null) { + if (newMastodonList != null) { mastodonListList.add(0, newMastodonList); + if (mastodonListAdapter == null) { + mastodonListAdapter = new MastodonListAdapter(mastodonListList); + mastodonListAdapter.actionOnList = MastodonListActivity.this; + binding.notContent.setVisibility(View.GONE); + binding.recyclerView.setAdapter(mastodonListAdapter); + binding.recyclerView.setLayoutManager(new LinearLayoutManager(MastodonListActivity.this)); + } mastodonListAdapter.notifyItemInserted(0); } else { Toasty.error(MastodonListActivity.this, getString(R.string.toast_error), Toasty.LENGTH_LONG).show(); From 9d864857dac4e653ad25180c4f2dc1694286a2ca Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Nov 2022 15:57:06 +0100 Subject: [PATCH 33/61] Fix issue #490 - Remove grouped notifications for Android 5 & 6 --- app/src/main/java/app/fedilab/android/helper/Helper.java | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java index df3b8c9f..aa73e044 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -1610,8 +1610,11 @@ public class Helper { .setGroup(account.mastodon_account != null ? account.mastodon_account.username + "@" + account.instance : "" + "@" + account.instance) .setGroupSummary(true) .build(); + notificationManager.notify(notificationId++, notificationBuilder.build()); - notificationManager.notify(0, summaryNotification); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N_MR1) { + notificationManager.notify(0, summaryNotification); + } } public static void transfertIfExist(Context context) { From 637bb92a4150e49d45846592e29156cbd4b111fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xos=C3=A9=20M?= Date: Wed, 23 Nov 2022 15:44:09 +0100 Subject: [PATCH 34/61] Translated using Weblate (Galician) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (895 of 895 strings) Co-authored-by: Xosé M Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/ Translation: Fedilab/Strings --- app/src/main/res/values-gl/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 6fdd9ba0..4316d5f1 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -508,7 +508,7 @@ Queres deixar de seguir esta conta? Mostrar cadro de confirmación antes de deixar de seguir Substituír ligazóns a Medium - Substituír as ligazóns a medium.com cunha interface de código aberto alternativa centrada na privacidade. + Usar unha interface alternativa para Medium Por defecto: scribe.rip Usar un sistema de notificacións push para ter notificacións en tempo real. Engadir notas From 132cb89b2a5634e22ef23cd922311b52fe74952b Mon Sep 17 00:00:00 2001 From: Thomas Date: Wed, 23 Nov 2022 16:19:01 +0100 Subject: [PATCH 35/61] Fix issue #522 - Notifications lost their content and fallback to default view. --- .../timeline/FragmentNotificationContainer.java | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentNotificationContainer.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentNotificationContainer.java index b5cc921c..354a6b50 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentNotificationContainer.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentNotificationContainer.java @@ -25,6 +25,7 @@ import android.view.View; import android.view.ViewGroup; import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; import androidx.core.content.res.ResourcesCompat; @@ -56,10 +57,15 @@ public class FragmentNotificationContainer extends Fragment { public static UpdateCounters update; private FragmentNotificationContainerBinding binding; + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { - binding = FragmentNotificationContainerBinding.inflate(inflater, container, false); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); boolean display_all_notification = sharedpreferences.getBoolean(getString(R.string.SET_DISPLAY_ALL_NOTIFICATIONS_TYPE) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance, false); if (!display_all_notification) { @@ -234,9 +240,9 @@ public class FragmentNotificationContainer extends Fragment { } } }); - return binding.getRoot(); } + private void doAction(boolean changed, List excludedCategoriesList) { SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(requireActivity()); if (changed) { From e32b3bf6da78ceca439e3f77b5d64bd8cd687122 Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 24 Nov 2022 10:35:50 +0100 Subject: [PATCH 36/61] Fix admin with api/v2 --- .../activities/AccountReportActivity.java | 19 ++--- .../client/entities/api/AdminAccount.java | 78 +++++++++++++++---- .../ui/drawer/AdminAccountAdapter.java | 3 +- .../android/viewmodel/mastodon/AdminVM.java | 15 ++-- 4 files changed, 78 insertions(+), 37 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/activities/AccountReportActivity.java b/app/src/main/java/app/fedilab/android/activities/AccountReportActivity.java index 6cbbe4fd..e8a701cd 100644 --- a/app/src/main/java/app/fedilab/android/activities/AccountReportActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/AccountReportActivity.java @@ -221,7 +221,7 @@ public class AccountReportActivity extends BaseActivity { binding.email.setVisibility(View.GONE); binding.emailLabel.setVisibility(View.GONE); } - if (accountAdmin.ip == null || accountAdmin.ip.ip.trim().equals("")) { + if (accountAdmin.ip == null || accountAdmin.ip.trim().equals("")) { binding.recentIp.setVisibility(View.GONE); binding.recentIpLabel.setVisibility(View.GONE); } @@ -243,7 +243,7 @@ public class AccountReportActivity extends BaseActivity { binding.emailUser.setVisibility(View.VISIBLE); binding.commentLabel.setVisibility(View.VISIBLE); binding.comment.setVisibility(View.VISIBLE); - binding.recentIp.setText(accountAdmin.ip != null ? accountAdmin.ip.ip : ""); + binding.recentIp.setText(accountAdmin.ip != null ? accountAdmin.ip : ""); binding.disable.setVisibility(View.VISIBLE); binding.suspend.setVisibility(View.VISIBLE); } else { @@ -260,18 +260,9 @@ public class AccountReportActivity extends BaseActivity { } if (accountAdmin.role != null) { - switch (accountAdmin.role) { - case "user": - binding.permissions.setText(getString(R.string.user)); - break; - case "moderator": - binding.permissions.setText(getString(R.string.moderator)); - break; - case "admin": - binding.permissions.setText(getString(R.string.administrator)); - break; - } - if (accountAdmin.role.equals("admin") || accountAdmin.role.equals("moderator")) { + binding.permissions.setText(AdminAccount.permissions.get(accountAdmin.role.permissions)); + binding.permissions.setText(getString(R.string.user)); + if (accountAdmin.role.permissions == 1 || accountAdmin.role.permissions == 400) { binding.warn.setVisibility(View.GONE); binding.suspend.setVisibility(View.GONE); binding.silence.setVisibility(View.GONE); diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/AdminAccount.java b/app/src/main/java/app/fedilab/android/client/entities/api/AdminAccount.java index 0dcaf923..99da1e3b 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/AdminAccount.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/AdminAccount.java @@ -18,6 +18,7 @@ import com.google.gson.annotations.SerializedName; import java.io.Serializable; import java.util.Date; +import java.util.LinkedHashMap; import java.util.List; public class AdminAccount implements Serializable { @@ -32,26 +33,48 @@ public class AdminAccount implements Serializable { public Date created_at; @SerializedName("email") public String email; + public static LinkedHashMap permissions; + + static { + permissions = new LinkedHashMap<>(); + permissions.put(1, "Administrator"); + permissions.put(2, "Devops"); + permissions.put(4, "View Audit Log"); + permissions.put(8, "View Dashboard"); + permissions.put(10, "Manage Reports"); + permissions.put(20, "Manage Federation"); + permissions.put(40, "Manage Settings"); + permissions.put(80, "Manage Blocks"); + permissions.put(100, "Manage Taxonomies"); + permissions.put(200, "Manage Appeals"); + permissions.put(400, "Manage Users"); + permissions.put(800, "Manage Invites"); + permissions.put(1000, "Manage Rules"); + permissions.put(2000, "Manage Announcements"); + permissions.put(4000, "Manage Custom Emojis"); + permissions.put(8000, "Manage Webhooks"); + permissions.put(10000, "Invite Users"); + permissions.put(20000, "Manage Roles"); + permissions.put(40000, "Manage User Access"); + permissions.put(80000, "Delete User Data"); + } + @SerializedName("ip") - public IP ip; - @SerializedName("ips") - public List ips; - @SerializedName("locale") - public String locale; - @SerializedName("invite_request") - public String invite_request; + public String ip; @SerializedName("role") - public String role; + public Role role; @SerializedName("confirmed") public boolean confirmed; - @SerializedName("approved") - public boolean approved; - @SerializedName("disabled") - public boolean disabled; - @SerializedName("silenced") - public boolean silenced; @SerializedName("suspended") public boolean suspended; + @SerializedName("silenced") + public boolean silenced; + @SerializedName("disabled") + public boolean disabled; + @SerializedName("approved") + public boolean approved; + @SerializedName("ips") + public List ips; @SerializedName("account") public Account account; @SerializedName("created_by_application_id") @@ -65,7 +88,30 @@ public class AdminAccount implements Serializable { public String ip; @SerializedName("used_at") public Date used_at; - @SerializedName("user_id") - public String user_id; } + + @SerializedName("locale") + public String locale; + @SerializedName("invite_request") + public String invite_request; + + public static class Role implements Serializable { + @SerializedName("ip") + public String ip; + @SerializedName("name") + public String name; + @SerializedName("color") + public String color; + @SerializedName("position") + public long position; + @SerializedName("permissions") + public int permissions; + @SerializedName("highlighted") + public boolean highlighted; + @SerializedName("created_at") + public Date created_at; + @SerializedName("updated_at") + public Date updated_at; + } + } diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/AdminAccountAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/AdminAccountAdapter.java index 5d35e711..d4556fe4 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/AdminAccountAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/AdminAccountAdapter.java @@ -78,8 +78,7 @@ public class AdminAccountAdapter extends RecyclerView.Adapter 0) { holder.binding.lastActive.setText(Helper.shortDateToString(adminAccount.ips.get(0).used_at)); holder.binding.ip.setText(adminAccount.ips.get(0).ip); diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java index cb764d01..88bd007e 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java @@ -23,9 +23,6 @@ import androidx.lifecycle.AndroidViewModel; import androidx.lifecycle.LiveData; import androidx.lifecycle.MutableLiveData; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - import java.util.List; import java.util.concurrent.TimeUnit; @@ -60,7 +57,6 @@ public class AdminVM extends AndroidViewModel { } private MastodonAdminService init(@NonNull String instance) { - Gson gson = new GsonBuilder().setDateFormat("yyyy-MM-dd'T'HH:mm:ssZ").create(); Retrofit retrofit = new Retrofit.Builder() .baseUrl("https://" + instance + "/api/v1/") .addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder())) @@ -69,6 +65,15 @@ public class AdminVM extends AndroidViewModel { return retrofit.create(MastodonAdminService.class); } + private MastodonAdminService initv2(@NonNull String instance) { + Retrofit retrofit = new Retrofit.Builder() + .baseUrl("https://" + instance + "/api/v2/") + .addConverterFactory(GsonConverterFactory.create(Helper.getDateBuilder())) + .client(okHttpClient) + .build(); + return retrofit.create(MastodonAdminService.class); + } + /** * View accounts matching certain criteria for filtering, up to 100 at a time. * @@ -107,7 +112,7 @@ public class AdminVM extends AndroidViewModel { String maxId, String sinceId, Integer limit) { - MastodonAdminService mastodonAdminService = init(instance); + MastodonAdminService mastodonAdminService = initv2(instance); adminAccountsListMutableLiveData = new MutableLiveData<>(); new Thread(() -> { Call> getAccountsCall = mastodonAdminService.getAccounts( From c6bd8ac265dddd4a093d36572d2ee51035499c43 Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 24 Nov 2022 10:53:44 +0100 Subject: [PATCH 37/61] Add new endpoints and fix migration to v2 --- .../activities/AccountReportActivity.java | 4 +- .../activities/AdminAccountActivity.java | 2 +- .../activities/AdminReportActivity.java | 2 +- .../endpoints/MastodonAdminService.java | 4 +- .../api/{ => admin}/AdminAccount.java | 13 ++---- .../api/{ => admin}/AdminAccounts.java | 4 +- .../entities/api/admin/AdminDomainBlock.java | 43 ++++++++++++++++++ .../api/admin/AdminEmailDomainBlock.java | 36 +++++++++++++++ .../client/entities/api/admin/AdminIp.java | 29 ++++++++++++ .../entities/api/admin/AdminIpBlock.java | 37 +++++++++++++++ .../entities/api/admin/AdminMeasure.java | 45 +++++++++++++++++++ .../entities/api/{ => admin}/AdminReport.java | 25 +++++++---- .../api/{ => admin}/AdminReports.java | 4 +- .../ui/drawer/AdminAccountAdapter.java | 2 +- .../android/ui/drawer/ReportAdapter.java | 2 +- .../fragment/admin/FragmentAdminAccount.java | 4 +- .../fragment/admin/FragmentAdminReport.java | 4 +- .../android/viewmodel/mastodon/AdminVM.java | 8 ++-- 18 files changed, 232 insertions(+), 36 deletions(-) rename app/src/main/java/app/fedilab/android/client/entities/api/{ => admin}/AdminAccount.java (93%) rename app/src/main/java/app/fedilab/android/client/entities/api/{ => admin}/AdminAccounts.java (88%) create mode 100644 app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminDomainBlock.java create mode 100644 app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminEmailDomainBlock.java create mode 100644 app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminIp.java create mode 100644 app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminIpBlock.java create mode 100644 app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminMeasure.java rename app/src/main/java/app/fedilab/android/client/entities/api/{ => admin}/AdminReport.java (84%) rename app/src/main/java/app/fedilab/android/client/entities/api/{ => admin}/AdminReports.java (88%) diff --git a/app/src/main/java/app/fedilab/android/activities/AccountReportActivity.java b/app/src/main/java/app/fedilab/android/activities/AccountReportActivity.java index e8a701cd..63257fed 100644 --- a/app/src/main/java/app/fedilab/android/activities/AccountReportActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/AccountReportActivity.java @@ -31,9 +31,9 @@ import androidx.recyclerview.widget.LinearLayoutManager; import java.util.ArrayList; import app.fedilab.android.R; -import app.fedilab.android.client.entities.api.AdminAccount; -import app.fedilab.android.client.entities.api.AdminReport; import app.fedilab.android.client.entities.api.Status; +import app.fedilab.android.client.entities.api.admin.AdminAccount; +import app.fedilab.android.client.entities.api.admin.AdminReport; import app.fedilab.android.databinding.ActivityAdminReportBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.ThemeHelper; diff --git a/app/src/main/java/app/fedilab/android/activities/AdminAccountActivity.java b/app/src/main/java/app/fedilab/android/activities/AdminAccountActivity.java index db0732a5..95b9fad9 100644 --- a/app/src/main/java/app/fedilab/android/activities/AdminAccountActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/AdminAccountActivity.java @@ -54,8 +54,8 @@ import java.util.concurrent.TimeUnit; import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; -import app.fedilab.android.client.entities.api.AdminAccount; import app.fedilab.android.client.entities.api.Attachment; +import app.fedilab.android.client.entities.api.admin.AdminAccount; import app.fedilab.android.databinding.ActivityAdminAccountBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastodonHelper; diff --git a/app/src/main/java/app/fedilab/android/activities/AdminReportActivity.java b/app/src/main/java/app/fedilab/android/activities/AdminReportActivity.java index 74afa790..6486f651 100644 --- a/app/src/main/java/app/fedilab/android/activities/AdminReportActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/AdminReportActivity.java @@ -56,8 +56,8 @@ import java.util.concurrent.TimeUnit; import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; import app.fedilab.android.client.entities.api.Account; -import app.fedilab.android.client.entities.api.AdminAccount; import app.fedilab.android.client.entities.api.Attachment; +import app.fedilab.android.client.entities.api.admin.AdminAccount; import app.fedilab.android.databinding.ActivityAdminAccountBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastodonHelper; diff --git a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java index 0cef6998..7db4317e 100644 --- a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java +++ b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java @@ -17,8 +17,8 @@ package app.fedilab.android.client.endpoints; import java.util.List; -import app.fedilab.android.client.entities.api.AdminAccount; -import app.fedilab.android.client.entities.api.AdminReport; +import app.fedilab.android.client.entities.api.admin.AdminAccount; +import app.fedilab.android.client.entities.api.admin.AdminReport; import retrofit2.Call; import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/AdminAccount.java b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminAccount.java similarity index 93% rename from app/src/main/java/app/fedilab/android/client/entities/api/AdminAccount.java rename to app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminAccount.java index 99da1e3b..26eb86a7 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/AdminAccount.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminAccount.java @@ -1,4 +1,4 @@ -package app.fedilab.android.client.entities.api; +package app.fedilab.android.client.entities.api.admin; /* Copyright 2021 Thomas Schneider * * This file is a part of Fedilab @@ -21,6 +21,8 @@ import java.util.Date; import java.util.LinkedHashMap; import java.util.List; +import app.fedilab.android.client.entities.api.Account; + public class AdminAccount implements Serializable { @SerializedName("id") @@ -74,7 +76,7 @@ public class AdminAccount implements Serializable { @SerializedName("approved") public boolean approved; @SerializedName("ips") - public List ips; + public List ips; @SerializedName("account") public Account account; @SerializedName("created_by_application_id") @@ -83,13 +85,6 @@ public class AdminAccount implements Serializable { public String invited_by_account_id; - public static class IP implements Serializable { - @SerializedName("ip") - public String ip; - @SerializedName("used_at") - public Date used_at; - } - @SerializedName("locale") public String locale; @SerializedName("invite_request") diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/AdminAccounts.java b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminAccounts.java similarity index 88% rename from app/src/main/java/app/fedilab/android/client/entities/api/AdminAccounts.java rename to app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminAccounts.java index eed98dcd..02fc4b82 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/AdminAccounts.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminAccounts.java @@ -1,4 +1,4 @@ -package app.fedilab.android.client.entities.api; +package app.fedilab.android.client.entities.api.admin; /* Copyright 2021 Thomas Schneider * * This file is a part of Fedilab @@ -17,6 +17,8 @@ package app.fedilab.android.client.entities.api; import java.util.List; +import app.fedilab.android.client.entities.api.Pagination; + public class AdminAccounts { public Pagination pagination = new Pagination(); diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminDomainBlock.java b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminDomainBlock.java new file mode 100644 index 00000000..e25b9b15 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminDomainBlock.java @@ -0,0 +1,43 @@ +package app.fedilab.android.client.entities.api.admin; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; +import java.util.Date; + +public class AdminDomainBlock implements Serializable { + + @SerializedName("id") + public String id; + @SerializedName("domain") + public String domain; + @SerializedName("created_at") + public Date created_at; + @SerializedName("severity") + public String severity; + @SerializedName("reject_media") + public boolean reject_media; + @SerializedName("reject_reports") + public boolean reject_reports; + @SerializedName("private_comment") + public String private_comment; + @SerializedName("public_comment") + public String public_comment; + @SerializedName("obfuscate") + public boolean obfuscate; +} diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminEmailDomainBlock.java b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminEmailDomainBlock.java new file mode 100644 index 00000000..e72e649b --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminEmailDomainBlock.java @@ -0,0 +1,36 @@ +package app.fedilab.android.client.entities.api.admin; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +import app.fedilab.android.client.entities.api.History; + +public class AdminEmailDomainBlock implements Serializable { + + @SerializedName("id") + public String id; + @SerializedName("domain") + public String domain; + @SerializedName("created_at") + public Date created_at; + @SerializedName("history") + public List history; +} diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminIp.java b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminIp.java new file mode 100644 index 00000000..9962b481 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminIp.java @@ -0,0 +1,29 @@ +package app.fedilab.android.client.entities.api.admin; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; +import java.util.Date; + +public class AdminIp implements Serializable { + + @SerializedName("ip") + public String ip; + @SerializedName("used_at") + public Date used_at; +} diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminIpBlock.java b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminIpBlock.java new file mode 100644 index 00000000..24d1af15 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminIpBlock.java @@ -0,0 +1,37 @@ +package app.fedilab.android.client.entities.api.admin; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; +import java.util.Date; + +public class AdminIpBlock implements Serializable { + + @SerializedName("id") + public String id; + @SerializedName("ip") + public String ip; + @SerializedName("severity") + public String severity; + @SerializedName("comment") + public String comment; + @SerializedName("created_at") + public Date created_at; + @SerializedName("expires_at") + public Date expires_at; +} diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminMeasure.java b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminMeasure.java new file mode 100644 index 00000000..6e33b2d8 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminMeasure.java @@ -0,0 +1,45 @@ +package app.fedilab.android.client.entities.api.admin; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + + +import com.google.gson.annotations.SerializedName; + +import java.io.Serializable; +import java.util.Date; +import java.util.List; + +public class AdminMeasure implements Serializable { + + @SerializedName("key") + public String key; + @SerializedName("unit") + public String unit; + @SerializedName("total") + public int total; + @SerializedName("human_value") + public int human_value; + @SerializedName("previous_total") + public int previous_total; + @SerializedName("data") + public List data; + + public static class Data implements Serializable { + @SerializedName("date") + public Date date; + @SerializedName("value") + public int value; + } +} diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/AdminReport.java b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminReport.java similarity index 84% rename from app/src/main/java/app/fedilab/android/client/entities/api/AdminReport.java rename to app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminReport.java index 92eaea06..988ace1b 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/AdminReport.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminReport.java @@ -1,4 +1,4 @@ -package app.fedilab.android.client.entities.api; +package app.fedilab.android.client.entities.api.admin; /* Copyright 2021 Thomas Schneider * * This file is a part of Fedilab @@ -20,30 +20,37 @@ import java.io.Serializable; import java.util.Date; import java.util.List; +import app.fedilab.android.client.entities.api.Instance; +import app.fedilab.android.client.entities.api.Status; + public class AdminReport implements Serializable { @SerializedName("id") public String id; - @SerializedName("account") - public AdminAccount account; @SerializedName("action_taken") public Boolean action_taken; - @SerializedName("action_taken_by_account") - public AdminAccount action_taken_by_account; - @SerializedName("assigned_account") - public AdminAccount assigned_account; + @SerializedName("action_taken_at") + public Date action_taken_at; @SerializedName("category") public String category; @SerializedName("comment") public String comment; + @SerializedName("forwarded") + public boolean forwarded; @SerializedName("created_at") public Date created_at; + @SerializedName("updated_at") + public Date updated_at; + @SerializedName("account") + public AdminAccount account; @SerializedName("target_account") public AdminAccount target_account; + @SerializedName("assigned_account") + public AdminAccount assigned_account; + @SerializedName("action_taken_by_account") + public AdminAccount action_taken_by_account; @SerializedName("statuses") public List statuses; @SerializedName("rules") public List rules; - @SerializedName("updated_at") - public Date updated_at; } diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/AdminReports.java b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminReports.java similarity index 88% rename from app/src/main/java/app/fedilab/android/client/entities/api/AdminReports.java rename to app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminReports.java index e7674d3a..e6b61481 100644 --- a/app/src/main/java/app/fedilab/android/client/entities/api/AdminReports.java +++ b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminReports.java @@ -1,4 +1,4 @@ -package app.fedilab.android.client.entities.api; +package app.fedilab.android.client.entities.api.admin; /* Copyright 2021 Thomas Schneider * * This file is a part of Fedilab @@ -17,6 +17,8 @@ package app.fedilab.android.client.entities.api; import java.util.List; +import app.fedilab.android.client.entities.api.Pagination; + public class AdminReports { public Pagination pagination = new Pagination(); diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/AdminAccountAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/AdminAccountAdapter.java index d4556fe4..99ab7635 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/AdminAccountAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/AdminAccountAdapter.java @@ -28,7 +28,7 @@ import java.util.List; import java.util.Locale; import app.fedilab.android.activities.AdminAccountActivity; -import app.fedilab.android.client.entities.api.AdminAccount; +import app.fedilab.android.client.entities.api.admin.AdminAccount; import app.fedilab.android.databinding.DrawerAdminAccountBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastodonHelper; diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/ReportAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/ReportAdapter.java index 394a9bc1..6a1fa987 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/ReportAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/ReportAdapter.java @@ -31,7 +31,7 @@ import java.util.List; import app.fedilab.android.activities.AccountReportActivity; import app.fedilab.android.client.entities.api.Account; -import app.fedilab.android.client.entities.api.AdminReport; +import app.fedilab.android.client.entities.api.admin.AdminReport; import app.fedilab.android.databinding.DrawerReportBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastodonHelper; diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminAccount.java b/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminAccount.java index ba3e9b3b..6e3663c1 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminAccount.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminAccount.java @@ -34,8 +34,8 @@ import java.util.List; import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; import app.fedilab.android.activities.AdminActionActivity; -import app.fedilab.android.client.entities.api.AdminAccount; -import app.fedilab.android.client.entities.api.AdminAccounts; +import app.fedilab.android.client.entities.api.admin.AdminAccount; +import app.fedilab.android.client.entities.api.admin.AdminAccounts; import app.fedilab.android.databinding.FragmentPaginationBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastodonHelper; diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminReport.java b/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminReport.java index 0f5a5ec1..6a44c743 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminReport.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminReport.java @@ -34,8 +34,8 @@ import java.util.List; import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; import app.fedilab.android.activities.AdminActionActivity; -import app.fedilab.android.client.entities.api.AdminReport; -import app.fedilab.android.client.entities.api.AdminReports; +import app.fedilab.android.client.entities.api.admin.AdminReport; +import app.fedilab.android.client.entities.api.admin.AdminReports; import app.fedilab.android.databinding.FragmentPaginationBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.ThemeHelper; diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java index 88bd007e..d00a39d3 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java @@ -27,10 +27,10 @@ import java.util.List; import java.util.concurrent.TimeUnit; import app.fedilab.android.client.endpoints.MastodonAdminService; -import app.fedilab.android.client.entities.api.AdminAccount; -import app.fedilab.android.client.entities.api.AdminAccounts; -import app.fedilab.android.client.entities.api.AdminReport; -import app.fedilab.android.client.entities.api.AdminReports; +import app.fedilab.android.client.entities.api.admin.AdminAccount; +import app.fedilab.android.client.entities.api.admin.AdminAccounts; +import app.fedilab.android.client.entities.api.admin.AdminReport; +import app.fedilab.android.client.entities.api.admin.AdminReports; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastodonHelper; import okhttp3.OkHttpClient; From f96de62fef98995a4b24fe8a212cc16940cb93a9 Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 24 Nov 2022 11:24:55 +0100 Subject: [PATCH 38/61] Add api endpoints --- .../endpoints/MastodonAdminService.java | 53 +++++++++++++ .../entities/api/admin/AdminDomainBlocks.java | 26 +++++++ .../android/viewmodel/mastodon/AdminVM.java | 74 +++++++++++++++++++ 3 files changed, 153 insertions(+) create mode 100644 app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminDomainBlocks.java diff --git a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java index 7db4317e..5d3aff27 100644 --- a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java +++ b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java @@ -18,13 +18,16 @@ package app.fedilab.android.client.endpoints; import java.util.List; import app.fedilab.android.client.entities.api.admin.AdminAccount; +import app.fedilab.android.client.entities.api.admin.AdminDomainBlock; import app.fedilab.android.client.entities.api.admin.AdminReport; import retrofit2.Call; +import retrofit2.http.DELETE; import retrofit2.http.Field; import retrofit2.http.FormUrlEncoded; import retrofit2.http.GET; import retrofit2.http.Header; import retrofit2.http.POST; +import retrofit2.http.PUT; import retrofit2.http.Path; import retrofit2.http.Query; @@ -115,6 +118,7 @@ public interface MastodonAdminService { @Query("limit") int limit ); + //***************** ADMIN REPORTS ************** @GET("admin/reports/{id}") Call getReport( @@ -149,4 +153,53 @@ public interface MastodonAdminService { @Header("Authorization") String app_token, @Path("id") String id ); + + + //*************** ADMIN DOMAINS **************** + + @GET("admin/domain_blocks") + Call> getDomainBlocks( + @Header("Authorization") String token, + @Query("max_id") String max_id, + @Query("limit") int limit + ); + + @GET("admin/domain_blocks/{id}") + Call getDomainBlock( + @Header("Authorization") String token, + @Path("id") String id + ); + + + @FormUrlEncoded + @POST("admin/domain_blocks") + Call blockDomain( + @Header("Authorization") String app_token, + @Path("domain") String domain, + @Field("severity") String severity, + @Field("reject_media") Boolean reject_media, + @Field("reject_reports") Boolean reject_reports, + @Field("private_comment") String private_comment, + @Field("public_comment") String public_comment, + @Field("obfuscate") Boolean obfuscate + ); + + @FormUrlEncoded + @PUT("admin/domain_blocks") + Call updateBlockDomain( + @Header("Authorization") String app_token, + @Path("domain") String domain, + @Field("severity") String severity, + @Field("reject_media") Boolean reject_media, + @Field("reject_reports") Boolean reject_reports, + @Field("private_comment") String private_comment, + @Field("public_comment") String public_comment, + @Field("obfuscate") Boolean obfuscate + ); + + @DELETE("admin/domain_blocks/{id}") + Call deleteBlockDomain( + @Header("Authorization") String app_token, + @Path("id") String id + ); } diff --git a/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminDomainBlocks.java b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminDomainBlocks.java new file mode 100644 index 00000000..451da28d --- /dev/null +++ b/app/src/main/java/app/fedilab/android/client/entities/api/admin/AdminDomainBlocks.java @@ -0,0 +1,26 @@ +package app.fedilab.android.client.entities.api.admin; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + + +import java.util.List; + +import app.fedilab.android.client.entities.api.Pagination; + +public class AdminDomainBlocks { + + public Pagination pagination = new Pagination(); + public List adminDomainBlocks; +} diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java index d00a39d3..11568c0a 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java @@ -29,6 +29,8 @@ import java.util.concurrent.TimeUnit; import app.fedilab.android.client.endpoints.MastodonAdminService; import app.fedilab.android.client.entities.api.admin.AdminAccount; import app.fedilab.android.client.entities.api.admin.AdminAccounts; +import app.fedilab.android.client.entities.api.admin.AdminDomainBlock; +import app.fedilab.android.client.entities.api.admin.AdminDomainBlocks; import app.fedilab.android.client.entities.api.admin.AdminReport; import app.fedilab.android.client.entities.api.admin.AdminReports; import app.fedilab.android.helper.Helper; @@ -51,6 +53,9 @@ public class AdminVM extends AndroidViewModel { private MutableLiveData adminAccountsListMutableLiveData; private MutableLiveData adminReportMutableLiveData; private MutableLiveData adminReporstListMutableLiveData; + private MutableLiveData adminDomainBlockMutableLiveData; + private MutableLiveData adminDomainBlockListMutableLiveData; + public AdminVM(@NonNull Application application) { super(application); @@ -556,4 +561,73 @@ public class AdminVM extends AndroidViewModel { }).start(); return adminReportMutableLiveData; } + + + /** + * View all domains blocked. + * + * @param instance Instance domain of the active account + * @param token Access token of the active account + * @return {@link LiveData} containing a {@link List} of {@link AdminDomainBlocks}s + */ + public LiveData getDomainBlocks(@NonNull String instance, + String token, + String max_id) { + MastodonAdminService mastodonAdminService = init(instance); + adminDomainBlockListMutableLiveData = new MutableLiveData<>(); + new Thread(() -> { + List adminDomainBlockList; + Call> getDomainBlocks = mastodonAdminService.getDomainBlocks(token, max_id, MastodonHelper.statusesPerCall(getApplication())); + AdminDomainBlocks adminDomainBlocks = new AdminDomainBlocks(); + if (getDomainBlocks != null) { + try { + Response> getDomainBlocksResponse = getDomainBlocks.execute(); + if (getDomainBlocksResponse.isSuccessful()) { + adminDomainBlocks.adminDomainBlocks = getDomainBlocksResponse.body(); + adminDomainBlocks.pagination = MastodonHelper.getPagination(getDomainBlocksResponse.headers()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> adminDomainBlockListMutableLiveData.setValue(adminDomainBlocks); + mainHandler.post(myRunnable); + }).start(); + return adminDomainBlockListMutableLiveData; + } + + + /** + * View a single blocked domain + * + * @param instance Instance domain of the active account + * @param token Access token of the active account + * @return {@link LiveData} containing a {@link List} of {@link AdminDomainBlocks}s + */ + public LiveData getDomainBlock(@NonNull String instance, + String token, + String id) { + MastodonAdminService mastodonAdminService = init(instance); + adminDomainBlockMutableLiveData = new MutableLiveData<>(); + new Thread(() -> { + AdminDomainBlock adminDomainBlock = null; + Call getDomainBlock = mastodonAdminService.getDomainBlock(token, id); + if (getDomainBlock != null) { + try { + Response getDomainBlocksResponse = getDomainBlock.execute(); + if (getDomainBlocksResponse.isSuccessful()) { + adminDomainBlock = getDomainBlocksResponse.body(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + AdminDomainBlock finalAdminDomainBlock = adminDomainBlock; + Runnable myRunnable = () -> adminDomainBlockMutableLiveData.setValue(finalAdminDomainBlock); + mainHandler.post(myRunnable); + }).start(); + return adminDomainBlockMutableLiveData; + } } From b5a304be5647e213f70bf5cdf5ce5ef9ec6afad7 Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 24 Nov 2022 12:32:33 +0100 Subject: [PATCH 39/61] Add some endpoints + ui --- app/src/main/AndroidManifest.xml | 6 +- .../app/fedilab/android/BaseMainActivity.java | 2 +- .../{ => admin}/AdminAccountActivity.java | 9 +- .../{ => admin}/AdminActionActivity.java | 42 +++- .../{ => admin}/AdminReportActivity.java | 8 +- .../endpoints/MastodonAdminService.java | 27 +++ .../android/ui/drawer/StatusAdapter.java | 2 +- .../{ => admin}/AdminAccountAdapter.java | 4 +- .../ui/drawer/admin/AdminDomainAdapter.java | 90 ++++++++ .../ui/drawer/{ => admin}/ReportAdapter.java | 2 +- .../fragment/admin/FragmentAdminAccount.java | 4 +- .../fragment/admin/FragmentAdminDomain.java | 204 ++++++++++++++++++ .../fragment/admin/FragmentAdminReport.java | 4 +- .../android/viewmodel/mastodon/AdminVM.java | 68 ++++++ .../res/layout/activity_admin_actions.xml | 14 ++ .../main/res/layout/drawer_admin_domain.xml | 56 +++++ app/src/main/res/values/strings.xml | 1 + 17 files changed, 517 insertions(+), 26 deletions(-) rename app/src/main/java/app/fedilab/android/activities/{ => admin}/AdminAccountActivity.java (98%) rename app/src/main/java/app/fedilab/android/activities/{ => admin}/AdminActionActivity.java (88%) rename app/src/main/java/app/fedilab/android/activities/{ => admin}/AdminReportActivity.java (98%) rename app/src/main/java/app/fedilab/android/ui/drawer/{ => admin}/AdminAccountAdapter.java (97%) create mode 100644 app/src/main/java/app/fedilab/android/ui/drawer/admin/AdminDomainAdapter.java rename app/src/main/java/app/fedilab/android/ui/drawer/{ => admin}/ReportAdapter.java (98%) create mode 100644 app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java create mode 100644 app/src/main/res/layout/drawer_admin_domain.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 17bec2ac..8fb92343 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -201,7 +201,7 @@ android:configChanges="keyboardHidden|orientation|screenSize" android:label="@string/account" /> @@ -259,7 +259,7 @@ android:label="@string/interactions" android:theme="@style/AppThemeBar" /> diff --git a/app/src/main/java/app/fedilab/android/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/BaseMainActivity.java index 49d407c9..8fc1e428 100644 --- a/app/src/main/java/app/fedilab/android/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/BaseMainActivity.java @@ -95,7 +95,6 @@ import java.util.regex.Pattern; import app.fedilab.android.activities.AboutActivity; import app.fedilab.android.activities.ActionActivity; -import app.fedilab.android.activities.AdminActionActivity; import app.fedilab.android.activities.AnnouncementActivity; import app.fedilab.android.activities.BaseActivity; import app.fedilab.android.activities.CacheActivity; @@ -119,6 +118,7 @@ import app.fedilab.android.activities.SearchResultTabActivity; import app.fedilab.android.activities.SettingsActivity; import app.fedilab.android.activities.SuggestionActivity; import app.fedilab.android.activities.TrendsActivity; +import app.fedilab.android.activities.admin.AdminActionActivity; import app.fedilab.android.broadcastreceiver.NetworkStateReceiver; import app.fedilab.android.client.entities.api.Emoji; import app.fedilab.android.client.entities.api.EmojiInstance; diff --git a/app/src/main/java/app/fedilab/android/activities/AdminAccountActivity.java b/app/src/main/java/app/fedilab/android/activities/admin/AdminAccountActivity.java similarity index 98% rename from app/src/main/java/app/fedilab/android/activities/AdminAccountActivity.java rename to app/src/main/java/app/fedilab/android/activities/admin/AdminAccountActivity.java index 95b9fad9..c40cace7 100644 --- a/app/src/main/java/app/fedilab/android/activities/AdminAccountActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/admin/AdminAccountActivity.java @@ -1,4 +1,4 @@ -package app.fedilab.android.activities; +package app.fedilab.android.activities.admin; /* Copyright 2022 Thomas Schneider * * This file is a part of Fedilab @@ -54,8 +54,13 @@ import java.util.concurrent.TimeUnit; import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; +import app.fedilab.android.activities.BaseActivity; +import app.fedilab.android.activities.InstanceProfileActivity; +import app.fedilab.android.activities.MainActivity; +import app.fedilab.android.activities.MediaActivity; import app.fedilab.android.client.entities.api.Attachment; import app.fedilab.android.client.entities.api.admin.AdminAccount; +import app.fedilab.android.client.entities.api.admin.AdminIp; import app.fedilab.android.databinding.ActivityAdminAccountBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastodonHelper; @@ -204,7 +209,7 @@ public class AdminAccountActivity extends BaseActivity { StringBuilder lastActive = new StringBuilder(); if (adminAccount.ips != null) { int count = 0; - for (AdminAccount.IP ip : adminAccount.ips) { + for (AdminIp ip : adminAccount.ips) { lastActive.append(Helper.shortDateToString(ip.used_at)).append(" - ").append(ip.ip).append("\r\n"); count++; if (count > 4) { diff --git a/app/src/main/java/app/fedilab/android/activities/AdminActionActivity.java b/app/src/main/java/app/fedilab/android/activities/admin/AdminActionActivity.java similarity index 88% rename from app/src/main/java/app/fedilab/android/activities/AdminActionActivity.java rename to app/src/main/java/app/fedilab/android/activities/admin/AdminActionActivity.java index 30a3abab..db022ddc 100644 --- a/app/src/main/java/app/fedilab/android/activities/AdminActionActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/admin/AdminActionActivity.java @@ -1,4 +1,4 @@ -package app.fedilab.android.activities; +package app.fedilab.android.activities.admin; /* Copyright 2022 Thomas Schneider * * This file is a part of Fedilab @@ -14,7 +14,9 @@ package app.fedilab.android.activities; * You should have received a copy of the GNU General Public License along with Fedilab; if not, * see . */ -import static app.fedilab.android.activities.AdminActionActivity.AdminEnum.REPORT; +import static app.fedilab.android.activities.admin.AdminActionActivity.AdminEnum.ACCOUNT; +import static app.fedilab.android.activities.admin.AdminActionActivity.AdminEnum.DOMAIN; +import static app.fedilab.android.activities.admin.AdminActionActivity.AdminEnum.REPORT; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; @@ -31,12 +33,14 @@ import androidx.fragment.app.FragmentTransaction; import com.google.gson.annotations.SerializedName; import app.fedilab.android.R; +import app.fedilab.android.activities.BaseActivity; import app.fedilab.android.databinding.ActivityAdminActionsBinding; import app.fedilab.android.databinding.PopupAdminFilterAccountsBinding; import app.fedilab.android.databinding.PopupAdminFilterReportsBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.ThemeHelper; import app.fedilab.android.ui.fragment.admin.FragmentAdminAccount; +import app.fedilab.android.ui.fragment.admin.FragmentAdminDomain; import app.fedilab.android.ui.fragment.admin.FragmentAdminReport; public class AdminActionActivity extends BaseActivity { @@ -47,6 +51,7 @@ public class AdminActionActivity extends BaseActivity { private boolean canGoBack; private FragmentAdminReport fragmentAdminReport; private FragmentAdminAccount fragmentAdminAccount; + private FragmentAdminDomain fragmentAdminDomain; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -61,13 +66,13 @@ public class AdminActionActivity extends BaseActivity { } canGoBack = false; binding.reports.setOnClickListener(v -> displayTimeline(REPORT)); - binding.accounts.setOnClickListener(v -> displayTimeline(AdminEnum.ACCOUNT)); + binding.accounts.setOnClickListener(v -> displayTimeline(ACCOUNT)); + binding.domains.setOnClickListener(v -> displayTimeline(DOMAIN)); } private void displayTimeline(AdminEnum type) { canGoBack = true; if (type == REPORT) { - ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> { fragmentAdminReport = new FragmentAdminReport(); Bundle bundle = new Bundle(); @@ -80,9 +85,7 @@ public class AdminActionActivity extends BaseActivity { fragmentTransaction.replace(R.id.fragment_container, fragmentAdminReport); fragmentTransaction.commit(); }); - - } else { - + } else if (type == ACCOUNT) { ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> { fragmentAdminAccount = new FragmentAdminAccount(); Bundle bundle = new Bundle(); @@ -95,7 +98,19 @@ public class AdminActionActivity extends BaseActivity { fragmentTransaction.replace(R.id.fragment_container, fragmentAdminAccount); fragmentTransaction.commit(); }); - + } else if (type == DOMAIN) { + ThemeHelper.slideViewsToLeft(binding.buttonContainer, binding.fragmentContainer, () -> { + fragmentAdminDomain = new FragmentAdminDomain(); + Bundle bundle = new Bundle(); + bundle.putSerializable(Helper.ARG_TIMELINE_TYPE, type); + bundle.putString(Helper.ARG_VIEW_MODEL_KEY, "FEDILAB_" + type.getValue()); + fragmentAdminDomain.setArguments(bundle); + FragmentManager fragmentManager = getSupportFragmentManager(); + FragmentTransaction fragmentTransaction = + fragmentManager.beginTransaction(); + fragmentTransaction.replace(R.id.fragment_container, fragmentAdminDomain); + fragmentTransaction.commit(); + }); } switch (type) { case REPORT: @@ -104,6 +119,9 @@ public class AdminActionActivity extends BaseActivity { case ACCOUNT: setTitle(R.string.accounts); break; + case DOMAIN: + setTitle(R.string.domains); + break; } invalidateOptionsMenu(); } @@ -283,6 +301,9 @@ public class AdminActionActivity extends BaseActivity { if (fragmentAdminAccount != null) { fragmentAdminAccount.onDestroyView(); } + if (fragmentAdminDomain != null) { + fragmentAdminDomain.onDestroyView(); + } setTitle(R.string.administration); invalidateOptionsMenu(); }); @@ -296,8 +317,9 @@ public class AdminActionActivity extends BaseActivity { @SerializedName("REPORT") REPORT("REPORT"), @SerializedName("ACCOUNT") - ACCOUNT("ACCOUNT"); - + ACCOUNT("ACCOUNT"), + @SerializedName("DOMAIN") + DOMAIN("DOMAIN"); private final String value; AdminEnum(String value) { diff --git a/app/src/main/java/app/fedilab/android/activities/AdminReportActivity.java b/app/src/main/java/app/fedilab/android/activities/admin/AdminReportActivity.java similarity index 98% rename from app/src/main/java/app/fedilab/android/activities/AdminReportActivity.java rename to app/src/main/java/app/fedilab/android/activities/admin/AdminReportActivity.java index 6486f651..c74897c0 100644 --- a/app/src/main/java/app/fedilab/android/activities/AdminReportActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/admin/AdminReportActivity.java @@ -1,4 +1,4 @@ -package app.fedilab.android.activities; +package app.fedilab.android.activities.admin; /* Copyright 2022 Thomas Schneider * * This file is a part of Fedilab @@ -55,9 +55,13 @@ import java.util.concurrent.TimeUnit; import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; +import app.fedilab.android.activities.BaseActivity; +import app.fedilab.android.activities.InstanceProfileActivity; +import app.fedilab.android.activities.MediaActivity; import app.fedilab.android.client.entities.api.Account; import app.fedilab.android.client.entities.api.Attachment; import app.fedilab.android.client.entities.api.admin.AdminAccount; +import app.fedilab.android.client.entities.api.admin.AdminIp; import app.fedilab.android.databinding.ActivityAdminAccountBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastodonHelper; @@ -141,7 +145,7 @@ public class AdminReportActivity extends BaseActivity { binding.email.setText(adminAccount.email); StringBuilder lastActive = new StringBuilder(); if (adminAccount.ips != null) { - for (AdminAccount.IP ip : adminAccount.ips) { + for (AdminIp ip : adminAccount.ips) { lastActive.append(Helper.shortDateToString(ip.used_at)).append(" - ").append(ip.ip).append("\r\n"); } } diff --git a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java index 5d3aff27..ee57e8ec 100644 --- a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java +++ b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java @@ -164,12 +164,25 @@ public interface MastodonAdminService { @Query("limit") int limit ); + @GET("admin/domain_allows") + Call> getDomainAllows( + @Header("Authorization") String token, + @Query("max_id") String max_id, + @Query("limit") int limit + ); + @GET("admin/domain_blocks/{id}") Call getDomainBlock( @Header("Authorization") String token, @Path("id") String id ); + @GET("admin/domain_allows/{id}") + Call getDomainAllow( + @Header("Authorization") String token, + @Path("id") String id + ); + @FormUrlEncoded @POST("admin/domain_blocks") @@ -184,6 +197,13 @@ public interface MastodonAdminService { @Field("obfuscate") Boolean obfuscate ); + @FormUrlEncoded + @POST("admin/domain_allows") + Call allowDomain( + @Header("Authorization") String app_token, + @Path("domain") String domain + ); + @FormUrlEncoded @PUT("admin/domain_blocks") Call updateBlockDomain( @@ -202,4 +222,11 @@ public interface MastodonAdminService { @Header("Authorization") String app_token, @Path("id") String id ); + + + @DELETE("admin/domain_allows/{id}") + Call deleteAllowDomain( + @Header("Authorization") String app_token, + @Path("id") String id + ); } diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java index ddba4f6f..cb8cfe57 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java @@ -100,7 +100,6 @@ import java.util.regex.Pattern; import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; -import app.fedilab.android.activities.AdminAccountActivity; import app.fedilab.android.activities.ComposeActivity; import app.fedilab.android.activities.ContextActivity; import app.fedilab.android.activities.CustomSharingActivity; @@ -109,6 +108,7 @@ import app.fedilab.android.activities.ProfileActivity; import app.fedilab.android.activities.ReportActivity; import app.fedilab.android.activities.StatusHistoryActivity; import app.fedilab.android.activities.StatusInfoActivity; +import app.fedilab.android.activities.admin.AdminAccountActivity; import app.fedilab.android.client.entities.api.Attachment; import app.fedilab.android.client.entities.api.Poll; import app.fedilab.android.client.entities.api.Reaction; diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/AdminAccountAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/admin/AdminAccountAdapter.java similarity index 97% rename from app/src/main/java/app/fedilab/android/ui/drawer/AdminAccountAdapter.java rename to app/src/main/java/app/fedilab/android/ui/drawer/admin/AdminAccountAdapter.java index 99ab7635..16968e41 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/AdminAccountAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/admin/AdminAccountAdapter.java @@ -1,4 +1,4 @@ -package app.fedilab.android.ui.drawer; +package app.fedilab.android.ui.drawer.admin; /* Copyright 2022 Thomas Schneider * * This file is a part of Fedilab @@ -27,7 +27,7 @@ import androidx.recyclerview.widget.RecyclerView; import java.util.List; import java.util.Locale; -import app.fedilab.android.activities.AdminAccountActivity; +import app.fedilab.android.activities.admin.AdminAccountActivity; import app.fedilab.android.client.entities.api.admin.AdminAccount; import app.fedilab.android.databinding.DrawerAdminAccountBinding; import app.fedilab.android.helper.Helper; diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/admin/AdminDomainAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/admin/AdminDomainAdapter.java new file mode 100644 index 00000000..502a2488 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/ui/drawer/admin/AdminDomainAdapter.java @@ -0,0 +1,90 @@ +package app.fedilab.android.ui.drawer.admin; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + +import android.content.Context; +import android.view.LayoutInflater; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.recyclerview.widget.RecyclerView; + +import org.jetbrains.annotations.NotNull; + +import java.util.List; + +import app.fedilab.android.client.entities.api.admin.AdminDomainBlock; +import app.fedilab.android.databinding.DrawerAdminDomainBinding; +import app.fedilab.android.helper.Helper; + + +public class AdminDomainAdapter extends RecyclerView.Adapter { + + private final List adminDomainBlockList; + private Context context; + + + public AdminDomainAdapter(List adminDomainBlocks) { + this.adminDomainBlockList = adminDomainBlocks; + } + + @NotNull + @Override + public RecyclerView.ViewHolder onCreateViewHolder(@NotNull ViewGroup parent, int viewType) { + context = parent.getContext(); + DrawerAdminDomainBinding itemBinding = DrawerAdminDomainBinding.inflate(LayoutInflater.from(parent.getContext()), parent, false); + return new DomainViewHolder(itemBinding); + } + + @Override + public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int position) { + DomainViewHolder holder = (DomainViewHolder) viewHolder; + AdminDomainBlock adminDomainBlock = adminDomainBlockList.get(position); + + holder.binding.date.setText(Helper.shortDateToString(adminDomainBlock.created_at)); + holder.binding.title.setText(adminDomainBlock.domain); + holder.binding.severity.setText(adminDomainBlock.severity); + /*holder.binding.mainContainer.setOnClickListener(view -> { + Intent intent = new Intent(context, AccountReportActivity.class); + Bundle b = new Bundle(); + b.putSerializable(Helper.ARG_REPORT, report); + intent.putExtras(b); + context.startActivity(intent); + });*/ + + } + + @Override + public long getItemId(int position) { + return position; + } + + @Override + public int getItemCount() { + return adminDomainBlockList.size(); + } + + + public static class DomainViewHolder extends RecyclerView.ViewHolder { + DrawerAdminDomainBinding binding; + + DomainViewHolder(DrawerAdminDomainBinding itemView) { + super(itemView.getRoot()); + binding = itemView; + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/ReportAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/admin/ReportAdapter.java similarity index 98% rename from app/src/main/java/app/fedilab/android/ui/drawer/ReportAdapter.java rename to app/src/main/java/app/fedilab/android/ui/drawer/admin/ReportAdapter.java index 6a1fa987..34dedb56 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/ReportAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/admin/ReportAdapter.java @@ -1,4 +1,4 @@ -package app.fedilab.android.ui.drawer; +package app.fedilab.android.ui.drawer.admin; /* Copyright 2022 Thomas Schneider * * This file is a part of Fedilab diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminAccount.java b/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminAccount.java index 6e3663c1..5400a711 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminAccount.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminAccount.java @@ -33,14 +33,14 @@ import java.util.List; import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; -import app.fedilab.android.activities.AdminActionActivity; +import app.fedilab.android.activities.admin.AdminActionActivity; import app.fedilab.android.client.entities.api.admin.AdminAccount; import app.fedilab.android.client.entities.api.admin.AdminAccounts; import app.fedilab.android.databinding.FragmentPaginationBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.MastodonHelper; import app.fedilab.android.helper.ThemeHelper; -import app.fedilab.android.ui.drawer.AdminAccountAdapter; +import app.fedilab.android.ui.drawer.admin.AdminAccountAdapter; import app.fedilab.android.viewmodel.mastodon.AdminVM; diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java b/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java new file mode 100644 index 00000000..f146c0fb --- /dev/null +++ b/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java @@ -0,0 +1,204 @@ +package app.fedilab.android.ui.fragment.admin; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + + +import android.os.Bundle; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; + +import androidx.annotation.NonNull; +import androidx.annotation.Nullable; +import androidx.fragment.app.Fragment; +import androidx.lifecycle.ViewModelProvider; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; + +import java.util.ArrayList; +import java.util.List; + +import app.fedilab.android.BaseMainActivity; +import app.fedilab.android.R; +import app.fedilab.android.client.entities.api.admin.AdminDomainBlock; +import app.fedilab.android.client.entities.api.admin.AdminDomainBlocks; +import app.fedilab.android.databinding.FragmentPaginationBinding; +import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.ThemeHelper; +import app.fedilab.android.ui.drawer.admin.AdminDomainAdapter; +import app.fedilab.android.viewmodel.mastodon.AdminVM; + + +public class FragmentAdminDomain extends Fragment { + + + private FragmentPaginationBinding binding; + private AdminVM adminVM; + private boolean flagLoading; + private List adminDomainBlocks; + private String max_id, min_id; + private AdminDomainAdapter adminDomainAdapter; + private LinearLayoutManager mLayoutManager; + private String viewModelKey; + + public void scrollToTop() { + if (binding != null) { + binding.recyclerView.scrollToPosition(0); + } + } + + public View onCreateView(@NonNull LayoutInflater inflater, + ViewGroup container, Bundle savedInstanceState) { + + if (getArguments() != null) { + viewModelKey = getArguments().getString(Helper.ARG_VIEW_MODEL_KEY, ""); + } + + binding = FragmentPaginationBinding.inflate(inflater, container, false); + binding.getRoot().setBackgroundColor(ThemeHelper.getBackgroundColor(requireActivity())); + + int c1 = getResources().getColor(R.color.cyanea_accent_reference); + binding.swipeContainer.setProgressBackgroundColorSchemeColor(getResources().getColor(R.color.cyanea_primary_reference)); + binding.swipeContainer.setColorSchemeColors( + c1, c1, c1 + ); + + adminVM = new ViewModelProvider(FragmentAdminDomain.this).get(viewModelKey, AdminVM.class); + + binding.loader.setVisibility(View.VISIBLE); + binding.recyclerView.setVisibility(View.GONE); + flagLoading = false; + adminVM.getDomainBlocks( + BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null) + .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); + return binding.getRoot(); + } + + @Override + public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { + super.onViewCreated(view, savedInstanceState); + } + + /** + * Intialize the common view for domain block on different timelines + * + * @param adminDomainBlocks {@link AdminDomainBlocks} + */ + private void initializeStatusesCommonView(final AdminDomainBlocks adminDomainBlocks) { + if (binding == null || !isAdded() || getActivity() == null) { + return; + } + binding.loader.setVisibility(View.GONE); + binding.noAction.setVisibility(View.GONE); + binding.swipeContainer.setRefreshing(false); + binding.swipeContainer.setOnRefreshListener(() -> { + binding.swipeContainer.setRefreshing(true); + max_id = null; + flagLoading = false; + adminVM.getDomainBlocks( + BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null) + .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); + }); + + if (adminDomainBlocks == null || adminDomainBlocks.adminDomainBlocks == null || adminDomainBlocks.adminDomainBlocks.size() == 0) { + binding.noAction.setVisibility(View.VISIBLE); + return; + } + flagLoading = adminDomainBlocks.pagination.max_id == null; + binding.recyclerView.setVisibility(View.VISIBLE); + if (adminDomainAdapter != null && this.adminDomainBlocks != null) { + int size = this.adminDomainBlocks.size(); + this.adminDomainBlocks.clear(); + this.adminDomainBlocks = new ArrayList<>(); + adminDomainAdapter.notifyItemRangeRemoved(0, size); + } + if (this.adminDomainBlocks == null) { + this.adminDomainBlocks = new ArrayList<>(); + } + this.adminDomainBlocks.addAll(adminDomainBlocks.adminDomainBlocks); + + if (max_id == null || (adminDomainBlocks.pagination.max_id != null && Helper.compareTo(adminDomainBlocks.pagination.max_id, max_id) < 0)) { + max_id = adminDomainBlocks.pagination.max_id; + } + if (min_id == null || (adminDomainBlocks.pagination.max_id != null && Helper.compareTo(adminDomainBlocks.pagination.min_id, min_id) > 0)) { + min_id = adminDomainBlocks.pagination.min_id; + } + + adminDomainAdapter = new AdminDomainAdapter(adminDomainBlocks.adminDomainBlocks); + + mLayoutManager = new LinearLayoutManager(requireActivity()); + binding.recyclerView.setLayoutManager(mLayoutManager); + binding.recyclerView.setAdapter(adminDomainAdapter); + DividerItemDecoration dividerItemDecoration = new DividerItemDecoration(binding.recyclerView.getContext(), + mLayoutManager.getOrientation()); + binding.recyclerView.addItemDecoration(dividerItemDecoration); + binding.recyclerView.addOnScrollListener(new RecyclerView.OnScrollListener() { + @Override + public void onScrolled(@NonNull RecyclerView recyclerView, int dx, int dy) { + if (requireActivity() instanceof BaseMainActivity) { + if (dy < 0 && !((BaseMainActivity) requireActivity()).getFloatingVisibility()) + ((BaseMainActivity) requireActivity()).manageFloatingButton(true); + if (dy > 0 && ((BaseMainActivity) requireActivity()).getFloatingVisibility()) + ((BaseMainActivity) requireActivity()).manageFloatingButton(false); + } + int firstVisibleItem = mLayoutManager.findFirstVisibleItemPosition(); + if (dy > 0) { + int visibleItemCount = mLayoutManager.getChildCount(); + int totalItemCount = mLayoutManager.getItemCount(); + if (firstVisibleItem + visibleItemCount == totalItemCount) { + if (!flagLoading) { + flagLoading = true; + binding.loadingNextElements.setVisibility(View.VISIBLE); + adminVM.getDomainAllows( + BaseMainActivity.currentInstance, BaseMainActivity.currentToken, max_id) + .observe(getViewLifecycleOwner(), adminDomainBlocks1 -> dealWithPagination(adminDomainBlocks1)); + } + } else { + binding.loadingNextElements.setVisibility(View.GONE); + } + } + } + }); + } + + /** + * Update view and pagination when scrolling down + * + * @param admDomainBlocks AdminDomainBlocks + */ + private void dealWithPagination(AdminDomainBlocks admDomainBlocks) { + + if (binding == null || !isAdded() || getActivity() == null) { + return; + } + binding.loadingNextElements.setVisibility(View.GONE); + if (this.adminDomainBlocks != null && admDomainBlocks != null && admDomainBlocks.adminDomainBlocks != null && admDomainBlocks.adminDomainBlocks.size() > 0) { + flagLoading = admDomainBlocks.pagination.max_id == null; + //There are some adminDomainBlocks present in the timeline + int startId = this.adminDomainBlocks.size(); + this.adminDomainBlocks.addAll(admDomainBlocks.adminDomainBlocks); + adminDomainAdapter.notifyItemRangeInserted(startId, admDomainBlocks.adminDomainBlocks.size()); + if (max_id == null || (admDomainBlocks.pagination.max_id != null && Helper.compareTo(admDomainBlocks.pagination.max_id, max_id) < 0)) { + max_id = admDomainBlocks.pagination.max_id; + } + if (min_id == null || (admDomainBlocks.pagination.min_id != null && Helper.compareTo(admDomainBlocks.pagination.min_id, min_id) > 0)) { + min_id = admDomainBlocks.pagination.min_id; + } + } + } + + +} \ No newline at end of file diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminReport.java b/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminReport.java index 6a44c743..5f8536c4 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminReport.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminReport.java @@ -33,13 +33,13 @@ import java.util.List; import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; -import app.fedilab.android.activities.AdminActionActivity; +import app.fedilab.android.activities.admin.AdminActionActivity; import app.fedilab.android.client.entities.api.admin.AdminReport; import app.fedilab.android.client.entities.api.admin.AdminReports; import app.fedilab.android.databinding.FragmentPaginationBinding; import app.fedilab.android.helper.Helper; import app.fedilab.android.helper.ThemeHelper; -import app.fedilab.android.ui.drawer.ReportAdapter; +import app.fedilab.android.ui.drawer.admin.ReportAdapter; import app.fedilab.android.viewmodel.mastodon.AdminVM; diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java index 11568c0a..9f3d17b3 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java @@ -630,4 +630,72 @@ public class AdminVM extends AndroidViewModel { }).start(); return adminDomainBlockMutableLiveData; } + + + /** + * View all allowed domains. + * + * @param instance Instance domain of the active account + * @param token Access token of the active account + * @return {@link LiveData} containing a {@link List} of {@link AdminDomainBlocks}s + */ + public LiveData getDomainAllows(@NonNull String instance, + String token, + String max_id) { + MastodonAdminService mastodonAdminService = init(instance); + adminDomainBlockListMutableLiveData = new MutableLiveData<>(); + new Thread(() -> { + List adminDomainBlockList; + Call> getDomainBlocks = mastodonAdminService.getDomainAllows(token, max_id, MastodonHelper.statusesPerCall(getApplication())); + AdminDomainBlocks adminDomainBlocks = new AdminDomainBlocks(); + if (getDomainBlocks != null) { + try { + Response> getDomainBlocksResponse = getDomainBlocks.execute(); + if (getDomainBlocksResponse.isSuccessful()) { + adminDomainBlocks.adminDomainBlocks = getDomainBlocksResponse.body(); + adminDomainBlocks.pagination = MastodonHelper.getPagination(getDomainBlocksResponse.headers()); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> adminDomainBlockListMutableLiveData.setValue(adminDomainBlocks); + mainHandler.post(myRunnable); + }).start(); + return adminDomainBlockListMutableLiveData; + } + + /** + * View a single allowed domain + * + * @param instance Instance domain of the active account + * @param token Access token of the active account + * @return {@link LiveData} containing a {@link List} of {@link AdminDomainBlocks}s + */ + public LiveData getDomainAllow(@NonNull String instance, + String token, + String id) { + MastodonAdminService mastodonAdminService = init(instance); + adminDomainBlockMutableLiveData = new MutableLiveData<>(); + new Thread(() -> { + AdminDomainBlock adminDomainBlock = null; + Call getDomainBlock = mastodonAdminService.getDomainAllow(token, id); + if (getDomainBlock != null) { + try { + Response getDomainBlocksResponse = getDomainBlock.execute(); + if (getDomainBlocksResponse.isSuccessful()) { + adminDomainBlock = getDomainBlocksResponse.body(); + } + } catch (Exception e) { + e.printStackTrace(); + } + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + AdminDomainBlock finalAdminDomainBlock = adminDomainBlock; + Runnable myRunnable = () -> adminDomainBlockMutableLiveData.setValue(finalAdminDomainBlock); + mainHandler.post(myRunnable); + }).start(); + return adminDomainBlockMutableLiveData; + } } diff --git a/app/src/main/res/layout/activity_admin_actions.xml b/app/src/main/res/layout/activity_admin_actions.xml index f6d7b102..79ea3386 100644 --- a/app/src/main/res/layout/activity_admin_actions.xml +++ b/app/src/main/res/layout/activity_admin_actions.xml @@ -41,6 +41,20 @@ app:iconTint="@color/cyanea_accent_dark_reference" app:strokeColor="@color/cyanea_accent_dark_reference" /> + diff --git a/app/src/main/res/layout/drawer_admin_domain.xml b/app/src/main/res/layout/drawer_admin_domain.xml new file mode 100644 index 00000000..57ea5204 --- /dev/null +++ b/app/src/main/res/layout/drawer_admin_domain.xml @@ -0,0 +1,56 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 9d8b5255..ae5912e8 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1914,4 +1914,5 @@ Remove pinned timeline? Are you sure to unpin that timeline? Delete the pinned timelines? + Domains \ No newline at end of file From 0b690838bbbb79d4cc35555e238bb15a3056b9d3 Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 24 Nov 2022 16:44:24 +0100 Subject: [PATCH 40/61] Fix issue #533 - Add an option to keep notifications when muting --- .../android/activities/ProfileActivity.java | 93 +++++++++++-------- .../android/ui/drawer/StatusAdapter.java | 10 +- app/src/main/res/values/strings.xml | 1 + 3 files changed, 61 insertions(+), 43 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java b/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java index e2b97c1c..24f350b6 100644 --- a/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java @@ -695,7 +695,6 @@ public class ProfileActivity extends BaseActivity { splitAcct = account.acct.split("@"); } SharedPreferences sharedpreferences = PreferenceManager.getDefaultSharedPreferences(ProfileActivity.this); - AlertDialog.Builder builderInner = null; final boolean isOwner = account != null && account.id != null && BaseMainActivity.currentUserID != null && account.id.compareToIgnoreCase(BaseMainActivity.currentUserID) == 0; final String[] stringArrayConf; if (isOwner) { @@ -920,21 +919,43 @@ public class ProfileActivity extends BaseActivity { startActivity(intent); return true; } else if (itemId == R.id.action_mute) { - + AlertDialog.Builder builderInner; if (relationship != null) { - if (relationship.muting) { - builderInner = new AlertDialog.Builder(ProfileActivity.this, Helper.dialogStyle()); - builderInner.setTitle(stringArrayConf[4]); - doActionAccount = action.UNMUTE; + String target; + if (item.getItemId() == R.id.action_block_instance) { + target = account.acct.split("@")[1]; } else { - builderInner = new AlertDialog.Builder(ProfileActivity.this, Helper.dialogStyle()); - builderInner.setTitle(stringArrayConf[0]); - doActionAccount = action.MUTE; + target = account.id; } - } else { - doActionAccount = action.NOTHING; - } + if (relationship.muting) { + accountsVM.unmute(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target) + .observe(ProfileActivity.this, relationShip -> { + this.relationship = relationShip; + updateAccount(); + }); + return true; + } + builderInner = new AlertDialog.Builder(ProfileActivity.this, Helper.dialogStyle()); + builderInner.setTitle(stringArrayConf[0]); + builderInner.setNeutralButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); + builderInner.setNegativeButton(R.string.keep_notifications, (dialog, which) -> { + accountsVM.mute(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target, false, 0) + .observe(ProfileActivity.this, relationShip -> { + this.relationship = relationShip; + updateAccount(); + }); + }); + builderInner.setPositiveButton(R.string.action_mute, (dialog, which) -> { + accountsVM.mute(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target, true, 0) + .observe(ProfileActivity.this, relationShip -> { + this.relationship = relationShip; + updateAccount(); + }); + dialog.dismiss(); + }); + builderInner.show(); + } } else if (itemId == R.id.action_timed_mute) { MastodonHelper.scheduleBoost(ProfileActivity.this, MastodonHelper.ScheduleType.TIMED_MUTED, null, account, rs -> { this.relationship = rs; @@ -942,7 +963,7 @@ public class ProfileActivity extends BaseActivity { }); return true; } else if (itemId == R.id.action_report) { - builderInner = new AlertDialog.Builder(ProfileActivity.this, Helper.dialogStyle()); + AlertDialog.Builder builderInner = new AlertDialog.Builder(ProfileActivity.this, Helper.dialogStyle()); builderInner.setTitle(R.string.report_account); //Text for report EditText input = new EditText(ProfileActivity.this); @@ -962,7 +983,7 @@ public class ProfileActivity extends BaseActivity { builderInner.show(); return true; } else if (itemId == R.id.action_block) { - builderInner = new AlertDialog.Builder(ProfileActivity.this, Helper.dialogStyle()); + AlertDialog.Builder builderInner = new AlertDialog.Builder(ProfileActivity.this, Helper.dialogStyle()); if (relationship != null) { if (relationship.blocking) { builderInner.setTitle(stringArrayConf[5]); @@ -974,15 +995,6 @@ public class ProfileActivity extends BaseActivity { } else { doActionAccount = action.NOTHING; } - } else if (itemId == R.id.action_block_instance) { - builderInner = new AlertDialog.Builder(ProfileActivity.this, Helper.dialogStyle()); - doActionAccount = action.BLOCK_DOMAIN; - String domain = account.acct.split("@")[1]; - builderInner.setMessage(getString(R.string.block_domain_confirm_message, domain)); - } else { - return true; - } - if (doAction != action.NOTHING && builderInner != null) { builderInner.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); builderInner.setPositiveButton(R.string.yes, (dialog, which) -> { String target; @@ -992,20 +1004,6 @@ public class ProfileActivity extends BaseActivity { target = account.id; } switch (doActionAccount) { - case MUTE: - accountsVM.mute(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target, true, 0) - .observe(ProfileActivity.this, relationShip -> { - this.relationship = relationShip; - updateAccount(); - }); - break; - case UNMUTE: - accountsVM.unmute(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target) - .observe(ProfileActivity.this, relationShip -> { - this.relationship = relationShip; - updateAccount(); - }); - break; case BLOCK: accountsVM.block(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target) .observe(ProfileActivity.this, relationShip -> { @@ -1020,13 +1018,28 @@ public class ProfileActivity extends BaseActivity { updateAccount(); }); break; - case BLOCK_DOMAIN: - accountsVM.addDomainBlocks(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target); - break; } dialog.dismiss(); }); builderInner.show(); + } else if (itemId == R.id.action_block_instance) { + AlertDialog.Builder builderInner = new AlertDialog.Builder(ProfileActivity.this, Helper.dialogStyle()); + String domain = account.acct.split("@")[1]; + builderInner.setMessage(getString(R.string.block_domain_confirm_message, domain)); + builderInner.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); + builderInner.setPositiveButton(R.string.yes, (dialog, which) -> { + String target; + if (item.getItemId() == R.id.action_block_instance) { + target = account.acct.split("@")[1]; + } else { + target = account.id; + } + accountsVM.addDomainBlocks(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, target); + dialog.dismiss(); + }); + builderInner.show(); + } else { + return true; } return true; } diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java index cb8cfe57..75c5f853 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java @@ -1863,14 +1863,18 @@ public class StatusAdapter extends RecyclerView.Adapter AlertDialog.Builder builderInner = new AlertDialog.Builder(context, Helper.dialogStyle()); builderInner.setTitle(stringArrayConf[0]); builderInner.setMessage(statusToDeal.account.acct); - builderInner.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); - builderInner.setPositiveButton(R.string.yes, (dialog, which) -> accountsVM.mute(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.account.id, null, null) + builderInner.setNeutralButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); + builderInner.setNegativeButton(R.string.keep_notifications, (dialog, which) -> accountsVM.mute(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.account.id, false, null) + .observe((LifecycleOwner) context, relationShip -> { + sendAction(context, Helper.ARG_STATUS_ACCOUNT_ID_DELETED, null, statusToDeal.account.id); + Toasty.info(context, context.getString(R.string.toast_mute), Toasty.LENGTH_LONG).show(); + })); + builderInner.setPositiveButton(R.string.action_mute, (dialog, which) -> accountsVM.mute(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.account.id, null, null) .observe((LifecycleOwner) context, relationShip -> { sendAction(context, Helper.ARG_STATUS_ACCOUNT_ID_DELETED, null, statusToDeal.account.id); Toasty.info(context, context.getString(R.string.toast_mute), Toasty.LENGTH_LONG).show(); })); builderInner.show(); - } else if (itemId == R.id.action_mute_conversation) { if (statusToDeal.muted) { statusesVM.unMute(BaseMainActivity.currentInstance, BaseMainActivity.currentToken, statusToDeal.id).observe((LifecycleOwner) context, status1 -> Toasty.info(context, context.getString(R.string.toast_unmute_conversation)).show()); diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index ae5912e8..c8cd1be2 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1915,4 +1915,5 @@ Are you sure to unpin that timeline? Delete the pinned timelines? Domains + Keep notifications \ No newline at end of file From f9b87f762b1b787a11db26dc802d1a99838893ea Mon Sep 17 00:00:00 2001 From: Thomas Date: Thu, 24 Nov 2022 17:33:20 +0100 Subject: [PATCH 41/61] Fix issue #534 - Allow to filter notifications for admin/moderators --- .../app/fedilab/android/helper/Helper.java | 15 +++ .../android/helper/NotificationsHelper.java | 118 ++++++++++++------ .../FragmentNotificationContainer.java | 18 +++ .../FedilabNotificationPageAdapter.java | 8 +- .../ic_baseline_person_add_alt_1_24.xml | 2 +- .../res/drawable/ic_baseline_report_24.xml | 10 ++ .../layout/popup_notification_settings.xml | 16 +++ app/src/main/res/values/strings.xml | 16 ++- app/src/main/res/xml/pref_notifications.xml | 18 +++ 9 files changed, 181 insertions(+), 40 deletions(-) create mode 100644 app/src/main/res/drawable/ic_baseline_report_24.xml diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java index aa73e044..3c2ecf29 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -1528,6 +1528,18 @@ public class Helper { channelId = "channel_status"; channelTitle = context.getString(R.string.channel_notif_status); break; + case UPDATE: + channelId = "channel_update"; + channelTitle = context.getString(R.string.channel_notif_update); + break; + case SIGN_UP: + channelId = "channel_signup"; + channelTitle = context.getString(R.string.channel_notif_signup); + break; + case REPORT: + channelId = "channel_report"; + channelTitle = context.getString(R.string.channel_notif_report); + break; default: channelId = "channel_boost"; channelTitle = context.getString(R.string.channel_notif_boost); @@ -1989,6 +2001,9 @@ public class Helper { BOOST, FAV, POLL, + UPDATE, + SIGN_UP, + REPORT, STATUS, BACKUP, STORE, diff --git a/app/src/main/java/app/fedilab/android/helper/NotificationsHelper.java b/app/src/main/java/app/fedilab/android/helper/NotificationsHelper.java index 57675c2a..18a81cfb 100644 --- a/app/src/main/java/app/fedilab/android/helper/NotificationsHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/NotificationsHelper.java @@ -158,6 +158,9 @@ public class NotificationsHelper { boolean notif_poll = prefs.getBoolean(context.getString(R.string.SET_NOTIF_POLL), true); boolean notif_fav = prefs.getBoolean(context.getString(R.string.SET_NOTIF_FAVOURITE), true); boolean notif_status = prefs.getBoolean(context.getString(R.string.SET_NOTIF_STATUS), true); + boolean notif_update = prefs.getBoolean(context.getString(R.string.SET_NOTIF_UPDATE), true); + boolean notif_signup = prefs.getBoolean(context.getString(R.string.SET_NOTIF_ADMIN_SIGNUP), true); + boolean notif_report = prefs.getBoolean(context.getString(R.string.SET_NOTIF_ADMIN_REPORT), true); final String max_id = prefs.getString(context.getString(R.string.LAST_NOTIFICATION_ID) + key, null); @@ -232,18 +235,18 @@ public class NotificationsHelper { title = String.format("%s %s", notification.account.display_name, context.getString(R.string.notif_reblog)); else title = String.format("@%s %s", notification.account.acct, context.getString(R.string.notif_reblog)); - } - if (notification.status != null) { - if (notification.status.spoiler_text != null && notification.status.spoiler_text.length() > 0) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = new SpannableString(Html.fromHtml(notification.status.spoiler_text, FROM_HTML_MODE_LEGACY)).toString(); - else - message = new SpannableString(Html.fromHtml(notification.status.spoiler_text)).toString(); - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = new SpannableString(Html.fromHtml(notification.status.content, FROM_HTML_MODE_LEGACY)).toString(); - else - message = new SpannableString(Html.fromHtml(notification.status.content)).toString(); + if (notification.status != null) { + if (notification.status.spoiler_text != null && notification.status.spoiler_text.length() > 0) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + message = new SpannableString(Html.fromHtml(notification.status.spoiler_text, FROM_HTML_MODE_LEGACY)).toString(); + else + message = new SpannableString(Html.fromHtml(notification.status.spoiler_text)).toString(); + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + message = new SpannableString(Html.fromHtml(notification.status.content, FROM_HTML_MODE_LEGACY)).toString(); + else + message = new SpannableString(Html.fromHtml(notification.status.content)).toString(); + } } } break; @@ -254,18 +257,18 @@ public class NotificationsHelper { title = String.format("%s %s", notification.account.display_name, context.getString(R.string.notif_favourite)); else title = String.format("@%s %s", notification.account.acct, context.getString(R.string.notif_favourite)); - } - if (notification.status != null) { - if (notification.status.spoiler_text != null && notification.status.spoiler_text.length() > 0) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = new SpannableString(Html.fromHtml(notification.status.spoiler_text, FROM_HTML_MODE_LEGACY)).toString(); - else - message = new SpannableString(Html.fromHtml(notification.status.spoiler_text)).toString(); - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = new SpannableString(Html.fromHtml(notification.status.content, FROM_HTML_MODE_LEGACY)).toString(); - else - message = new SpannableString(Html.fromHtml(notification.status.content)).toString(); + if (notification.status != null) { + if (notification.status.spoiler_text != null && notification.status.spoiler_text.length() > 0) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + message = new SpannableString(Html.fromHtml(notification.status.spoiler_text, FROM_HTML_MODE_LEGACY)).toString(); + else + message = new SpannableString(Html.fromHtml(notification.status.spoiler_text)).toString(); + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + message = new SpannableString(Html.fromHtml(notification.status.content, FROM_HTML_MODE_LEGACY)).toString(); + else + message = new SpannableString(Html.fromHtml(notification.status.content)).toString(); + } } } break; @@ -298,21 +301,62 @@ public class NotificationsHelper { title = context.getString(R.string.notif_poll_self); else title = context.getString(R.string.notif_poll); - } - if (notification.status != null) { - if (notification.status.spoiler_text != null && notification.status.spoiler_text.length() > 0) { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = new SpannableString(Html.fromHtml(notification.status.spoiler_text, FROM_HTML_MODE_LEGACY)).toString(); - else - message = new SpannableString(Html.fromHtml(notification.status.spoiler_text)).toString(); - } else { - if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) - message = new SpannableString(Html.fromHtml(notification.status.content, FROM_HTML_MODE_LEGACY)).toString(); - else - message = new SpannableString(Html.fromHtml(notification.status.content)).toString(); + if (notification.status != null) { + if (notification.status.spoiler_text != null && notification.status.spoiler_text.length() > 0) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + message = new SpannableString(Html.fromHtml(notification.status.spoiler_text, FROM_HTML_MODE_LEGACY)).toString(); + else + message = new SpannableString(Html.fromHtml(notification.status.spoiler_text)).toString(); + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + message = new SpannableString(Html.fromHtml(notification.status.content, FROM_HTML_MODE_LEGACY)).toString(); + else + message = new SpannableString(Html.fromHtml(notification.status.content)).toString(); + } } } break; + case "update": + notifType = Helper.NotifType.UPDATE; + if (notif_update) { + title = context.getString(R.string.notif_update_push); + if (notification.status != null) { + if (notification.status.spoiler_text != null && notification.status.spoiler_text.length() > 0) { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + message = new SpannableString(Html.fromHtml(notification.status.spoiler_text, FROM_HTML_MODE_LEGACY)).toString(); + else + message = new SpannableString(Html.fromHtml(notification.status.spoiler_text)).toString(); + } else { + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + message = new SpannableString(Html.fromHtml(notification.status.content, FROM_HTML_MODE_LEGACY)).toString(); + else + message = new SpannableString(Html.fromHtml(notification.status.content)).toString(); + } + } + } + break; + case "admin.sign_up": + notifType = Helper.NotifType.SIGN_UP; + if (notif_signup) { + title = context.getString(R.string.notif_sign_up); + if (notification.account.display_name != null && notification.account.display_name.length() > 0) + message = String.format("%s %s", notification.account.display_name, context.getString(R.string.notif_signed_up)); + else + message = String.format("@%s %s", notification.account.acct, context.getString(R.string.notif_signed_up)); + targeted_account = notification.account.id; + } + break; + case "admin.report": + notifType = Helper.NotifType.REPORT; + if (notif_report) { + title = context.getString(R.string.notif_report); + if (notification.account.display_name != null && notification.account.display_name.length() > 0) + message = String.format("%s %s", notification.account.display_name, context.getString(R.string.notif_reported)); + else + message = String.format("@%s %s", notification.account.acct, context.getString(R.string.notif_reported)); + targeted_account = notification.account.id; + } + break; default: } if (message != null) { @@ -321,7 +365,7 @@ public class NotificationsHelper { intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK); intent.putExtra(Helper.INTENT_ACTION, Helper.NOTIFICATION_INTENT); intent.putExtra(Helper.PREF_KEY_ID, account.user_id); - if (targeted_account != null && notifType == Helper.NotifType.FOLLLOW) + if (targeted_account != null) intent.putExtra(Helper.INTENT_TARGETED_ACCOUNT, targeted_account); intent.putExtra(Helper.PREF_INSTANCE, account.instance); notificationUrl = notification.account.avatar; diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentNotificationContainer.java b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentNotificationContainer.java index 354a6b50..97e0b50f 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentNotificationContainer.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/timeline/FragmentNotificationContainer.java @@ -83,6 +83,8 @@ public class FragmentNotificationContainer extends Fragment { binding.tabLayout.addTab(binding.tabLayout.newTab().setIcon(R.drawable.ic_baseline_home_24)); binding.tabLayout.addTab(binding.tabLayout.newTab().setIcon(R.drawable.ic_baseline_person_add_alt_1_24)); binding.tabLayout.addTab(binding.tabLayout.newTab().setIcon(R.drawable.ic_baseline_edit_24)); + binding.tabLayout.addTab(binding.tabLayout.newTab().setIcon(R.drawable.ic_baseline_person_add_alt_1_24)); + binding.tabLayout.addTab(binding.tabLayout.newTab().setIcon(R.drawable.ic_baseline_report_24)); binding.viewpagerNotificationContainer.setAdapter(new FedilabNotificationPageAdapter(getChildFragmentManager(), true)); } AtomicBoolean changes = new AtomicBoolean(false); @@ -98,6 +100,8 @@ public class FragmentNotificationContainer extends Fragment { ThemeHelper.changeButtonColor(requireActivity(), dialogView.displayUpdatesFromPeople); ThemeHelper.changeButtonColor(requireActivity(), dialogView.displayFollows); ThemeHelper.changeButtonColor(requireActivity(), dialogView.displayUpdates); + ThemeHelper.changeButtonColor(requireActivity(), dialogView.displaySignups); + ThemeHelper.changeButtonColor(requireActivity(), dialogView.displayReports); DrawableCompat.setTintList(DrawableCompat.wrap(dialogView.displayAllCategories.getThumbDrawable()), ThemeHelper.getSwitchCompatThumbDrawable(requireActivity())); DrawableCompat.setTintList(DrawableCompat.wrap(dialogView.displayAllCategories.getTrackDrawable()), ThemeHelper.getSwitchCompatTrackDrawable(requireActivity())); @@ -134,6 +138,8 @@ public class FragmentNotificationContainer extends Fragment { dialogView.displayUpdatesFromPeople.setChecked(true); dialogView.displayFollows.setChecked(true); dialogView.displayUpdates.setChecked(true); + dialogView.displaySignups.setChecked(true); + dialogView.displayReports.setChecked(true); String excludedCategories = sharedpreferences.getString(getString(R.string.SET_EXCLUDED_NOTIFICATIONS_TYPE) + BaseMainActivity.currentUserID + BaseMainActivity.currentInstance, null); List excludedCategoriesList = new ArrayList<>(); if (excludedCategories != null) { @@ -168,6 +174,14 @@ public class FragmentNotificationContainer extends Fragment { excludedCategoriesList.add("update"); dialogView.displayUpdates.setChecked(false); break; + case "admin.sign_up": + excludedCategoriesList.add("admin.sign_up"); + dialogView.displaySignups.setChecked(false); + break; + case "admin.report": + excludedCategoriesList.add("admin.report"); + dialogView.displayReports.setChecked(false); + break; } } } @@ -188,6 +202,10 @@ public class FragmentNotificationContainer extends Fragment { notificationType = "follow"; } else if (checkedId == R.id.display_updates) { notificationType = "update"; + } else if (checkedId == R.id.display_signups) { + notificationType = "admin.sign_up"; + } else if (checkedId == R.id.display_reports) { + notificationType = "admin.report"; } if (isChecked) { excludedCategoriesList.remove(notificationType); diff --git a/app/src/main/java/app/fedilab/android/ui/pageadapter/FedilabNotificationPageAdapter.java b/app/src/main/java/app/fedilab/android/ui/pageadapter/FedilabNotificationPageAdapter.java index edd50f9c..b9ec991f 100644 --- a/app/src/main/java/app/fedilab/android/ui/pageadapter/FedilabNotificationPageAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/pageadapter/FedilabNotificationPageAdapter.java @@ -86,6 +86,12 @@ public class FedilabNotificationPageAdapter extends FragmentStatePagerAdapter { case 7: bundle.putSerializable(Helper.ARG_NOTIFICATION_TYPE, FragmentMastodonNotification.NotificationTypeEnum.UPDATES); break; + case 8: + bundle.putSerializable(Helper.ARG_NOTIFICATION_TYPE, FragmentMastodonNotification.NotificationTypeEnum.ADMIN_SIGNUP); + break; + case 9: + bundle.putSerializable(Helper.ARG_NOTIFICATION_TYPE, FragmentMastodonNotification.NotificationTypeEnum.ADMIN_REPORT); + break; } } fragmentMastodonNotification.setArguments(bundle); @@ -94,6 +100,6 @@ public class FedilabNotificationPageAdapter extends FragmentStatePagerAdapter { @Override public int getCount() { - return extended ? 8 : 2; + return extended ? 10 : 2; } } \ No newline at end of file diff --git a/app/src/main/res/drawable/ic_baseline_person_add_alt_1_24.xml b/app/src/main/res/drawable/ic_baseline_person_add_alt_1_24.xml index 11162198..33fa9bd3 100644 --- a/app/src/main/res/drawable/ic_baseline_person_add_alt_1_24.xml +++ b/app/src/main/res/drawable/ic_baseline_person_add_alt_1_24.xml @@ -1,7 +1,7 @@ + + diff --git a/app/src/main/res/layout/popup_notification_settings.xml b/app/src/main/res/layout/popup_notification_settings.xml index 330dcdfa..636f6fc0 100644 --- a/app/src/main/res/layout/popup_notification_settings.xml +++ b/app/src/main/res/layout/popup_notification_settings.xml @@ -109,6 +109,22 @@ android:layout_height="wrap_content" android:text="@string/notif_display_updates" app:icon="@drawable/ic_baseline_edit_24" /> + + + + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index c8cd1be2..b4f71fbf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -320,6 +320,9 @@ Poll Ended Messages Backup New posts + New update + New sign-up + New report Media Download Select Tone Enable time slot @@ -404,6 +407,9 @@ Vote A poll you have voted in has ended A poll you published has ended + A message you shared has been edited + A user signed-up + A user sent a report Categories Move timeline Hide timeline @@ -449,7 +455,8 @@ I agree to %1$s and %2$s server rules terms of service - Sign up + Sign-up + Sign-ups This instance works with invitations. Your account will need to be manually approved by an administrator before being usable. This field cannot be empty! Passwords don\'t match! @@ -1254,6 +1261,9 @@ SET_NOTIF_FAVOURITE SET_NOTIF_POLL SET_NOTIF_STATUS + SET_NOTIF_UPDATE + SET_NOTIF_ADMIN_SIGNUP + SET_NOTIF_ADMIN_REPORT SET_FONT_SCALE SET_MAX_INDENTATION SET_FONT_SCALE_INT @@ -1904,6 +1914,7 @@ Delete timeline Submitted a report Signed up + sent a report Suggestions Not interested Blocked domains @@ -1916,4 +1927,7 @@ Delete the pinned timelines? Domains Keep notifications + Notify for updates + New sign-up (moderators) + New report (moderators) \ No newline at end of file diff --git a/app/src/main/res/xml/pref_notifications.xml b/app/src/main/res/xml/pref_notifications.xml index 9c4e7cb6..a6fe7ac3 100644 --- a/app/src/main/res/xml/pref_notifications.xml +++ b/app/src/main/res/xml/pref_notifications.xml @@ -77,6 +77,24 @@ app:key="@string/SET_NOTIF_STATUS" app:singleLineTitle="false" app:title="@string/set_notif_status" /> + + + Date: Fri, 25 Nov 2022 07:45:43 +0100 Subject: [PATCH 42/61] Translated using Weblate (German) Currently translated at 14.2% (6 of 42 strings) Co-authored-by: Murat H Translate-URL: https://hosted.weblate.org/projects/fedilab/description/de/ Translation: Fedilab/description --- src/fdroid/fastlane/metadata/android/de/changelogs/393.txt | 2 ++ src/fdroid/fastlane/metadata/android/de/changelogs/397.txt | 1 + 2 files changed, 3 insertions(+) create mode 100644 src/fdroid/fastlane/metadata/android/de/changelogs/393.txt create mode 100644 src/fdroid/fastlane/metadata/android/de/changelogs/397.txt diff --git a/src/fdroid/fastlane/metadata/android/de/changelogs/393.txt b/src/fdroid/fastlane/metadata/android/de/changelogs/393.txt new file mode 100644 index 00000000..517893df --- /dev/null +++ b/src/fdroid/fastlane/metadata/android/de/changelogs/393.txt @@ -0,0 +1,2 @@ +- Fehlerbehebungen +- Verbesserungen bei angepinnten Zeitleisten diff --git a/src/fdroid/fastlane/metadata/android/de/changelogs/397.txt b/src/fdroid/fastlane/metadata/android/de/changelogs/397.txt new file mode 100644 index 00000000..c0cc70fb --- /dev/null +++ b/src/fdroid/fastlane/metadata/android/de/changelogs/397.txt @@ -0,0 +1 @@ +- Einige berichtete Fehler beseitigt. From 6bf67ecbfd71de63547ea199700870ea4fef7f3e Mon Sep 17 00:00:00 2001 From: RintanBroadleaf Date: Fri, 25 Nov 2022 07:45:43 +0100 Subject: [PATCH 43/61] Translated using Weblate (Japanese) Currently translated at 19.0% (8 of 42 strings) Co-authored-by: RintanBroadleaf Translate-URL: https://hosted.weblate.org/projects/fedilab/description/ja/ Translation: Fedilab/description --- src/fdroid/fastlane/metadata/android/ja/changelogs/390.txt | 5 +++++ src/fdroid/fastlane/metadata/android/ja/changelogs/391.txt | 5 +++++ src/fdroid/fastlane/metadata/android/ja/changelogs/393.txt | 2 ++ src/fdroid/fastlane/metadata/android/ja/changelogs/394.txt | 1 + src/fdroid/fastlane/metadata/android/ja/changelogs/395.txt | 2 ++ src/fdroid/fastlane/metadata/android/ja/title.txt | 1 + 6 files changed, 16 insertions(+) create mode 100644 src/fdroid/fastlane/metadata/android/ja/changelogs/390.txt create mode 100644 src/fdroid/fastlane/metadata/android/ja/changelogs/391.txt create mode 100644 src/fdroid/fastlane/metadata/android/ja/changelogs/393.txt create mode 100644 src/fdroid/fastlane/metadata/android/ja/changelogs/394.txt create mode 100644 src/fdroid/fastlane/metadata/android/ja/changelogs/395.txt create mode 100644 src/fdroid/fastlane/metadata/android/ja/title.txt diff --git a/src/fdroid/fastlane/metadata/android/ja/changelogs/390.txt b/src/fdroid/fastlane/metadata/android/ja/changelogs/390.txt new file mode 100644 index 00000000..daee8142 --- /dev/null +++ b/src/fdroid/fastlane/metadata/android/ja/changelogs/390.txt @@ -0,0 +1,5 @@ +新バージョンのFedilabの新機能 +- スレッドを作成できます +- 返信時にスレッド全体を確認できます +- キャッシュのサポート +- 新しいデザイン diff --git a/src/fdroid/fastlane/metadata/android/ja/changelogs/391.txt b/src/fdroid/fastlane/metadata/android/ja/changelogs/391.txt new file mode 100644 index 00000000..daee8142 --- /dev/null +++ b/src/fdroid/fastlane/metadata/android/ja/changelogs/391.txt @@ -0,0 +1,5 @@ +新バージョンのFedilabの新機能 +- スレッドを作成できます +- 返信時にスレッド全体を確認できます +- キャッシュのサポート +- 新しいデザイン diff --git a/src/fdroid/fastlane/metadata/android/ja/changelogs/393.txt b/src/fdroid/fastlane/metadata/android/ja/changelogs/393.txt new file mode 100644 index 00000000..8cd8b494 --- /dev/null +++ b/src/fdroid/fastlane/metadata/android/ja/changelogs/393.txt @@ -0,0 +1,2 @@ +- バグ修正 +- ピン留めされたタイムラインの改善 diff --git a/src/fdroid/fastlane/metadata/android/ja/changelogs/394.txt b/src/fdroid/fastlane/metadata/android/ja/changelogs/394.txt new file mode 100644 index 00000000..2035ba22 --- /dev/null +++ b/src/fdroid/fastlane/metadata/android/ja/changelogs/394.txt @@ -0,0 +1 @@ +- 一部のPleromaインスタンスでクラッシュする問題の修正 diff --git a/src/fdroid/fastlane/metadata/android/ja/changelogs/395.txt b/src/fdroid/fastlane/metadata/android/ja/changelogs/395.txt new file mode 100644 index 00000000..669f0094 --- /dev/null +++ b/src/fdroid/fastlane/metadata/android/ja/changelogs/395.txt @@ -0,0 +1,2 @@ +- バグの修正 +- Fedilabに共有できるように diff --git a/src/fdroid/fastlane/metadata/android/ja/title.txt b/src/fdroid/fastlane/metadata/android/ja/title.txt new file mode 100644 index 00000000..e6f369e8 --- /dev/null +++ b/src/fdroid/fastlane/metadata/android/ja/title.txt @@ -0,0 +1 @@ +Fedilab From f38f93b85b1568389dcbdcbbdb878b56b739063b Mon Sep 17 00:00:00 2001 From: Murat H Date: Fri, 25 Nov 2022 07:45:48 +0100 Subject: [PATCH 44/61] Translated using Weblate (German) Currently translated at 99.5% (910 of 914 strings) Translated using Weblate (German) Currently translated at 98.3% (883 of 898 strings) Co-authored-by: Murat H Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/de/ Translation: Fedilab/Strings --- app/src/main/res/values-de/strings.xml | 104 ++++++++++++++++++++++++- 1 file changed, 102 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml index 7e39a813..7f0a8576 100644 --- a/app/src/main/res/values-de/strings.xml +++ b/app/src/main/res/values-de/strings.xml @@ -393,7 +393,7 @@ Kategorien Zeitleiste verschieben Zeitleiste ausblenden - Zeitleiste neu anordnen + Verwaltung von Zeitleisten Liste endgültig gelöscht Gefolgte Instanz entfernt Angeheftetes Schlagwort entfernt @@ -718,7 +718,7 @@ Sind Sie sich sicher, den Cache zu leeren\? Wenn Sie Entwürfe mit Bildern, etc haben, werden die angehängten Dateien gelöscht. Verlassen, ohne das Bild zu speichern\? Form - Domain + Domäne Personal Nachrichtensprache Meine Instanz @@ -770,4 +770,104 @@ Push-Dienst Die App konnte kein Token abrufen Nachricht bearbeiten + Angepinnte Zeitleisten löschen\? + Bericht eingereicht + Datenschutz-Bestimmungen + Die angepinnte Zeitleiste entfernen\? + Bist Du sicher, dass diese Zeitleiste nicht mehr angepinnt sein soll\? + Geblockte Domänen + %1$s bearbeitet %2$s + Domäne freigeben + Du hast keine geblockten Domänen + Sicher, dass Du %1$s wieder freigeben willst\? + Vorschläge + Nicht interessiert + Zuweisung aufheben + Als ungelöst markieren + Konto verwarnt + Konto-Sperrung aufgehoben + Konto gesperrt + Als gelöst markieren + Konto abgelehnt + Konto genehmigt + Meldung + Medien in Benachrichtigungen für Reblogs und Favoriten werden angezeigt + Übersetzung in eine spezifische Sprache erzwingen. Wähle den ersten Wert um die Einstellungen zurückzusetzen + Nachrichten-Verlauf + Bearbeitet am %1$s + Erstellt am %1$s + Nicht angezeigte Antworten + Benachrichtigungen wurden aus dem Zwischenspeicher entfernt. + eMail-Status + Beigetreten + Aktuelle IP + Erlauben + Warnen + Benutzer per eMail benachrichtigen + Benutzerdefinierte Warnung + Status von Meldungen + Stummgeschaltet + Benutzer + Moderator + Administrator + Bestätigt + Unbestätigt + Mir zuweisen + Konto deaktiviert + Konto nicht stummgeschaltet + Konto stummgeschaltet + App neu starten\? + Neustart + Du solltest die App neu starten um die Änderungen anzuwenden. + Du folgst bisher keinen Tags! + Tag nicht mehr folgen + Bist Du sicher dass Du diesem Tag nicht mehr folgen willst\? + Nicht mehr folgen + Tag folgen + Schreibe den Tag dem Du folgen willst + Gefolgte Tags + Tag folgen + Falls aktiv wird die App alle zusammenhängenden Benachrichtigungen einklappen + Liste bearbeiten + Profile + Deine Instanz scheint diese Funktion nicht zu unterstützen! + Erkunde Trends dieser Instanz + Nachricht bearbeiten + Aktualisierungen + Mit Warnung verstecken + Vollständig verstecken + Den gefilterten Inhalt komplett verstecken und so verhalten als ob dieser nie existiert hätte + Start und Listen + Titel + Schlagwort oder Satz + Schlagwort löschen + Schlagwort hinzufügen + Gefiltert: %1$s + Trotzdem anzeigen + Zeitleiste löschen + Angemeldet + Neue Registrierung + Eine Benutzer hat sich registriert + Remote-Profil anzeigen + Die App kann keine Remote-Daten finden! + Über Updates benachrichtigen + Domänen + Neues Update + Eine Nachricht die Du geteilt hast wurde bearbeitet + Ein Benutzer hat eine Meldung gesendet + Registrierungen + Neue Meldung + Verstecke den gefilterten Inhalt hinter einer Warnung die den Titel des Filters enthält + Aktion filtern + Wähle die Aktion aus die durchgeführt werden soll wenn ein Beitrag Deinem Filter entspricht + Hinzufügen des Kontos zur Liste fehlgeschlagen! + Benachrichtigungen behalten + Neue Meldung (Moderatoren) + Neue Registrierung (Moderatoren) + Benachrichtigungen zusammenfassen + Betrifft nur \"öffentliche\" Antworten. Falls aktiv, werden Deine Antworten automatisch \"nicht gelistet\" statt \"öffentlich\" + Anmeldestatus + Sprachen in der Auswahl + Erlaube die Liste der Sprachen in der Auswahl beim Verfassen einer Nachricht zu reduzieren. + Bezeichnung des Tags ist nicht zulässig! \ No newline at end of file From 824be3d2d1017090e4d3219f7e384e19e76919e1 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Xos=C3=A9=20M?= Date: Fri, 25 Nov 2022 07:45:48 +0100 Subject: [PATCH 45/61] Translated using Weblate (Galician) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (898 of 898 strings) Co-authored-by: Xosé M Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/gl/ Translation: Fedilab/Strings --- app/src/main/res/values-gl/strings.xml | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/res/values-gl/strings.xml b/app/src/main/res/values-gl/strings.xml index 4316d5f1..8ec4cea8 100644 --- a/app/src/main/res/values-gl/strings.xml +++ b/app/src/main/res/values-gl/strings.xml @@ -860,4 +860,7 @@ A app non puido engadir a conta á lista! Eliminar cronoloxía Rexistrada + Eliminar as cronoloxías fixadas\? + Eliminar a cronoloxía fixada\? + Tes a certeza de desafixar a cronoloxía\? \ No newline at end of file From ce9a15d0341721af7091bec3b0a4d5e8edafbf1e Mon Sep 17 00:00:00 2001 From: RintanBroadleaf Date: Fri, 25 Nov 2022 07:45:48 +0100 Subject: [PATCH 46/61] Translated using Weblate (Japanese) Currently translated at 100.0% (898 of 898 strings) Co-authored-by: RintanBroadleaf Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/ja/ Translation: Fedilab/Strings --- app/src/main/res/values-ja/strings.xml | 19 ++++++++++++++++--- 1 file changed, 16 insertions(+), 3 deletions(-) diff --git a/app/src/main/res/values-ja/strings.xml b/app/src/main/res/values-ja/strings.xml index 65487523..77b64af8 100644 --- a/app/src/main/res/values-ja/strings.xml +++ b/app/src/main/res/values-ja/strings.xml @@ -389,7 +389,7 @@ カテゴリ タイムラインの移動 タイムラインを非表示にする - タイムラインの並び替え + タイムラインの管理 削除されたリスト フォローしたサーバーを削除 ピン留めされたタグの削除 @@ -601,7 +601,7 @@ 全ての通知を既読にする 全てのカテゴリーを表示する 全ての通知を削除しますか?この操作は取り消せません。 - 相互 + 交流 変更を保存 ボットアカウント アカウントを検索可能にする @@ -698,7 +698,7 @@ アナウンス・%1$s - %2$s 他のタイムラインの投稿のキャッシュ 下書きのメッセージ - ファイルキャッシュのサイズ + キャッシュファイルのサイズ キャッシュを消去 直近 フィルター @@ -846,4 +846,17 @@ 更新 強制的に表示する リモートのプロフィールを表示 + プライバシーポリシー + ピン留めしたタイムラインを削除しますか? + ピン留めされたタイムラインを削除しますか? + このタイムラインのピン留めを解除しますか? + ブロックしたドメイン + ドメインのブロックを解除 + ブロックしているドメインはありません + %1$sのブロックを解除しますか? + サジェスト + 興味なし + タイムラインの削除 + レポートを送信しました + 登録しました \ No newline at end of file From 0fa5ed41ce39aecbf6f97ce2344070d5fb5819cd Mon Sep 17 00:00:00 2001 From: Ajeje Brazorf Date: Fri, 25 Nov 2022 07:45:49 +0100 Subject: [PATCH 47/61] Translated using Weblate (Sardinian) Currently translated at 98.9% (889 of 898 strings) Co-authored-by: Ajeje Brazorf Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/sc/ Translation: Fedilab/Strings --- app/src/main/res/values-sc/strings.xml | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/app/src/main/res/values-sc/strings.xml b/app/src/main/res/values-sc/strings.xml index 25d6872f..768e9969 100644 --- a/app/src/main/res/values-sc/strings.xml +++ b/app/src/main/res/values-sc/strings.xml @@ -849,4 +849,16 @@ Mustra su profilu remotu S\'aplicatzione non resesset a agatare datos remotos! Iscantzella sa lìnia de tempus + Polìtica de riservadesa + Bogare sa lìnia de tempus apicada\? + Ses seguru de bòlere isblocare cussa lìnia de tempus\? + Iscantzellare is lìnias de tempus apicadas\? + Domìnios blocados + Isbloca su domìniu + Non tenes domìnios blocados + Ses seguru de bòlere isblocare %1$s\? + Cussìgios + No interessadu + At imbiadu un\'informe + S\'est registradu \ No newline at end of file From b044246457cd2ef3c195d9924fcde36b20f12983 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?O=C4=9Fuz=20Ersen?= Date: Fri, 25 Nov 2022 07:45:49 +0100 Subject: [PATCH 48/61] Translated using Weblate (Turkish) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (914 of 914 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (898 of 898 strings) Co-authored-by: Oğuz Ersen Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/ Translation: Fedilab/Strings --- app/src/main/res/values-tr/strings.xml | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index 1c5bdabf..f672f81d 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -864,4 +864,20 @@ Etki alanının engelini kaldır %1$s engellemesini kaldırmak istediğinizden emin misiniz\? Gizlilik politikası + Sabitlenen zaman çizelgeleri silinsin mi\? + Bu zaman çizelgesinin sabitlemesini kaldırmak istediğinizden emin misiniz\? + Sabitlenen zaman çizelgesi kaldırılsın mı\? + Yeni rapor + Etki alanları + Güncellemeler için bildir + Yeni kayıt (moderatörler) + Yeni güncelleme + bir rapor gönderdi + Yeni kayıt + Paylaştığınız bir mesaj düzenlendi + Bir kullanıcı kaydoldu + Bir kullanıcı bir rapor gönderdi + Bildirimleri sakla + Yeni rapor (moderatörler) + Kayıtlar \ No newline at end of file From 6d39c6a45f8e92492b833a46e1b2b172419eca20 Mon Sep 17 00:00:00 2001 From: Zekovski Date: Fri, 25 Nov 2022 07:45:50 +0100 Subject: [PATCH 49/61] Translated using Weblate (French) Currently translated at 97.8% (894 of 914 strings) Co-authored-by: Zekovski Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/fr/ Translation: Fedilab/Strings --- app/src/main/res/values-fr/strings.xml | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml index 364d5891..d13e95e7 100644 --- a/app/src/main/res/values-fr/strings.xml +++ b/app/src/main/res/values-fr/strings.xml @@ -399,7 +399,7 @@ Catégories Déplacer le fil Cacher le fil - Réorganiser les fils + Gérer les fils Liste supprimée définitivement Instance suivie supprimée Balise épinglée supprimée @@ -857,4 +857,11 @@ Statut de la connexion Afficher le profil distant L\'application ne trouve pas de données distantes ! + Un⋅e utilisateur⋅ice a envoyé un signalement + Inscriptions + Nouvelle mise à jour + Nouvel abonnement + Nouveau signalement + Un message que vous avez partagé a été édité + Un⋅e utilisateur⋅ice s\'est abonné⋅e \ No newline at end of file From bfa50d19c4db8ae9ff85302b26e146cf225251c2 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 25 Nov 2022 08:29:11 +0100 Subject: [PATCH 50/61] Fix a crash when long pressing URLs --- .../java/app/fedilab/android/helper/SpannableHelper.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java b/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java index 6418f1dc..3da822cd 100644 --- a/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java +++ b/app/src/main/java/app/fedilab/android/helper/SpannableHelper.java @@ -255,6 +255,12 @@ public class SpannableHelper { if (urlDetails.containsValue(uniqueUrl)) { finalURl = Helper.getKeyByValue(urlDetails, uniqueUrl); } + if (finalURl == null) { + return; + } + if (finalURl.startsWith("http://")) { + finalURl = finalURl.replace("http://", "https://"); + } String finalURl1 = finalURl; popupLinksBinding.displayFullLink.setOnClickListener(v -> { AlertDialog.Builder builder = new AlertDialog.Builder(mContext, Helper.dialogStyle()); From d2644b22a7deb143c24e9a8a50626a29a615d49c Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 25 Nov 2022 09:04:24 +0100 Subject: [PATCH 51/61] Fix issue #538 - Open a message with another account --- .../app/fedilab/android/BaseMainActivity.java | 20 ++++++- .../app/fedilab/android/helper/Helper.java | 2 + .../android/ui/drawer/StatusAdapter.java | 53 +++++++++++++++++++ app/src/main/res/menu/option_toot.xml | 5 +- app/src/main/res/values/strings.xml | 1 + 5 files changed, 79 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/BaseMainActivity.java b/app/src/main/java/app/fedilab/android/BaseMainActivity.java index 8fc1e428..f98860b4 100644 --- a/app/src/main/java/app/fedilab/android/BaseMainActivity.java +++ b/app/src/main/java/app/fedilab/android/BaseMainActivity.java @@ -811,10 +811,11 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt String action = intent.getAction(); String type = intent.getType(); Bundle extras = intent.getExtras(); - String userIdIntent, instanceIntent; + String userIdIntent, instanceIntent, urlOfMessage; if (extras != null && extras.containsKey(Helper.INTENT_ACTION)) { userIdIntent = extras.getString(Helper.PREF_KEY_ID); //Id of the account in the intent instanceIntent = extras.getString(Helper.PREF_INSTANCE); + urlOfMessage = extras.getString(Helper.PREF_MESSAGE_URL); if (extras.getInt(Helper.INTENT_ACTION) == Helper.NOTIFICATION_INTENT) { if (userIdIntent != null && instanceIntent != null && userIdIntent.equals(currentUserID) && instanceIntent.equals(currentInstance)) { openNotifications(intent); @@ -840,6 +841,23 @@ public abstract class BaseMainActivity extends BaseActivity implements NetworkSt } } else if (extras.getInt(Helper.INTENT_ACTION) == Helper.OPEN_NOTIFICATION) { openNotifications(intent); + } else if (extras.getInt(Helper.INTENT_ACTION) == Helper.OPEN_WITH_ANOTHER_ACCOUNT) { + CrossActionHelper.fetchRemoteStatus(BaseMainActivity.this, MainActivity.currentAccount, urlOfMessage, new CrossActionHelper.Callback() { + @Override + public void federatedStatus(Status status) { + if (status != null) { + Intent intent = new Intent(BaseMainActivity.this, ContextActivity.class); + intent.putExtra(Helper.ARG_STATUS, status); + intent.addFlags(Intent.FLAG_ACTIVITY_NEW_TASK); + startActivity(intent); + } + } + + @Override + public void federatedAccount(app.fedilab.android.client.entities.api.Account account) { + + } + }); } } else if (Intent.ACTION_SEND.equals(action) && type != null) { if ("text/plain".equals(type)) { diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java index 3c2ecf29..c644a779 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -302,12 +302,14 @@ public class Helper { public static final String PREF_IS_MODERATOR = "PREF_IS_MODERATOR"; public static final String PREF_IS_ADMINISTRATOR = "PREF_IS_ADMINISTRATOR"; public static final String PREF_KEY_ID = "PREF_KEY_ID"; + public static final String PREF_MESSAGE_URL = "PREF_MESSAGE_URL"; public static final String PREF_INSTANCE = "PREF_INSTANCE"; public static final String SET_SECURITY_PROVIDER = "SET_SECURITY_PROVIDER"; public static final int NOTIFICATION_INTENT = 1; public static final int OPEN_NOTIFICATION = 2; + public static final int OPEN_WITH_ANOTHER_ACCOUNT = 3; public static final String INTENT_TARGETED_ACCOUNT = "INTENT_TARGETED_ACCOUNT"; public static final String INTENT_SEND_MODIFIED_IMAGE = "INTENT_SEND_MODIFIED_IMAGE"; public static final String INTENT_COMPOSE_ERROR_MESSAGE = "INTENT_COMPOSE_ERROR_MESSAGE"; diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java index 75c5f853..af62860f 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/StatusAdapter.java @@ -24,6 +24,7 @@ import static app.fedilab.android.BaseMainActivity.regex_public; import static app.fedilab.android.BaseMainActivity.show_boosts; import static app.fedilab.android.BaseMainActivity.show_replies; import static app.fedilab.android.activities.ContextActivity.expand; +import static app.fedilab.android.helper.Helper.PREF_USER_TOKEN; import android.annotation.SuppressLint; import android.app.Activity; @@ -38,6 +39,8 @@ import android.graphics.drawable.Drawable; import android.os.Build; import android.os.Bundle; import android.os.CountDownTimer; +import android.os.Handler; +import android.os.Looper; import android.text.Html; import android.text.SpannableString; import android.text.Spanned; @@ -103,6 +106,7 @@ import app.fedilab.android.R; import app.fedilab.android.activities.ComposeActivity; import app.fedilab.android.activities.ContextActivity; import app.fedilab.android.activities.CustomSharingActivity; +import app.fedilab.android.activities.MainActivity; import app.fedilab.android.activities.MediaActivity; import app.fedilab.android.activities.ProfileActivity; import app.fedilab.android.activities.ReportActivity; @@ -114,6 +118,7 @@ import app.fedilab.android.client.entities.api.Poll; import app.fedilab.android.client.entities.api.Reaction; import app.fedilab.android.client.entities.api.Status; import app.fedilab.android.client.entities.app.Account; +import app.fedilab.android.client.entities.app.BaseAccount; import app.fedilab.android.client.entities.app.StatusCache; import app.fedilab.android.client.entities.app.StatusDraft; import app.fedilab.android.client.entities.app.Timeline; @@ -2011,6 +2016,54 @@ public class StatusAdapter extends RecyclerView.Adapter b.putSerializable(Helper.ARG_STATUS_MENTION, statusToDeal); intent.putExtras(b); context.startActivity(intent); + } else if (itemId == R.id.action_open_with) { + new Thread(() -> { + try { + List accounts = new Account(context).getCrossAccounts(); + if (accounts.size() > 1) { + List accountList = new ArrayList<>(); + for (BaseAccount account : accounts) { + accountList.add(account.mastodon_account); + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> { + AlertDialog.Builder builderSingle = new AlertDialog.Builder(context, Helper.dialogStyle()); + builderSingle.setTitle(context.getString(R.string.choose_accounts)); + final AccountsSearchAdapter accountsSearchAdapter = new AccountsSearchAdapter(context, accountList); + final BaseAccount[] accountArray = new BaseAccount[accounts.size()]; + int i = 0; + for (BaseAccount account : accounts) { + accountArray[i] = account; + i++; + } + builderSingle.setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()); + builderSingle.setAdapter(accountsSearchAdapter, (dialog, which) -> { + BaseAccount account = accountArray[which]; + + Toasty.info(context, context.getString(R.string.toast_account_changed, "@" + account.mastodon_account.acct + "@" + account.instance), Toasty.LENGTH_LONG).show(); + BaseMainActivity.currentToken = account.token; + BaseMainActivity.currentUserID = account.user_id; + BaseMainActivity.currentInstance = account.instance; + MainActivity.currentAccount = account; + SharedPreferences.Editor editor = sharedpreferences.edit(); + editor.putString(PREF_USER_TOKEN, account.token); + editor.commit(); + Intent mainActivity = new Intent(context, MainActivity.class); + mainActivity.putExtra(Helper.INTENT_ACTION, Helper.OPEN_WITH_ANOTHER_ACCOUNT); + mainActivity.putExtra(Helper.PREF_MESSAGE_URL, statusToDeal.url); + context.startActivity(mainActivity); + ((Activity) context).finish(); + dialog.dismiss(); + }); + builderSingle.show(); + }; + mainHandler.post(myRunnable); + } + + } catch (DBException e) { + e.printStackTrace(); + } + }).start(); } return true; }); diff --git a/app/src/main/res/menu/option_toot.xml b/app/src/main/res/menu/option_toot.xml index bb67f133..a4393cc9 100644 --- a/app/src/main/res/menu/option_toot.xml +++ b/app/src/main/res/menu/option_toot.xml @@ -83,5 +83,8 @@ android:id="@+id/action_mention" android:title="@string/more_action_7" app:showAsAction="never" /> - + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index b4f71fbf..4a05fa19 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -1930,4 +1930,5 @@ Notify for updates New sign-up (moderators) New report (moderators) + Open with another account \ No newline at end of file From 39f5a83105e88725b4e22d7ff6fd28e5d2be1184 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 25 Nov 2022 09:22:44 +0100 Subject: [PATCH 52/61] Fix issue #536 - Set as single line --- app/src/main/res/layout/drawer_status.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/app/src/main/res/layout/drawer_status.xml b/app/src/main/res/layout/drawer_status.xml index 1708ffd8..d6fce74a 100644 --- a/app/src/main/res/layout/drawer_status.xml +++ b/app/src/main/res/layout/drawer_status.xml @@ -148,7 +148,7 @@ android:layout_marginStart="6dp" android:layout_weight="1" android:ellipsize="end" - android:maxLines="1" + android:singleLine="true" tools:text="@tools:sample/full_names" /> Date: Fri, 25 Nov 2022 09:27:58 +0100 Subject: [PATCH 53/61] Fix issue #532 - Make filtered messages smaller --- app/src/main/res/layout/drawer_status_filtered.xml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/app/src/main/res/layout/drawer_status_filtered.xml b/app/src/main/res/layout/drawer_status_filtered.xml index 9ab7fbba..5bf1f85c 100644 --- a/app/src/main/res/layout/drawer_status_filtered.xml +++ b/app/src/main/res/layout/drawer_status_filtered.xml @@ -30,14 +30,14 @@ android:layout_width="match_parent" android:layout_height="match_parent" android:layout_gravity="center" - android:padding="5dp"> + android:padding="3dp"> Date: Fri, 25 Nov 2022 10:08:57 +0100 Subject: [PATCH 54/61] Fix issue #529 - Lists are ordered alphabetically 'ASC' by default can be reverted with a button --- .../activities/MastodonListActivity.java | 38 +++++++++++++++++-- .../app/fedilab/android/helper/Helper.java | 2 +- .../drawable/ic_baseline_filter_asc_24.xml | 15 ++++++++ .../drawable/ic_baseline_filter_desc_24.xml | 10 +++++ app/src/main/res/menu/menu_main_list.xml | 6 +++ app/src/main/res/values/strings.xml | 1 + 6 files changed, 68 insertions(+), 4 deletions(-) create mode 100644 app/src/main/res/drawable/ic_baseline_filter_asc_24.xml create mode 100644 app/src/main/res/drawable/ic_baseline_filter_desc_24.xml diff --git a/app/src/main/java/app/fedilab/android/activities/MastodonListActivity.java b/app/src/main/java/app/fedilab/android/activities/MastodonListActivity.java index ba64cd17..4b9d35d7 100644 --- a/app/src/main/java/app/fedilab/android/activities/MastodonListActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/MastodonListActivity.java @@ -15,8 +15,6 @@ package app.fedilab.android.activities; * see . */ -import static app.fedilab.android.helper.PinnedTimelineHelper.sortListPositionAsc; - import android.content.Intent; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; @@ -39,6 +37,7 @@ import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import java.util.ArrayList; +import java.util.Collections; import java.util.List; import app.fedilab.android.BaseMainActivity; @@ -78,6 +77,7 @@ public class MastodonListActivity extends BaseActivity implements MastodonListAd private boolean flagLoading; private String max_id; private FragmentMastodonTimeline fragmentMastodonTimeline; + private boolean orderASC; @Override protected void onCreate(Bundle savedInstanceState) { @@ -92,6 +92,7 @@ public class MastodonListActivity extends BaseActivity implements MastodonListAd getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary))); } flagLoading = false; + orderASC = true; max_id = null; accountsVM = new ViewModelProvider(MastodonListActivity.this).get(AccountsVM.class); timelinesVM = new ViewModelProvider(MastodonListActivity.this).get(TimelinesVM.class); @@ -114,7 +115,7 @@ public class MastodonListActivity extends BaseActivity implements MastodonListAd } } } - sortListPositionAsc(mastodonListList); + sortAsc(mastodonListList); mastodonListAdapter = new MastodonListAdapter(mastodonListList); mastodonListAdapter.actionOnList = this; binding.notContent.setVisibility(View.GONE); @@ -127,6 +128,19 @@ public class MastodonListActivity extends BaseActivity implements MastodonListAd }); } + + private void sortAsc(List mastodonLists) { + Collections.sort(mastodonLists, (obj1, obj2) -> obj1.title.compareToIgnoreCase(obj2.title)); + orderASC = true; + invalidateOptionsMenu(); + } + + private void sortDesc(List mastodonLists) { + Collections.sort(mastodonLists, (obj1, obj2) -> obj2.title.compareToIgnoreCase(obj1.title)); + orderASC = false; + invalidateOptionsMenu(); + } + @Override public boolean onOptionsItemSelected(MenuItem item) { if (item.getItemId() == android.R.id.home) { @@ -374,6 +388,16 @@ public class MastodonListActivity extends BaseActivity implements MastodonListAd }); dialogBuilder.setNegativeButton(R.string.cancel, (dialog, id) -> dialog.dismiss()); dialogBuilder.create().show(); + } else if (item.getItemId() == R.id.action_order) { + if (mastodonListList != null && mastodonListList.size() > 0 && mastodonListAdapter != null) { + if (orderASC) { + sortDesc(mastodonListList); + } else { + sortAsc(mastodonListList); + } + invalidateOptionsMenu(); + mastodonListAdapter.notifyItemRangeChanged(0, mastodonListList.size()); + } } return super.onOptionsItemSelected(item); } @@ -403,6 +427,14 @@ public class MastodonListActivity extends BaseActivity implements MastodonListAd public boolean onCreateOptionsMenu(@NonNull Menu menu) { if (!canGoBack) { getMenuInflater().inflate(R.menu.menu_main_list, menu); + MenuItem order = menu.findItem(R.id.action_order); + if (order != null) { + if (orderASC) { + order.setIcon(R.drawable.ic_baseline_filter_asc_24); + } else { + order.setIcon(R.drawable.ic_baseline_filter_desc_24); + } + } } else { getMenuInflater().inflate(R.menu.menu_list, menu); } diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java index c644a779..f533c3ab 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -1031,7 +1031,7 @@ public class Helper { final Activity activity = (Activity) context; return !activity.isDestroyed() && !activity.isFinishing(); } - return false; + return true; } /** diff --git a/app/src/main/res/drawable/ic_baseline_filter_asc_24.xml b/app/src/main/res/drawable/ic_baseline_filter_asc_24.xml new file mode 100644 index 00000000..0d01f28e --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_filter_asc_24.xml @@ -0,0 +1,15 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_baseline_filter_desc_24.xml b/app/src/main/res/drawable/ic_baseline_filter_desc_24.xml new file mode 100644 index 00000000..b311f427 --- /dev/null +++ b/app/src/main/res/drawable/ic_baseline_filter_desc_24.xml @@ -0,0 +1,10 @@ + + + diff --git a/app/src/main/res/menu/menu_main_list.xml b/app/src/main/res/menu/menu_main_list.xml index 01b82f78..a3e9b56a 100644 --- a/app/src/main/res/menu/menu_main_list.xml +++ b/app/src/main/res/menu/menu_main_list.xml @@ -1,6 +1,12 @@ + + New sign-up (moderators) New report (moderators) Open with another account + Order lists \ No newline at end of file From e66fbf5fd75323b206863cfc07d09450bac49a89 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 25 Nov 2022 15:03:14 +0100 Subject: [PATCH 55/61] Domain blocks for admin (create/update) --- app/src/main/AndroidManifest.xml | 5 + .../activities/admin/AdminActionActivity.java | 5 +- .../admin/AdminDomainBlockActivity.java | 123 +++++++++++++ .../endpoints/MastodonAdminService.java | 6 +- .../app/fedilab/android/helper/Helper.java | 2 + .../ui/drawer/admin/AdminDomainAdapter.java | 21 ++- .../fragment/admin/FragmentAdminDomain.java | 9 + .../android/viewmodel/mastodon/AdminVM.java | 43 +++++ .../res/layout/activity_admin_domainblock.xml | 162 ++++++++++++++++++ .../main/res/layout/drawer_admin_domain.xml | 1 + .../main/res/layout/fragment_pagination.xml | 14 ++ app/src/main/res/values/strings.xml | 24 +++ 12 files changed, 406 insertions(+), 9 deletions(-) create mode 100644 app/src/main/java/app/fedilab/android/activities/admin/AdminDomainBlockActivity.java create mode 100644 app/src/main/res/layout/activity_admin_domainblock.xml diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 8fb92343..be259dfd 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -243,6 +243,11 @@ android:configChanges="keyboardHidden|orientation|screenSize" android:label="@string/action_about" android:theme="@style/AppThemeBar" /> + { if (fragmentAdminReport != null) { fragmentAdminReport.onDestroyView(); + fragmentAdminReport = null; } if (fragmentAdminAccount != null) { fragmentAdminAccount.onDestroyView(); + fragmentAdminAccount = null; } if (fragmentAdminDomain != null) { fragmentAdminDomain.onDestroyView(); + fragmentAdminDomain = null; } setTitle(R.string.administration); invalidateOptionsMenu(); diff --git a/app/src/main/java/app/fedilab/android/activities/admin/AdminDomainBlockActivity.java b/app/src/main/java/app/fedilab/android/activities/admin/AdminDomainBlockActivity.java new file mode 100644 index 00000000..e7d53d80 --- /dev/null +++ b/app/src/main/java/app/fedilab/android/activities/admin/AdminDomainBlockActivity.java @@ -0,0 +1,123 @@ +package app.fedilab.android.activities.admin; +/* Copyright 2022 Thomas Schneider + * + * This file is a part of Fedilab + * + * This program is free software; you can redistribute it and/or modify it under the terms of the + * GNU General Public License as published by the Free Software Foundation; either version 3 of the + * License, or (at your option) any later version. + * + * Fedilab is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even + * the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General + * Public License for more details. + * + * You should have received a copy of the GNU General Public License along with Fedilab; if not, + * see . */ + + +import android.graphics.drawable.ColorDrawable; +import android.os.Bundle; +import android.view.MenuItem; +import android.view.View; +import android.widget.AdapterView; +import android.widget.ArrayAdapter; + +import androidx.core.content.ContextCompat; +import androidx.lifecycle.ViewModelProvider; + +import app.fedilab.android.R; +import app.fedilab.android.activities.BaseActivity; +import app.fedilab.android.activities.MainActivity; +import app.fedilab.android.client.entities.api.admin.AdminDomainBlock; +import app.fedilab.android.databinding.ActivityAdminDomainblockBinding; +import app.fedilab.android.helper.Helper; +import app.fedilab.android.helper.ThemeHelper; +import app.fedilab.android.viewmodel.mastodon.AdminVM; +import es.dmoral.toasty.Toasty; + +public class AdminDomainBlockActivity extends BaseActivity { + + + private final String[] severityChoices = {"silence", "suspend", "noop"}; + private AdminVM adminVM; + private AdminDomainBlock adminDomainBlock; + + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + ThemeHelper.applyThemeBar(this); + ActivityAdminDomainblockBinding binding = ActivityAdminDomainblockBinding.inflate(getLayoutInflater()); + setContentView(binding.getRoot()); + if (getSupportActionBar() != null) { + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary))); + } + Bundle b = getIntent().getExtras(); + if (b != null) { + adminDomainBlock = (AdminDomainBlock) b.getSerializable(Helper.ARG_ADMIN_DOMAINBLOCK); + } + + ArrayAdapter adapterResize = ArrayAdapter.createFromResource(this, + R.array.admin_block_severity, android.R.layout.simple_spinner_dropdown_item); + binding.severity.setAdapter(adapterResize); + + if (adminDomainBlock != null) { + binding.domain.setText(adminDomainBlock.domain); + binding.domain.setEnabled(false); + for (int i = 0; i < severityChoices.length; i++) { + if (adminDomainBlock.severity.equalsIgnoreCase(severityChoices[i])) { + binding.severity.setSelection(i, false); + break; + } + } + binding.obfuscate.setChecked(adminDomainBlock.obfuscate); + binding.rejectMedia.setChecked(adminDomainBlock.reject_media); + binding.rejectReports.setChecked(adminDomainBlock.reject_reports); + binding.privateComment.setText(adminDomainBlock.private_comment); + binding.publicComment.setText(adminDomainBlock.public_comment); + } else { + adminDomainBlock = new AdminDomainBlock(); + } + + binding.severity.setOnItemSelectedListener(new AdapterView.OnItemSelectedListener() { + @Override + public void onItemSelected(AdapterView adapterView, View view, int position, long l) { + adminDomainBlock.severity = severityChoices[position]; + } + + @Override + public void onNothingSelected(AdapterView adapterView) { + } + }); + binding.obfuscate.setOnCheckedChangeListener((compoundButton, checked) -> adminDomainBlock.obfuscate = checked); + binding.rejectMedia.setOnCheckedChangeListener((compoundButton, checked) -> adminDomainBlock.reject_media = checked); + binding.rejectReports.setOnCheckedChangeListener((compoundButton, checked) -> adminDomainBlock.reject_reports = checked); + adminVM = new ViewModelProvider(AdminDomainBlockActivity.this).get(AdminVM.class); + binding.saveChanges.setOnClickListener(v -> { + adminDomainBlock.domain = binding.domain.getText().toString().trim(); + adminDomainBlock.public_comment = binding.publicComment.getText().toString().trim(); + adminDomainBlock.private_comment = binding.privateComment.getText().toString().trim(); + adminVM.createOrUpdateDomainBlock(MainActivity.currentInstance, MainActivity.currentToken, adminDomainBlock) + .observe(AdminDomainBlockActivity.this, adminDomainBlockResult -> { + if (adminDomainBlockResult != null) { + Toasty.success(AdminDomainBlockActivity.this, getString(R.string.saved_changes), Toasty.LENGTH_SHORT).show(); + } else { + Toasty.error(AdminDomainBlockActivity.this, getString(R.string.toast_error), Toasty.LENGTH_SHORT).show(); + } + + } + ); + }); + } + + @Override + public boolean onOptionsItemSelected(MenuItem item) { + int itemId = item.getItemId(); + if (itemId == android.R.id.home) { + finish(); + return true; + } + return true; + } + +} diff --git a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java index ee57e8ec..eeacfde6 100644 --- a/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java +++ b/app/src/main/java/app/fedilab/android/client/endpoints/MastodonAdminService.java @@ -188,7 +188,7 @@ public interface MastodonAdminService { @POST("admin/domain_blocks") Call blockDomain( @Header("Authorization") String app_token, - @Path("domain") String domain, + @Field("domain") String domain, @Field("severity") String severity, @Field("reject_media") Boolean reject_media, @Field("reject_reports") Boolean reject_reports, @@ -205,10 +205,10 @@ public interface MastodonAdminService { ); @FormUrlEncoded - @PUT("admin/domain_blocks") + @PUT("admin/domain_blocks/{id}") Call updateBlockDomain( @Header("Authorization") String app_token, - @Path("domain") String domain, + @Path("id") String id, @Field("severity") String severity, @Field("reject_media") Boolean reject_media, @Field("reject_reports") Boolean reject_reports, diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java index f533c3ab..14d19388 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -238,6 +238,8 @@ public class Helper { public static final String ARG_STATUS_REPLY_ID = "ARG_STATUS_REPLY_ID"; public static final String ARG_ACCOUNT = "ARG_ACCOUNT"; public static final String ARG_ACCOUNT_ID = "ARG_ACCOUNT_ID"; + public static final String ARG_ADMIN_DOMAINBLOCK = "ARG_ADMIN_DOMAINBLOCK"; + public static final String ARG_REPORT = "ARG_REPORT"; public static final String ARG_ACCOUNT_MENTION = "ARG_ACCOUNT_MENTION"; public static final String ARG_MINIFIED = "ARG_MINIFIED"; diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/admin/AdminDomainAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/admin/AdminDomainAdapter.java index 502a2488..bee4ccee 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/admin/AdminDomainAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/admin/AdminDomainAdapter.java @@ -15,6 +15,8 @@ package app.fedilab.android.ui.drawer.admin; * see . */ import android.content.Context; +import android.content.Intent; +import android.os.Bundle; import android.view.LayoutInflater; import android.view.ViewGroup; @@ -25,6 +27,8 @@ import org.jetbrains.annotations.NotNull; import java.util.List; +import app.fedilab.android.R; +import app.fedilab.android.activities.admin.AdminDomainBlockActivity; import app.fedilab.android.client.entities.api.admin.AdminDomainBlock; import app.fedilab.android.databinding.DrawerAdminDomainBinding; import app.fedilab.android.helper.Helper; @@ -55,14 +59,21 @@ public class AdminDomainAdapter extends RecyclerView.Adapter { - Intent intent = new Intent(context, AccountReportActivity.class); + String text = adminDomainBlock.severity; + if (adminDomainBlock.reject_media) { + text += " - " + context.getString(R.string.reject_media); + } + if (adminDomainBlock.reject_reports) { + text += " - " + context.getString(R.string.reject_reports); + } + holder.binding.severity.setText(text); + holder.binding.mainContainer.setOnClickListener(view -> { + Intent intent = new Intent(context, AdminDomainBlockActivity.class); Bundle b = new Bundle(); - b.putSerializable(Helper.ARG_REPORT, report); + b.putSerializable(Helper.ARG_ADMIN_DOMAINBLOCK, adminDomainBlock); intent.putExtras(b); context.startActivity(intent); - });*/ + }); } diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java b/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java index f146c0fb..d5f78812 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java @@ -15,6 +15,7 @@ package app.fedilab.android.ui.fragment.admin; * see . */ +import android.content.Intent; import android.os.Bundle; import android.view.LayoutInflater; import android.view.View; @@ -33,6 +34,7 @@ import java.util.List; import app.fedilab.android.BaseMainActivity; import app.fedilab.android.R; +import app.fedilab.android.activities.admin.AdminDomainBlockActivity; import app.fedilab.android.client.entities.api.admin.AdminDomainBlock; import app.fedilab.android.client.entities.api.admin.AdminDomainBlocks; import app.fedilab.android.databinding.FragmentPaginationBinding; @@ -84,6 +86,13 @@ public class FragmentAdminDomain extends Fragment { adminVM.getDomainBlocks( BaseMainActivity.currentInstance, BaseMainActivity.currentToken, null) .observe(getViewLifecycleOwner(), this::initializeStatusesCommonView); + binding.addAction.setVisibility(View.VISIBLE); + binding.addAction.setOnClickListener(v -> { + Intent intent = new Intent(requireActivity(), AdminDomainBlockActivity.class); + Bundle b = new Bundle(); + intent.putExtras(b); + startActivity(intent); + }); return binding.getRoot(); } diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java index 9f3d17b3..c45fe41c 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java @@ -17,6 +17,7 @@ package app.fedilab.android.viewmodel.mastodon; import android.app.Application; import android.os.Handler; import android.os.Looper; +import android.util.Log; import androidx.annotation.NonNull; import androidx.lifecycle.AndroidViewModel; @@ -632,6 +633,48 @@ public class AdminVM extends AndroidViewModel { } + /** + * View a single blocked domain + * + * @param instance Instance domain of the active account + * @param token Access token of the active account + * @return {@link LiveData} containing a {@link List} of {@link AdminDomainBlocks}s + */ + public LiveData createOrUpdateDomainBlock(@NonNull String instance, + String token, + AdminDomainBlock adminDomainBlock) { + MastodonAdminService mastodonAdminService = init(instance); + adminDomainBlockMutableLiveData = new MutableLiveData<>(); + new Thread(() -> { + AdminDomainBlock admDomainBlock = null; + Call getDomainBlock; + if (adminDomainBlock.id == null) { + getDomainBlock = mastodonAdminService.blockDomain(token, adminDomainBlock.domain, adminDomainBlock.severity, adminDomainBlock.reject_media, adminDomainBlock.reject_reports, adminDomainBlock.private_comment, adminDomainBlock.public_comment, adminDomainBlock.obfuscate); + } else { + getDomainBlock = mastodonAdminService.updateBlockDomain(token, adminDomainBlock.id, adminDomainBlock.severity, adminDomainBlock.reject_media, adminDomainBlock.reject_reports, adminDomainBlock.private_comment, adminDomainBlock.public_comment, adminDomainBlock.obfuscate); + } + if (getDomainBlock != null) { + try { + Response getDomainBlocksResponse = getDomainBlock.execute(); + if (getDomainBlocksResponse.isSuccessful()) { + admDomainBlock = getDomainBlocksResponse.body(); + } else { + Log.v(Helper.TAG, "errr: " + getDomainBlocksResponse.errorBody().string()); + } + } catch (Exception e) { + Log.v(Helper.TAG, "e: " + e.getMessage()); + e.printStackTrace(); + } + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + AdminDomainBlock finalAdminDomainBlock = admDomainBlock; + Runnable myRunnable = () -> adminDomainBlockMutableLiveData.setValue(finalAdminDomainBlock); + mainHandler.post(myRunnable); + }).start(); + return adminDomainBlockMutableLiveData; + } + + /** * View all allowed domains. * diff --git a/app/src/main/res/layout/activity_admin_domainblock.xml b/app/src/main/res/layout/activity_admin_domainblock.xml new file mode 100644 index 00000000..6e1e1caf --- /dev/null +++ b/app/src/main/res/layout/activity_admin_domainblock.xml @@ -0,0 +1,162 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/drawer_admin_domain.xml b/app/src/main/res/layout/drawer_admin_domain.xml index 57ea5204..63d5dbec 100644 --- a/app/src/main/res/layout/drawer_admin_domain.xml +++ b/app/src/main/res/layout/drawer_admin_domain.xml @@ -18,6 +18,7 @@ diff --git a/app/src/main/res/layout/fragment_pagination.xml b/app/src/main/res/layout/fragment_pagination.xml index 2cf598f4..f26f23ce 100644 --- a/app/src/main/res/layout/fragment_pagination.xml +++ b/app/src/main/res/layout/fragment_pagination.xml @@ -17,6 +17,7 @@ @@ -92,4 +93,17 @@ + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4d07413c..8741ba6a 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -354,6 +354,13 @@ Custom emoji picker Favicon Add description for media (for the visually impaired) + + + Silence + Suspend + None + + Never 30 minutes @@ -1932,4 +1939,21 @@ New report (moderators) Open with another account Order lists + Reject media + Reject reports + The domain block will not prevent creation of account entries in the database, but will retroactively and automatically apply specific moderation methods on those accounts. + Severity + Silence will make the account\'s posts invisible to anyone who isn\'t following them. Suspend will remove all of the account\'s content, media, and profile data. Use None if you just want to reject media files. + Reject media files + Ignore all reports coming from this domain. Irrelevant for suspensions + Reject reports + Ignore all reports coming from this domain. Irrelevant for suspensions + Obfuscate domain name + Partially obfuscate the domain name in the list if advertising the list of domain limitations is enabled + Private comment + Comment about this domain limitation for internal use by the moderators. + Public comment + Comment about this domain limitation for the general public, if advertising the list of domain limitations is enabled. + Changes have been saved! + Create domain block \ No newline at end of file From 5d0d838471683e4b857b191394a3d3866f48e119 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 25 Nov 2022 15:13:25 +0100 Subject: [PATCH 56/61] Fix issue #527 - Visibility set to unlisted after changing language --- .../java/app/fedilab/android/ui/drawer/ComposeAdapter.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/ComposeAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/ComposeAdapter.java index 1fb1e7a7..dff83d9b 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/ComposeAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/ComposeAdapter.java @@ -143,6 +143,7 @@ public class ComposeAdapter extends RecyclerView.Adapter emojisList = new ArrayList<>(); public promptDraftListener promptDraftListener; + private boolean unlisted_changed = false; public ComposeAdapter(List statusList, int statusCount, BaseAccount account, app.fedilab.android.client.entities.api.Account mentionedAccount, String visibility, String editMessageId) { this.statusList = statusList; @@ -1244,10 +1245,10 @@ public class ComposeAdapter extends RecyclerView.Adapter 1) { + if (!unlisted_changed && position == 0 && unlistedReplies && statusDraft.visibility.equalsIgnoreCase("public") && statusList.size() > 1) { statusDraft.visibility = "unlisted"; } - } else if (position == statusCount && unlistedReplies && statusDraft.visibility.equalsIgnoreCase("public") && statusList.size() > 1) { + } else if (!unlisted_changed && position == statusCount && unlistedReplies && statusDraft.visibility.equalsIgnoreCase("public") && statusList.size() > 1) { statusDraft.visibility = "unlisted"; } @@ -1277,6 +1278,7 @@ public class ComposeAdapter extends RecyclerView.Adapter { holder.binding.visibilityPanel.setVisibility(View.GONE); @@ -1292,6 +1294,7 @@ public class ComposeAdapter extends RecyclerView.Adapter Date: Fri, 25 Nov 2022 15:03:39 +0100 Subject: [PATCH 57/61] Translated using Weblate (Turkish) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Currently translated at 100.0% (916 of 916 strings) Translated using Weblate (Turkish) Currently translated at 100.0% (915 of 915 strings) Co-authored-by: Oğuz Ersen Translate-URL: https://hosted.weblate.org/projects/fedilab/strings/tr/ Translation: Fedilab/Strings --- app/src/main/res/values-tr/strings.xml | 2 ++ 1 file changed, 2 insertions(+) diff --git a/app/src/main/res/values-tr/strings.xml b/app/src/main/res/values-tr/strings.xml index f672f81d..1661cd33 100644 --- a/app/src/main/res/values-tr/strings.xml +++ b/app/src/main/res/values-tr/strings.xml @@ -880,4 +880,6 @@ Bildirimleri sakla Yeni rapor (moderatörler) Kayıtlar + Başka bir hesapla aç + Listeleri sırala \ No newline at end of file From 66fb64c5d1939f811be80a0410ec0487f7016f08 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 25 Nov 2022 16:39:52 +0100 Subject: [PATCH 58/61] Improve blocked domains for admins --- .../activities/admin/AdminActionActivity.java | 36 +++++++++++++++- .../admin/AdminDomainBlockActivity.java | 38 ++++++++++++++++- .../app/fedilab/android/helper/Helper.java | 1 + .../fragment/admin/FragmentAdminDomain.java | 42 +++++++++++++++++++ .../android/viewmodel/mastodon/AdminVM.java | 28 +++++++++++-- app/src/main/res/menu/menu_admin_domain.xml | 9 ++++ 6 files changed, 148 insertions(+), 6 deletions(-) create mode 100644 app/src/main/res/menu/menu_admin_domain.xml diff --git a/app/src/main/java/app/fedilab/android/activities/admin/AdminActionActivity.java b/app/src/main/java/app/fedilab/android/activities/admin/AdminActionActivity.java index 34511eaa..a703ff41 100644 --- a/app/src/main/java/app/fedilab/android/activities/admin/AdminActionActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/admin/AdminActionActivity.java @@ -18,6 +18,10 @@ import static app.fedilab.android.activities.admin.AdminActionActivity.AdminEnum import static app.fedilab.android.activities.admin.AdminActionActivity.AdminEnum.DOMAIN; import static app.fedilab.android.activities.admin.AdminActionActivity.AdminEnum.REPORT; +import android.content.BroadcastReceiver; +import android.content.Context; +import android.content.Intent; +import android.content.IntentFilter; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; import android.view.Menu; @@ -29,11 +33,13 @@ import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentTransaction; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import com.google.gson.annotations.SerializedName; import app.fedilab.android.R; import app.fedilab.android.activities.BaseActivity; +import app.fedilab.android.client.entities.api.admin.AdminDomainBlock; import app.fedilab.android.databinding.ActivityAdminActionsBinding; import app.fedilab.android.databinding.PopupAdminFilterAccountsBinding; import app.fedilab.android.databinding.PopupAdminFilterReportsBinding; @@ -53,13 +59,32 @@ public class AdminActionActivity extends BaseActivity { private FragmentAdminAccount fragmentAdminAccount; private FragmentAdminDomain fragmentAdminDomain; + + private final BroadcastReceiver mReceiver = new BroadcastReceiver() { + @Override + public void onReceive(Context context, Intent intent) { + Bundle b = intent.getExtras(); + if (b != null) { + AdminDomainBlock adminDomainBlock = (AdminDomainBlock) b.getSerializable(Helper.ARG_ADMIN_DOMAINBLOCK); + AdminDomainBlock adminDomainBlockDelete = (AdminDomainBlock) b.getSerializable(Helper.ARG_ADMIN_DOMAINBLOCK_DELETE); + if (adminDomainBlock != null && adminDomainBlock.domain != null && fragmentAdminDomain != null) { + fragmentAdminDomain.update(adminDomainBlock); + } + if (adminDomainBlockDelete != null && fragmentAdminDomain != null) { + fragmentAdminDomain.delete(adminDomainBlockDelete); + } + } + + } + }; + @Override protected void onCreate(@Nullable Bundle savedInstanceState) { super.onCreate(savedInstanceState); ThemeHelper.applyThemeBar(this); binding = ActivityAdminActionsBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); - + LocalBroadcastManager.getInstance(this).registerReceiver(mReceiver, new IntentFilter(Helper.BROADCAST_DATA)); if (getSupportActionBar() != null) { getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setBackgroundDrawable(new ColorDrawable(ContextCompat.getColor(this, R.color.cyanea_primary))); @@ -68,6 +93,7 @@ public class AdminActionActivity extends BaseActivity { binding.reports.setOnClickListener(v -> displayTimeline(REPORT)); binding.accounts.setOnClickListener(v -> displayTimeline(ACCOUNT)); binding.domains.setOnClickListener(v -> displayTimeline(DOMAIN)); + } private void displayTimeline(AdminEnum type) { @@ -290,6 +316,14 @@ public class AdminActionActivity extends BaseActivity { return super.onOptionsItemSelected(item); } + @Override + protected void onDestroy() { + super.onDestroy(); + if (mReceiver != null) { + LocalBroadcastManager.getInstance(this).unregisterReceiver(mReceiver); + } + } + @Override public void onBackPressed() { if (canGoBack) { diff --git a/app/src/main/java/app/fedilab/android/activities/admin/AdminDomainBlockActivity.java b/app/src/main/java/app/fedilab/android/activities/admin/AdminDomainBlockActivity.java index e7d53d80..c14affb4 100644 --- a/app/src/main/java/app/fedilab/android/activities/admin/AdminDomainBlockActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/admin/AdminDomainBlockActivity.java @@ -15,15 +15,22 @@ package app.fedilab.android.activities.admin; * see . */ +import static app.fedilab.android.helper.Helper.BROADCAST_DATA; + +import android.content.Intent; import android.graphics.drawable.ColorDrawable; import android.os.Bundle; +import android.view.Menu; import android.view.MenuItem; import android.view.View; import android.widget.AdapterView; import android.widget.ArrayAdapter; +import androidx.annotation.NonNull; +import androidx.appcompat.app.AlertDialog; import androidx.core.content.ContextCompat; import androidx.lifecycle.ViewModelProvider; +import androidx.localbroadcastmanager.content.LocalBroadcastManager; import app.fedilab.android.R; import app.fedilab.android.activities.BaseActivity; @@ -104,18 +111,47 @@ public class AdminDomainBlockActivity extends BaseActivity { } else { Toasty.error(AdminDomainBlockActivity.this, getString(R.string.toast_error), Toasty.LENGTH_SHORT).show(); } - + Intent intent = new Intent(BROADCAST_DATA).putExtra(Helper.ARG_ADMIN_DOMAINBLOCK, adminDomainBlockResult); + LocalBroadcastManager.getInstance(AdminDomainBlockActivity.this).sendBroadcast(intent); + finish(); } ); }); } + @Override + public boolean onCreateOptionsMenu(@NonNull Menu menu) { + getMenuInflater().inflate(R.menu.menu_admin_domain, menu); + return super.onCreateOptionsMenu(menu); + } + @Override public boolean onOptionsItemSelected(MenuItem item) { int itemId = item.getItemId(); if (itemId == android.R.id.home) { finish(); return true; + } else if (itemId == R.id.action_delete) { + if (adminDomainBlock.id != null) { + AlertDialog.Builder builder = new AlertDialog.Builder(AdminDomainBlockActivity.this, Helper.dialogStyle()); + builder.setMessage(getString(R.string.unblock_domain_confirm, adminDomainBlock.domain)); + builder + .setPositiveButton(R.string.unblock_domain, (dialog, which) -> { + adminVM.deleteDomain(MainActivity.currentInstance, MainActivity.currentToken, adminDomainBlock.id) + .observe(AdminDomainBlockActivity.this, adminDomainBlockResult -> { + Intent intent = new Intent(BROADCAST_DATA).putExtra(Helper.ARG_ADMIN_DOMAINBLOCK_DELETE, adminDomainBlock); + LocalBroadcastManager.getInstance(AdminDomainBlockActivity.this).sendBroadcast(intent); + finish(); + } + ); + dialog.dismiss(); + }) + .setNegativeButton(R.string.cancel, (dialog, which) -> dialog.dismiss()) + .show(); + } else { + finish(); + } + } return true; } diff --git a/app/src/main/java/app/fedilab/android/helper/Helper.java b/app/src/main/java/app/fedilab/android/helper/Helper.java index 14d19388..0c816eeb 100644 --- a/app/src/main/java/app/fedilab/android/helper/Helper.java +++ b/app/src/main/java/app/fedilab/android/helper/Helper.java @@ -239,6 +239,7 @@ public class Helper { public static final String ARG_ACCOUNT = "ARG_ACCOUNT"; public static final String ARG_ACCOUNT_ID = "ARG_ACCOUNT_ID"; public static final String ARG_ADMIN_DOMAINBLOCK = "ARG_ADMIN_DOMAINBLOCK"; + public static final String ARG_ADMIN_DOMAINBLOCK_DELETE = "ARG_ADMIN_DOMAINBLOCK_DELETE"; public static final String ARG_REPORT = "ARG_REPORT"; public static final String ARG_ACCOUNT_MENTION = "ARG_ACCOUNT_MENTION"; diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java b/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java index d5f78812..5204eacb 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java @@ -62,6 +62,48 @@ public class FragmentAdminDomain extends Fragment { } } + public void delete(AdminDomainBlock adminDomainBlock) { + int position = 0; + for (AdminDomainBlock adminDomainBlockPresent : adminDomainBlocks) { + if (adminDomainBlockPresent.id.equals(adminDomainBlock.id)) { + adminDomainBlocks.remove(position); + adminDomainAdapter.notifyItemRemoved(position); + break; + } + position++; + } + } + + public void update(AdminDomainBlock adminDomainBlock) { + if (adminDomainBlocks == null) { + AdminDomainBlocks adminDomainBlocks = new AdminDomainBlocks(); + adminDomainBlocks.adminDomainBlocks = new ArrayList<>(); + adminDomainBlocks.adminDomainBlocks.add(adminDomainBlock); + initializeStatusesCommonView(adminDomainBlocks); + } + int position = 0; + boolean find = false; + for (AdminDomainBlock adminDomainBlockPresent : adminDomainBlocks) { + if (adminDomainBlockPresent.id.equals(adminDomainBlock.id)) { + adminDomainBlocks.get(position).private_comment = adminDomainBlock.private_comment; + adminDomainBlocks.get(position).public_comment = adminDomainBlock.public_comment; + adminDomainBlocks.get(position).severity = adminDomainBlock.severity; + adminDomainBlocks.get(position).reject_reports = adminDomainBlock.reject_reports; + adminDomainBlocks.get(position).reject_media = adminDomainBlock.reject_media; + adminDomainBlocks.get(position).obfuscate = adminDomainBlock.obfuscate; + adminDomainAdapter.notifyItemChanged(position); + find = true; + break; + } + position++; + } + if (!find) { + adminDomainBlocks.add(0, adminDomainBlock); + adminDomainAdapter.notifyItemInserted(0); + } + } + + public View onCreateView(@NonNull LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { diff --git a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java index c45fe41c..3d58ae0c 100644 --- a/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java +++ b/app/src/main/java/app/fedilab/android/viewmodel/mastodon/AdminVM.java @@ -17,7 +17,6 @@ package app.fedilab.android.viewmodel.mastodon; import android.app.Application; import android.os.Handler; import android.os.Looper; -import android.util.Log; import androidx.annotation.NonNull; import androidx.lifecycle.AndroidViewModel; @@ -56,6 +55,7 @@ public class AdminVM extends AndroidViewModel { private MutableLiveData adminReporstListMutableLiveData; private MutableLiveData adminDomainBlockMutableLiveData; private MutableLiveData adminDomainBlockListMutableLiveData; + private MutableLiveData booleanMutableLiveData; public AdminVM(@NonNull Application application) { @@ -658,11 +658,8 @@ public class AdminVM extends AndroidViewModel { Response getDomainBlocksResponse = getDomainBlock.execute(); if (getDomainBlocksResponse.isSuccessful()) { admDomainBlock = getDomainBlocksResponse.body(); - } else { - Log.v(Helper.TAG, "errr: " + getDomainBlocksResponse.errorBody().string()); } } catch (Exception e) { - Log.v(Helper.TAG, "e: " + e.getMessage()); e.printStackTrace(); } } @@ -709,6 +706,29 @@ public class AdminVM extends AndroidViewModel { return adminDomainBlockListMutableLiveData; } + public LiveData deleteDomain(@NonNull String instance, + String token, + String id) { + MastodonAdminService mastodonAdminService = init(instance); + booleanMutableLiveData = new MutableLiveData<>(); + final boolean[] successReply = {false}; + new Thread(() -> { + Call getDomainBlock = mastodonAdminService.deleteBlockDomain(token, id); + if (getDomainBlock != null) { + try { + Response response = getDomainBlock.execute(); + successReply[0] = response.isSuccessful(); + } catch (Exception e) { + e.printStackTrace(); + } + } + Handler mainHandler = new Handler(Looper.getMainLooper()); + Runnable myRunnable = () -> booleanMutableLiveData.setValue(successReply[0]); + mainHandler.post(myRunnable); + }).start(); + return booleanMutableLiveData; + } + /** * View a single allowed domain * diff --git a/app/src/main/res/menu/menu_admin_domain.xml b/app/src/main/res/menu/menu_admin_domain.xml new file mode 100644 index 00000000..d785c16b --- /dev/null +++ b/app/src/main/res/menu/menu_admin_domain.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file From 99644a7e64fb581285c3dca93b7bb227b0ef9e4c Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 25 Nov 2022 17:30:04 +0100 Subject: [PATCH 59/61] Some improvements --- .../fedilab/android/activities/ProfileActivity.java | 2 +- .../app/fedilab/android/helper/CustomEmoji.java | 7 +++---- .../app/fedilab/android/ui/drawer/FieldAdapter.java | 13 ++++++++++--- .../ui/fragment/admin/FragmentAdminDomain.java | 1 + 4 files changed, 15 insertions(+), 8 deletions(-) diff --git a/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java b/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java index 24f350b6..38be0df2 100644 --- a/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java +++ b/app/src/main/java/app/fedilab/android/activities/ProfileActivity.java @@ -364,7 +364,7 @@ public class ProfileActivity extends BaseActivity { //Fields for profile List fields = account.fields; if (fields != null && fields.size() > 0) { - FieldAdapter fieldAdapter = new FieldAdapter(fields); + FieldAdapter fieldAdapter = new FieldAdapter(fields, account); binding.fieldsContainer.setAdapter(fieldAdapter); binding.fieldsContainer.setLayoutManager(new LinearLayoutManager(ProfileActivity.this)); } diff --git a/app/src/main/java/app/fedilab/android/helper/CustomEmoji.java b/app/src/main/java/app/fedilab/android/helper/CustomEmoji.java index 51ce2fc8..10835111 100644 --- a/app/src/main/java/app/fedilab/android/helper/CustomEmoji.java +++ b/app/src/main/java/app/fedilab/android/helper/CustomEmoji.java @@ -52,12 +52,11 @@ public class CustomEmoji extends ReplacementSpan { if (imageDrawable != null) { canvas.save(); int emojiSize = (int) (paint.getTextSize() * scale); - Drawable drawable = imageDrawable; - drawable.setBounds(0, 0, emojiSize, emojiSize); - int transY = bottom - drawable.getBounds().bottom; + imageDrawable.setBounds(0, 0, emojiSize, emojiSize); + int transY = bottom - imageDrawable.getBounds().bottom; transY -= paint.getFontMetrics().descent / 2; canvas.translate(x, (float) transY); - drawable.draw(canvas); + imageDrawable.draw(canvas); canvas.restore(); } } diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/FieldAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/FieldAdapter.java index b4b39fc1..3513525e 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/FieldAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/FieldAdapter.java @@ -38,10 +38,11 @@ public class FieldAdapter extends RecyclerView.Adapter fields; private Context context; - private Account account; + private final Account account; - public FieldAdapter(List fields) { + public FieldAdapter(List fields, Account account) { this.fields = fields; + this.account = account; } @Override @@ -68,12 +69,18 @@ public class FieldAdapter extends RecyclerView.Adapter(holder.binding.value)), TextView.BufferType.SPANNABLE); holder.binding.value.setMovementMethod(LinkMovementMethod.getInstance()); - holder.binding.label.setText(field.name); + + holder.binding.label.setText( + field.getValueSpan(context, account, + new WeakReference<>(holder.binding.label)), + TextView.BufferType.SPANNABLE); + holder.binding.label.setMovementMethod(LinkMovementMethod.getInstance()); } diff --git a/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java b/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java index 5204eacb..6123be6a 100644 --- a/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java +++ b/app/src/main/java/app/fedilab/android/ui/fragment/admin/FragmentAdminDomain.java @@ -122,6 +122,7 @@ public class FragmentAdminDomain extends Fragment { adminVM = new ViewModelProvider(FragmentAdminDomain.this).get(viewModelKey, AdminVM.class); + binding.noActionText.setText(R.string.no_blocked_domains); binding.loader.setVisibility(View.VISIBLE); binding.recyclerView.setVisibility(View.GONE); flagLoading = false; From f029f785cc4e73664f24371af0e5eb10729dad7e Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 25 Nov 2022 17:48:56 +0100 Subject: [PATCH 60/61] Avoid crash with friendica and suggestions --- .../java/app/fedilab/android/ui/drawer/SuggestionAdapter.java | 3 +++ 1 file changed, 3 insertions(+) diff --git a/app/src/main/java/app/fedilab/android/ui/drawer/SuggestionAdapter.java b/app/src/main/java/app/fedilab/android/ui/drawer/SuggestionAdapter.java index c5b6918b..ced0db25 100644 --- a/app/src/main/java/app/fedilab/android/ui/drawer/SuggestionAdapter.java +++ b/app/src/main/java/app/fedilab/android/ui/drawer/SuggestionAdapter.java @@ -93,6 +93,9 @@ public class SuggestionAdapter extends RecyclerView.Adapter(holder.binding.displayName)), From 401eb4f1aee999f75f6dfe3d14f932790757abb0 Mon Sep 17 00:00:00 2001 From: Thomas Date: Fri, 25 Nov 2022 18:21:03 +0100 Subject: [PATCH 61/61] Release 3.8.0 --- app/build.gradle | 4 ++-- app/src/main/assets/release_notes/notes.json | 5 +++++ .../metadata/android/en/changelogs/433.txt | 18 ++++++++++++++++++ 3 files changed, 25 insertions(+), 2 deletions(-) create mode 100644 src/fdroid/fastlane/metadata/android/en/changelogs/433.txt diff --git a/app/build.gradle b/app/build.gradle index 9b970279..a677471a 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -13,8 +13,8 @@ android { defaultConfig { minSdk 21 targetSdk 31 - versionCode 432 - versionName "3.7.5" + versionCode 433 + versionName "3.8.0" testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" } flavorDimensions "default" diff --git a/app/src/main/assets/release_notes/notes.json b/app/src/main/assets/release_notes/notes.json index 8c0b3433..ce67924f 100644 --- a/app/src/main/assets/release_notes/notes.json +++ b/app/src/main/assets/release_notes/notes.json @@ -1,4 +1,9 @@ [ + { + "version": "3.8.0", + "code": "433", + "note": "Added:\n- List of blocked domains (allow to unblock)\n- Support gemini links\n- Suggested followers\n- Mod/Adm: Manage instance blocked domains\n- Open messages with another account\n- Allow to disable notifications for admins\n- Sort lists\n\nChanged:\n- Allow search term to be edited\n\nFixed:\n- Drafts deleted with no warning\n- Remove lists from \"Manage timelines\"\n- App crashes when proxy is set\n- Filter not synced after being edited\n- Some crashes / improvements" + }, { "version": "3.7.5", "code": "432", diff --git a/src/fdroid/fastlane/metadata/android/en/changelogs/433.txt b/src/fdroid/fastlane/metadata/android/en/changelogs/433.txt new file mode 100644 index 00000000..a0329a6d --- /dev/null +++ b/src/fdroid/fastlane/metadata/android/en/changelogs/433.txt @@ -0,0 +1,18 @@ +Added: +- List of blocked domains (allow to unblock) +- Support gemini links +- Suggested followers +- Mod/Adm: Manage instance blocked domains +- Open messages with another account +- Allow to disable notifications for admins +- Sort lists + +Changed: +- Allow search term to be edited + +Fixed: +- Drafts deleted with no warning +- Remove lists from "Manage timelines" +- App crashes when proxy is set +- Filter not synced after being edited +- Some crashes / improvements \ No newline at end of file