From ba78f6cb36623063f4403c96b5d01e0de36e411b Mon Sep 17 00:00:00 2001 From: "Mr. Traduttore" <53177864+MrTraduttore@users.noreply.github.com> Date: Mon, 22 Jul 2019 12:40:03 +0000 Subject: [PATCH 01/21] Improved Italian translation Fixed some errors and translated using Twitter's terms --- .../main/res-localized/values-it/strings.xml | 130 +++++++++--------- 1 file changed, 65 insertions(+), 65 deletions(-) diff --git a/twidere/src/main/res-localized/values-it/strings.xml b/twidere/src/main/res-localized/values-it/strings.xml index 2f9347259..9929a2650 100644 --- a/twidere/src/main/res-localized/values-it/strings.xml +++ b/twidere/src/main/res-localized/values-it/strings.xml @@ -29,14 +29,14 @@ Edita Aggiungi ai preferiti - Finish + Fine Segui Importa da… - Like - Muto + Mi piace + Silenzia %1$s, salvato alle %2$s Apri nel browser Scegli colore @@ -47,7 +47,7 @@ Riprova - Retweet + Ritwitta Salva Cerca @@ -63,17 +63,17 @@ Impostazioni Scatta una foto - Toggle - Traduce + Apri + Traduci Filtra utente Utenti filtrati Sblocca Annulla like - Togli dai preferiti + Rimuovi dai preferiti Smetti di seguire - Muto off - Cancellati + Riattiva + Disiscriviti Mappa Account attivati Mie Attività @@ -82,7 +82,7 @@ Aggiungi immagine e altri %d Formato API URL - La tua Twitter app + La tua app per Twitter Twidere Twidere si riavvierà per rendere effettive le nuove le impostazioni. @@ -92,7 +92,7 @@ Modalità twip O xAuth Auto refresh - Background + Sfondo Modalità risparmio dati Disabilita l\'anteprima dei media su connessione a consumo Appartiene a @@ -116,11 +116,11 @@ Commento… Schede compatte Visualizza più schede sullo schermo - Compose Now - Azione Compose Now - Sostituisce la scorciatoia a Google Now con la Compose screen + Componimento veloce + Azione Componimento veloce + Sostituisce la scorciatoia a Google Now con la schemata di composizione Confitti con %s - Timeout connessione + Timeout per la connessione Consumer Key Consumer secret Contenuto @@ -141,19 +141,19 @@ Impostazioni di default API Queste impostazioni veranno applicate al prossimo login Suoneria predefinita - Cancella conversazione - Cancellare tutti i messaggi di questa conversazione? + Elimina conversazione + Eliminare tutti i messaggi di questa conversazione? Eliminare le bozze selezionate? Cancellare questo messaggio? Elimina utente %s Eliminare %s? Non è reversibile. Rimuovere %1$s dalla lista "%2$s\"? Elimina lista %s - Eliminare lisra %s? Non potrà essere annullato. + Eliminare lista %s? Non potrà essere annullato. Elimina utenti - Lista cancellata \"%s\". + La lista \"%s\" è stata eliminata. Rimosso %1$s dalla lista "%2$s\". - Richiesta %s di follow negata. + Richiesta di follow di %s negata. Rifiuta Elimina ricerca salvata \"%s\" Eliminare la ricerca \"%s\"? È possibile salvarla nuovamente in seguito. @@ -200,19 +200,19 @@ Caricamento veloce immagini Seleziona per far caricare più velocemente le immagini, disattivalo se alcune immagini non sono mostrate correttamente. - Keywords + Parole chiave Collegamenti Fonti Utenti Richiesta di follow inviata Seguito %s. - I tuoi following + Persone che segui Mostra notifiche solo dagli utenti che segui. Chi ti segue Segui Carattere Font - Da Camera + Da fotocamera Da galleria Da %1$s Da %1$s e %2$d altri @@ -227,8 +227,8 @@ Hashtag Impostazioni nascoste MAI cambiare queste impostazioni se non sai esattamente cosa fanno, o potrebbero:\n * Uccidere il tuo gatto \n * Lanciare testate nucleari in Corea del Nord \n * fArtI tWitTtarE CM 1 PaxxErEllo\n * Distruggere l\'universo - ATTENZIONE: queste opzioni possono far male! - Nascondi azioni di carte + ATTENZIONE: queste opzioni creare danni! + Nascondi le azioni per il tweet Nascondi citazioni Nascondi risposte Nascondi retweets @@ -241,18 +241,18 @@ Indirizzo (può essere un altro indirizzo host) Host Rivoglio le mie stelline! - Usa i preferiti (★) piuttosto che i like (♥︎) + Usa i preferiti (★) al posto dei mi piace (♥︎) Icona Icona ripristinata! Importa/Esporta le impostazioni Importa impostazioni Importa impostazioni… - Risposta a %s + Rispondi a %s Inbox - Richieste di seguirti in attesa - Immissione testo + Richieste di follow in attesa + Immetti testo Interazioni - Chaive da consumatore invalida + Chiave da consumatore invalida Segreto consumatore invalido Deve iniziare con una lettera e può contenere solo lettere, numeri, \"-\" o \"_\". Tab non valida @@ -261,9 +261,9 @@ Indietro Premi i tasti Scorciatoie da tastiera - Keyword: %s + Parola chiave: %s Tipo di autenticazione - Background operation service + Servizio di operazione in background @@ -318,11 +318,11 @@ Membri Menziona questo utente Menziona %s - Mention %1$s + Menziona %1$s Solo menzioni Dati delle API corrotti. - [DOMAIN]: Twitter API domain.\nPer Esempio: https://[DOMAIN].twitter.com/ sarà sostituito da https://api.twitter.com/. - Bloccato %s. + [DOMAIN]: Dominio dell' API di Twitter.\nPer Esempio: https://[DOMAIN].twitter.com/ sarà sostituito da https://api.twitter.com/. + %s bloccato. Messaggio diretto cancellato. Messaggio diretto inviato. Attendere prego. @@ -336,30 +336,30 @@ Link copiato negli appunti Ecco un regalino per te, trovalo tra le impostazioni di sistema :) - Verifica di login fallita. + Verifica del login fallita. Nessun account Nessun account selezionato. Premi di nuovo per chiudere Immagine dell\'intestazione profilo aggiornata. Permesso di memoria richiesto per salvare questo media. - Salvato in Galleria. + Salvato nella Galleria. Permessi di memoria richiesti per selezionare il file. Alcune applicazioni richiedono permessi per condividere alcuni media. - Tweet salvato in bozze. + Tweet salvato nelle bozze. Tweet inviato. URL delle API non corretto, o consumer key/secret errata. Per favore, controllale. Selezione multipla - Muto %s + Silenzia %s Silenziare %s? Non vedrai più i tweets di questo utente ma continuerai a seguirlo. - Muto on %s - %1$s e un altro hanno ReTweettato - Retwittato da %1$s e da altri %2$d + %s silenziato + %1$s e un altro hanno Ritwittato + Ritwittato da %1$s e da altri %2$d Mostra il nome prima - Display \@nomeschermo primo + Mostra \@nomeschermo per primo Visualizza prima il nome %s non settato - ReTweettato da %s + Ritwittato da %s %1$s (%2$s) Navigazione Rete @@ -420,7 +420,7 @@ Scrittura sul database, aggiornamento degli stati Riduci il tweet Sincronizza timeline - Upload media + Carica media Richiesta di permessi L\'app richiede i seguenti permessi Avviso di rischio phishing @@ -429,11 +429,11 @@ Apre un avvertimento quando stai per aprire un link potenzialmente pericoloso in un DM. Foto Seleziona file - Play + Riproduci %1$s · %2$s Interrompi auto-aggiornamento quando la batteria è scarica Limite massimo degli elementi memorizzati nel database per ogni account, impostare su un valore inferiore per risparmiare spazio e aumentare la velocità di caricamento. - Accounts + Profili Avanzate Limite dimensione database Landscape @@ -441,7 +441,7 @@ Archivio Attiva streaming Dimensione del testo - Traduce + Traduci Località per i Trends Precarica solo se in Wi-Fi Anteprima @@ -465,7 +465,7 @@ Colore testo Profilo aggiornato. Avanzamento - Project account + Account del progetto Progetti di cui siamo parte Twidere @@ -515,9 +515,9 @@ Resettare le scorciatoie da tastiera ai valori di default? Ripristina come da default Riprova se si verifica un errore di rete - ReTweettato da %d utenti - ReTweettato da %s - ReTweettato da %1$s e altri %2$d + Ritwittato da %d utenti + Ritwittato da %s + Ritwittato da %1$s e altri %2$d I miei retweets Revoca permessi Tonda @@ -526,9 +526,9 @@ Salva nella galleria Ricerche salvate Forse hai già salvato questa ricerca - Tweet schedulati + Tweet programmati Discarica - Cerca tweets o utenti + Cerca tweet o utenti Ricerca tweet Tweet Utenti @@ -619,7 +619,7 @@ Timeline di streaming in esecuzione Servizio sincronizzazione timeline Informazioni - Accounts + Profili Aggiungi o rimuovi dalla lista Blocca %s Utenti bloccati @@ -635,7 +635,7 @@ Preferiti Filtri URL - Followers + Follower Stai seguendo Home @@ -654,17 +654,17 @@ Utenti Colori personalizzati Appartiene a - Users che hanno inserito tra i preferiti + Utenti che hanno inserito questo elemento tra i preferiti Utenti a cui piace - Utenti che hanno retwittato questo + Utenti che hanno retwittato questo elemento Lingua Traduttori - Trends - Località per i Trends - Scegli la località per i trends. + Tendenze + Località per le tendenze + Scegli la località per le tendenze. Tweet da %1$s - Tweet #%1$s + Twitta #%1$s Twidere test Digita un nome da cercare Digita per comporre @@ -676,12 +676,12 @@ Disinstalla Lingua sconosciuta Posizione sconosciuta - Muto off %s - Mutato %s + Riattiva %s + %s riattivato Conteggio elementi non letti Annulla l\'iscrizione alla lista %s Annullare l\'iscrizione alla lista %s? Potrai riscriverti in seguito. - Cancellato dalla lista \"%s\". + Rimosso dalla lista \"%s\". Per favore rettifica l\'azione qui sopra Invia tweet Dettagli della lista \"%s\" aggiornati. From 78cf31881a3fe02d1b9224052d98e19bc13c807f Mon Sep 17 00:00:00 2001 From: Mariotaku Date: Wed, 20 Nov 2019 17:54:30 +0900 Subject: [PATCH 02/21] =?UTF-8?q?=E3=81=95=E3=82=88=E3=81=AA=E3=82=89?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/README.md b/README.md index 7d32500a9..a0c5fd261 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,11 @@ +# Good bye, my friends! # + +Months ago, I started a long and slow migration to fit new Android frameworks. Unfortunately, Twidere has hundreds of thousands lines of code, and over 50% needs to be rewritten because there're breaking changes. It's even impossible to maintain as publishing to Google Play requires new API target, and that target requires new libraries, and those libraries will break current code. + +So It's time to say goodbye. Don't worry, I don't like Twitter's new timeline & ads as much as you do. I can't give any promise, but my plan is to start a new one, focusing on cleaness & simplicity. And before this proposed product, I want to build a web service to help you manage your Twitter data, such as removing spam followers (like [TwitBlock](https://twitblock.org/)). + +Let's looking forward to something better come to this world. + # Twidere for Android # [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Twidere-brightgreen.svg?style=flat)](http://android-arsenal.com/details/3/2453) From af7791588803253bf022af32d0889722eb7e21e5 Mon Sep 17 00:00:00 2001 From: Suji Yan Date: Sun, 2 Feb 2020 05:00:04 -0800 Subject: [PATCH 03/21] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index a0c5fd261..a95a07008 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,10 @@ -# Good bye, my friends! # +# We'll be back soon # Months ago, I started a long and slow migration to fit new Android frameworks. Unfortunately, Twidere has hundreds of thousands lines of code, and over 50% needs to be rewritten because there're breaking changes. It's even impossible to maintain as publishing to Google Play requires new API target, and that target requires new libraries, and those libraries will break current code. So It's time to say goodbye. Don't worry, I don't like Twitter's new timeline & ads as much as you do. I can't give any promise, but my plan is to start a new one, focusing on cleaness & simplicity. And before this proposed product, I want to build a web service to help you manage your Twitter data, such as removing spam followers (like [TwitBlock](https://twitblock.org/)). -Let's looking forward to something better come to this world. +Twidere-Android will be maintained by community and supporter including [Dimension](https://dimension.im/). Stay tuned and we'll back soon! # Twidere for Android # From 24fc6f7de664cc01f520d0ba0e9b654abd006f30 Mon Sep 17 00:00:00 2001 From: Suji Yan Date: Sun, 2 Feb 2020 07:08:29 -0800 Subject: [PATCH 04/21] Update README.md --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index a95a07008..be2f5b3d1 100644 --- a/README.md +++ b/README.md @@ -2,7 +2,7 @@ Months ago, I started a long and slow migration to fit new Android frameworks. Unfortunately, Twidere has hundreds of thousands lines of code, and over 50% needs to be rewritten because there're breaking changes. It's even impossible to maintain as publishing to Google Play requires new API target, and that target requires new libraries, and those libraries will break current code. -So It's time to say goodbye. Don't worry, I don't like Twitter's new timeline & ads as much as you do. I can't give any promise, but my plan is to start a new one, focusing on cleaness & simplicity. And before this proposed product, I want to build a web service to help you manage your Twitter data, such as removing spam followers (like [TwitBlock](https://twitblock.org/)). +Don't worry, I don't like Twitter's new timeline & ads as much as you do. I can't give any promise, but my plan is to start a new one, focusing on cleaness & simplicity. And before this proposed product, I want to build a web service to help you manage your Twitter data, such as removing spam followers (like [TwitBlock](https://twitblock.org/)). Twidere-Android will be maintained by community and supporter including [Dimension](https://dimension.im/). Stay tuned and we'll back soon! From 2d6952a0408d122c567ef1811055e4c5e1749a32 Mon Sep 17 00:00:00 2001 From: Suji Yan Date: Sat, 28 Mar 2020 22:52:58 +0800 Subject: [PATCH 05/21] Update README.md Twidere is back on Google Play and FDroid --- README.md | 10 ++-------- 1 file changed, 2 insertions(+), 8 deletions(-) diff --git a/README.md b/README.md index be2f5b3d1..564a31893 100644 --- a/README.md +++ b/README.md @@ -1,11 +1,3 @@ -# We'll be back soon # - -Months ago, I started a long and slow migration to fit new Android frameworks. Unfortunately, Twidere has hundreds of thousands lines of code, and over 50% needs to be rewritten because there're breaking changes. It's even impossible to maintain as publishing to Google Play requires new API target, and that target requires new libraries, and those libraries will break current code. - -Don't worry, I don't like Twitter's new timeline & ads as much as you do. I can't give any promise, but my plan is to start a new one, focusing on cleaness & simplicity. And before this proposed product, I want to build a web service to help you manage your Twitter data, such as removing spam followers (like [TwitBlock](https://twitblock.org/)). - -Twidere-Android will be maintained by community and supporter including [Dimension](https://dimension.im/). Stay tuned and we'll back soon! - # Twidere for Android # [![Android Arsenal](https://img.shields.io/badge/Android%20Arsenal-Twidere-brightgreen.svg?style=flat)](http://android-arsenal.com/details/3/2453) @@ -14,6 +6,8 @@ Twidere-Android will be maintained by community and supporter including [Dimensi Material Design ready and feature rich Twitter app for Android 4.0+ +Twidere-Android is maintained by community and supporter including [Dimension](https://dimension.im/). + --- ## Features ## From 925e041a4b23c6de41c6c8cb5fffbaf2d983d063 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Mon, 30 Mar 2020 17:03:15 +0800 Subject: [PATCH 06/21] Workaround for #1178 --- .../kotlin/org/mariotaku/twidere/view/ComposeEditText.kt | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/ComposeEditText.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/ComposeEditText.kt index 5ba1b5c9e..7b64219dc 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/ComposeEditText.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/ComposeEditText.kt @@ -89,6 +89,13 @@ class ComposeEditText( } catch (e: AbstractMethodError) { // http://crashes.to/s/69acd0ea0de return true + }catch (e: IndexOutOfBoundsException) { + e.printStackTrace() + // workaround + // https://github.com/TwidereProject/Twidere-Android/issues/1178 + setSelection(length() - 1, length() - 1) + setSelection(length(), length()) + return true } } From 4bc560e8d3ea5e7db03c2bc0fdbdd41a516e4d52 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 1 Apr 2020 14:36:00 +0800 Subject: [PATCH 07/21] Fix #1223 Only 1.1.0-alpha01 works now due to https://android.googlesource.com/platform/frameworks/support/+/6eae6436c7b70d783c2a2e53e582fe5a68f1ca2d remove ViewCompat.setSystemGestureExclusionRects call, DrawerLayout can not support automatic opt-out behavior out of the box. --- twidere/build.gradle | 1 + 1 file changed, 1 insertion(+) diff --git a/twidere/build.gradle b/twidere/build.gradle index 9d80f9787..0de070290 100644 --- a/twidere/build.gradle +++ b/twidere/build.gradle @@ -216,6 +216,7 @@ dependencies { implementation 'androidx.palette:palette:1.0.0' implementation 'androidx.legacy:legacy-preference-v14:1.0.0' implementation 'androidx.browser:browser:1.2.0' + implementation "androidx.drawerlayout:drawerlayout:1.1.0-alpha01" implementation 'com.google.android.material:material:1.1.0' implementation 'androidx.exifinterface:exifinterface:1.1.0' implementation "com.twitter:twitter-text:${libVersions['TwitterText']}" From 251b9b1d3bbf992023d682123397230555b5e3e0 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 1 Apr 2020 16:03:35 +0800 Subject: [PATCH 08/21] Fix #1067 --- .../twidere/activity/MediaViewerActivity.kt | 23 +++++++++++-------- 1 file changed, 13 insertions(+), 10 deletions(-) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MediaViewerActivity.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MediaViewerActivity.kt index 8ac7f5e4c..f1e00586d 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MediaViewerActivity.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MediaViewerActivity.kt @@ -73,6 +73,7 @@ import org.mariotaku.twidere.util.support.WindowSupport import org.mariotaku.twidere.view.viewer.MediaSwipeCloseContainer import java.io.File import javax.inject.Inject +import kotlin.concurrent.thread import android.Manifest.permission as AndroidPermissions class MediaViewerActivity : BaseActivity(), IMediaViewerActivity, MediaSwipeCloseContainer.Listener, @@ -521,17 +522,19 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity, MediaSwipeClos private fun openSaveToDocumentChooser() { if (Build.VERSION.SDK_INT < Build.VERSION_CODES.KITKAT) return val fileInfo = getCurrentCacheFileInfo(viewPager.currentItem) ?: return - val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) - intent.type = fileInfo.mimeType ?: "*/*" - intent.addCategory(Intent.CATEGORY_OPENABLE) - val extension = fileInfo.fileExtension - val saveFileName = if (extension != null) { - "${fileInfo.fileName?.removeSuffix("_$extension")}.$extension" - } else { - fileInfo.fileName + thread { + val intent = Intent(Intent.ACTION_CREATE_DOCUMENT) + intent.type = fileInfo.mimeType ?: "*/*" + intent.addCategory(Intent.CATEGORY_OPENABLE) + val extension = fileInfo.fileExtension + val saveFileName = if (extension != null) { + "${fileInfo.fileName?.removeSuffix("_$extension")}.$extension" + } else { + fileInfo.fileName + } + intent.putExtra(Intent.EXTRA_TITLE, saveFileName) + startActivityForResult(intent, REQUEST_SELECT_SAVE_MEDIA) } - intent.putExtra(Intent.EXTRA_TITLE, saveFileName) - startActivityForResult(intent, REQUEST_SELECT_SAVE_MEDIA) } private fun saveMediaToContentUri(data: Uri) { From 177342a339692b0ebe2087d8e9b07221bb668d4f Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 1 Apr 2020 16:06:04 +0800 Subject: [PATCH 09/21] Add android.permission.FOREGROUND_SERVICE --- twidere/src/main/AndroidManifest.xml | 1 + 1 file changed, 1 insertion(+) diff --git a/twidere/src/main/AndroidManifest.xml b/twidere/src/main/AndroidManifest.xml index 0fba8b659..d013ecc65 100644 --- a/twidere/src/main/AndroidManifest.xml +++ b/twidere/src/main/AndroidManifest.xml @@ -58,6 +58,7 @@ + Date: Wed, 1 Apr 2020 16:10:03 +0800 Subject: [PATCH 10/21] Wrap to safeGetInt when getting gap --- .../org/mariotaku/twidere/adapter/ParcelableStatusesAdapter.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/ParcelableStatusesAdapter.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/ParcelableStatusesAdapter.kt index 899d31901..db73a7492 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/ParcelableStatusesAdapter.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/adapter/ParcelableStatusesAdapter.kt @@ -453,7 +453,7 @@ abstract class ParcelableStatusesAdapter( val timestamp = cursor.safeGetLong(indices[Statuses.TIMESTAMP]) val sortId = cursor.safeGetLong(indices[Statuses.SORT_ID]) val positionKey = cursor.safeGetLong(indices[Statuses.POSITION_KEY]) - val gap = cursor.getInt(indices[Statuses.IS_GAP]) == 1 + val gap = cursor.safeGetInt(indices[Statuses.IS_GAP]) == 1 val newInfo = StatusInfo(_id, accountKey, id, timestamp, sortId, positionKey, gap) infoCache?.set(dataPosition, newInfo) return@run newInfo From 149bbbf9c2ff53db0eb30b09d7b2b795746d9edf Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 1 Apr 2020 16:10:47 +0800 Subject: [PATCH 11/21] Check fetched items is not empty before inserting --- .../org/mariotaku/twidere/task/twitter/GetActivitiesTask.kt | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesTask.kt index 00642782c..6703722d8 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesTask.kt @@ -181,8 +181,10 @@ abstract class GetActivitiesTask( valuesList[valuesList.size - 1].put(Activities.IS_GAP, true) } } - // Insert previously fetched items. - ContentResolverUtils.bulkInsert(cr, writeUri, valuesList) + if (valuesList.isNotEmpty()) { + // Insert previously fetched items. + ContentResolverUtils.bulkInsert(cr, writeUri, valuesList) + } // Remove gap flag if (maxId != null && sinceId == null) { From 1db9968359c2fcd031d21f2925274ed1901d8f28 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 1 Apr 2020 16:26:13 +0800 Subject: [PATCH 12/21] Fix build error --- twidere/src/main/res-localized/values-it/strings.xml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twidere/src/main/res-localized/values-it/strings.xml b/twidere/src/main/res-localized/values-it/strings.xml index 9929a2650..3d2cbc235 100644 --- a/twidere/src/main/res-localized/values-it/strings.xml +++ b/twidere/src/main/res-localized/values-it/strings.xml @@ -321,7 +321,7 @@ Menziona %1$s Solo menzioni Dati delle API corrotti. - [DOMAIN]: Dominio dell' API di Twitter.\nPer Esempio: https://[DOMAIN].twitter.com/ sarà sostituito da https://api.twitter.com/. + [DOMAIN]: Dominio dell\' API di Twitter.\nPer Esempio: https://[DOMAIN].twitter.com/ sarà sostituito da https://api.twitter.com/. %s bloccato. Messaggio diretto cancellato. Messaggio diretto inviato. From 70d3d1f67f384f475748fc3852da5e2605ab9d08 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Wed, 1 Apr 2020 17:50:00 +0800 Subject: [PATCH 13/21] Version 4.0.4 --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 06958c277..94720ed43 100644 --- a/build.gradle +++ b/build.gradle @@ -17,8 +17,8 @@ allprojects { ext { projectGroupId = 'org.mariotaku.twidere' - projectVersionCode = 502 - projectVersionName = '4.0.3' + projectVersionCode = 503 + projectVersionName = '4.0.4' globalCompileSdkVersion = 29 globalBuildToolsVersion = '29.0.2' From 4fa12257420e7c900acb3fa150b8410c7576eacb Mon Sep 17 00:00:00 2001 From: Tlaster Date: Thu, 2 Apr 2020 15:04:31 +0800 Subject: [PATCH 14/21] Update storage api for android q --- .../twidere/activity/MediaViewerActivity.kt | 19 ++++++-- .../twidere/task/SaveMediaToGalleryTask.kt | 47 ++++++++++++++++++- 2 files changed, 62 insertions(+), 4 deletions(-) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MediaViewerActivity.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MediaViewerActivity.kt index f1e00586d..df5f88f82 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MediaViewerActivity.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/MediaViewerActivity.kt @@ -26,6 +26,7 @@ import android.os.Build import android.os.Bundle import android.os.Environment import android.os.Parcelable +import android.provider.MediaStore import androidx.annotation.RequiresApi import androidx.fragment.app.DialogFragment import androidx.fragment.app.Fragment @@ -505,13 +506,25 @@ class MediaViewerActivity : BaseActivity(), IMediaViewerActivity, MediaSwipeClos val type = (fileInfo as? CacheProvider.CacheFileTypeSupport)?.cacheFileType val pubDir = when (type) { CacheFileType.VIDEO -> { - Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_MOVIES) + } else { + getExternalFilesDir(Environment.DIRECTORY_MOVIES) + } } CacheFileType.IMAGE -> { - Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_PICTURES) + } else { + getExternalFilesDir(Environment.DIRECTORY_PICTURES) + } } else -> { - Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + if (Build.VERSION.SDK_INT < Build.VERSION_CODES.Q) { + Environment.getExternalStoragePublicDirectory(Environment.DIRECTORY_DOWNLOADS) + } else { + getExternalFilesDir(Environment.DIRECTORY_DOWNLOADS) + } } } val saveDir = File(pubDir, "Twidere") diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/SaveMediaToGalleryTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/SaveMediaToGalleryTask.kt index 75f44a444..f1f5b1c7b 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/SaveMediaToGalleryTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/SaveMediaToGalleryTask.kt @@ -20,9 +20,15 @@ package org.mariotaku.twidere.task import android.app.Activity +import android.content.ContentValues import android.media.MediaScannerConnection +import android.os.Build +import android.os.Environment +import android.provider.MediaStore import android.widget.Toast import org.mariotaku.twidere.R +import org.mariotaku.twidere.annotation.CacheFileType +import org.mariotaku.twidere.provider.CacheProvider import java.io.File /** @@ -30,14 +36,53 @@ import java.io.File */ class SaveMediaToGalleryTask( activity: Activity, - fileInfo: FileInfo, + private val fileInfo: FileInfo, destination: File ) : ProgressSaveFileTask(activity, destination, fileInfo) { override fun onFileSaved(savedFile: File, mimeType: String?) { val context = context ?: return + MediaScannerConnection.scanFile(context, arrayOf(savedFile.path), arrayOf(mimeType), null) + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) { + val type = (fileInfo as? CacheProvider.CacheFileTypeSupport)?.cacheFileType + val path = when (type) { + CacheFileType.VIDEO -> { + Environment.DIRECTORY_MOVIES + } + CacheFileType.IMAGE -> { + Environment.DIRECTORY_PICTURES + } + else -> { + Environment.DIRECTORY_DOWNLOADS + } + } + val url = when (type) { + CacheFileType.VIDEO -> { + MediaStore.Video.Media.EXTERNAL_CONTENT_URI + } + CacheFileType.IMAGE -> { + MediaStore.Images.Media.EXTERNAL_CONTENT_URI + } + else -> { + MediaStore.Downloads.EXTERNAL_CONTENT_URI + } + } + val contentValues = ContentValues() + contentValues.put(MediaStore.Images.Media.DISPLAY_NAME, fileInfo.fileName) + contentValues.put(MediaStore.Images.Media.DATE_TAKEN, System.currentTimeMillis()) + contentValues.put(MediaStore.Images.Media.MIME_TYPE, fileInfo.mimeType) + contentValues.put(MediaStore.MediaColumns.RELATIVE_PATH, "$path/Twidere") + context.contentResolver.insert(url, contentValues)?.let { uri -> + context.contentResolver.openOutputStream(uri)?.use { + savedFile.inputStream().use { fileInputStream -> + fileInputStream.copyTo(it) + } + } + } + } + savedFile.delete() Toast.makeText(context, R.string.message_toast_saved_to_gallery, Toast.LENGTH_SHORT).show() } From 1ab66a9e95b54a9c0070dba96f05798dc401c572 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Thu, 2 Apr 2020 15:21:11 +0800 Subject: [PATCH 15/21] Show keyboard when enter ComposeActivity with default intent --- .../kotlin/org/mariotaku/twidere/activity/ComposeActivity.kt | 1 + 1 file changed, 1 insertion(+) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/ComposeActivity.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/ComposeActivity.kt index 62d110741..d9dd6e598 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/ComposeActivity.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/ComposeActivity.kt @@ -1127,6 +1127,7 @@ class ComposeActivity : BaseActivity(), OnMenuItemClickListener, OnClickListener } else { editText.setSelection(selection.coerceIn(0..editText.length())) } + editText.requestFocus() return true } From 2407b9f193424511fe97f483692de85877814901 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 3 Apr 2020 17:14:42 +0800 Subject: [PATCH 16/21] Revert "Removed private API usages" This reverts commit 5052d1fc62809e7fa5aba5f256e0cd31c63f2c0c. --- .../microblog/library/twitter/Twitter.java | 2 +- .../library/twitter/TwitterPrivate.java | 35 +++ .../twitter/api/PrivateAccountResources.java | 37 +++ .../twitter/api/PrivateActivityResources.java | 52 ++++ .../api/PrivateDirectMessagesResources.java | 102 ++++++++ .../api/PrivateFriendsFollowersResources.java | 43 ++++ .../twitter/api/PrivateMutesResources.java | 42 ++++ .../library/twitter/api/PrivateResources.java | 23 ++ .../twitter/api/PrivateSearchResources.java | 38 +++ .../twitter/api/PrivateTimelineResources.java | 39 +++ .../twitter/api/PrivateTweetResources.java | 45 ++++ .../twidere/model/ConsumerKeyType.java | 3 +- .../model/account/TwitterAccountExtras.java | 14 ++ .../twidere/model/util/AccountUtils.java | 17 ++ .../util/InternalTwitterContentUtils.java | 56 +++++ .../twidere/util/MicroBlogAPIFactory.java | 15 ++ .../twidere/activity/SignInActivity.kt | 15 +- .../model/AccountDetailsExtensions.kt | 21 ++ .../extension/model/AccountExtensions.kt | 13 + .../extension/model/CredentialsExtensions.kt | 5 +- .../twidere/fragment/AbsActivitiesFragment.kt | 11 +- .../twidere/fragment/CustomTabsFragment.kt | 7 +- .../MessageConversationInfoFragment.kt | 72 ++++++ .../message/MessageNewConversationFragment.kt | 7 +- .../status/PinStatusDialogFragment.kt | 55 ++++ .../twidere/fragment/status/StatusFragment.kt | 41 +++ .../status/UnpinStatusDialogFragment.kt | 55 ++++ .../users/IncomingFriendshipsFragment.kt | 3 + .../loader/statuses/ConversationLoader.kt | 6 + .../statuses/MediaStatusesSearchLoader.kt | 14 ++ .../loader/statuses/MediaTimelineLoader.kt | 42 ++-- .../loader/statuses/TweetSearchLoader.kt | 13 + .../loader/statuses/UserMentionsLoader.kt | 4 + .../loader/users/StatusFavoritersLoader.kt | 5 +- .../tab/impl/InteractionsTabConfiguration.kt | 7 +- .../twidere/service/StreamingService.kt | 235 ++++++++++++++++++ .../twidere/task/AcceptFriendshipTask.kt | 5 +- .../twidere/task/DenyFriendshipTask.kt | 5 +- .../twidere/task/status/PinStatusTask.kt | 52 ++++ .../twidere/task/status/UnpinStatusTask.kt | 52 ++++ .../task/twitter/GetActivitiesAboutMeTask.kt | 23 ++ .../twidere/task/twitter/GetStatusesTask.kt | 2 +- .../twitter/message/AddParticipantsTask.kt | 97 ++++++++ .../message/DestroyConversationTask.kt | 4 + .../twitter/message/DestroyMessageTask.kt | 4 + .../task/twitter/message/GetMessagesTask.kt | 70 +++++- .../twitter/message/MarkMessageReadTask.kt | 18 ++ .../task/twitter/message/SendMessageTask.kt | 49 +++- ...SetConversationNotificationDisabledTask.kt | 19 ++ .../twidere/util/AsyncTwitterWrapper.kt | 12 + .../mariotaku/twidere/util/DataStoreUtils.kt | 19 +- .../org/mariotaku/twidere/util/MenuUtils.kt | 9 +- .../holder/status/DetailStatusViewHolder.kt | 22 +- .../main/res/values/arrays_no_translate.xml | 51 ++++ 54 files changed, 1659 insertions(+), 48 deletions(-) create mode 100644 twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/TwitterPrivate.java create mode 100644 twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateAccountResources.java create mode 100644 twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateActivityResources.java create mode 100644 twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateDirectMessagesResources.java create mode 100644 twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateFriendsFollowersResources.java create mode 100644 twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateMutesResources.java create mode 100644 twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateResources.java create mode 100644 twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateSearchResources.java create mode 100644 twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateTimelineResources.java create mode 100644 twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateTweetResources.java create mode 100644 twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/PinStatusDialogFragment.kt create mode 100644 twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/UnpinStatusDialogFragment.kt create mode 100644 twidere/src/main/kotlin/org/mariotaku/twidere/task/status/PinStatusTask.kt create mode 100644 twidere/src/main/kotlin/org/mariotaku/twidere/task/status/UnpinStatusTask.kt create mode 100644 twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/AddParticipantsTask.kt diff --git a/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/Twitter.java b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/Twitter.java index d3cc2c7dc..94b11e67c 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/Twitter.java +++ b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/Twitter.java @@ -41,5 +41,5 @@ public interface Twitter extends SearchResources, TimelineResources, TweetResour ListResources, DirectMessagesResources, DirectMessagesEventResources, FriendsFollowersResources, FavoritesResources, SpamReportingResources, SavedSearchesResources, TrendsResources, PlacesGeoResources, - HelpResources, MutesResources { + HelpResources, MutesResources, TwitterPrivate { } diff --git a/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/TwitterPrivate.java b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/TwitterPrivate.java new file mode 100644 index 000000000..4414f99be --- /dev/null +++ b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/TwitterPrivate.java @@ -0,0 +1,35 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright 2012-2017 Mariotaku Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.mariotaku.microblog.library.twitter; + +import org.mariotaku.microblog.library.twitter.api.PrivateAccountResources; +import org.mariotaku.microblog.library.twitter.api.PrivateActivityResources; +import org.mariotaku.microblog.library.twitter.api.PrivateDirectMessagesResources; +import org.mariotaku.microblog.library.twitter.api.PrivateFriendsFollowersResources; +import org.mariotaku.microblog.library.twitter.api.PrivateSearchResources; +import org.mariotaku.microblog.library.twitter.api.PrivateTimelineResources; +import org.mariotaku.microblog.library.twitter.api.PrivateTweetResources; + +/** + * Created by mariotaku on 16/3/4. + */ +public interface TwitterPrivate extends PrivateActivityResources, PrivateTweetResources, + PrivateTimelineResources, PrivateFriendsFollowersResources, PrivateDirectMessagesResources, + PrivateSearchResources, PrivateAccountResources { +} diff --git a/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateAccountResources.java b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateAccountResources.java new file mode 100644 index 000000000..9e6a699af --- /dev/null +++ b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateAccountResources.java @@ -0,0 +1,37 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright 2012-2017 Mariotaku Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.mariotaku.microblog.library.twitter.api; + +import org.mariotaku.microblog.library.MicroBlogException; +import org.mariotaku.microblog.library.twitter.model.PinTweetResult; +import org.mariotaku.restfu.annotation.method.POST; +import org.mariotaku.restfu.annotation.param.Param; + +/** + * Created by mariotaku on 16/8/20. + */ +public interface PrivateAccountResources extends PrivateResources { + + @POST("/account/pin_tweet.json") + PinTweetResult pinTweet(@Param("id") String id) throws MicroBlogException; + + @POST("/account/unpin_tweet.json") + PinTweetResult unpinTweet(@Param("id") String id) throws MicroBlogException; + +} diff --git a/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateActivityResources.java b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateActivityResources.java new file mode 100644 index 000000000..92e13c820 --- /dev/null +++ b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateActivityResources.java @@ -0,0 +1,52 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright 2012-2017 Mariotaku Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.mariotaku.microblog.library.twitter.api; + +import org.mariotaku.microblog.library.MicroBlogException; +import org.mariotaku.microblog.library.twitter.model.Activity; +import org.mariotaku.microblog.library.twitter.model.CursorTimestampResponse; +import org.mariotaku.microblog.library.twitter.model.Paging; +import org.mariotaku.microblog.library.twitter.model.ResponseList; +import org.mariotaku.microblog.library.twitter.template.StatusAnnotationTemplate; +import org.mariotaku.restfu.annotation.method.GET; +import org.mariotaku.restfu.annotation.method.POST; +import org.mariotaku.restfu.annotation.param.Param; +import org.mariotaku.restfu.annotation.param.Params; +import org.mariotaku.restfu.annotation.param.Queries; +import org.mariotaku.restfu.annotation.param.Query; +import org.mariotaku.restfu.http.BodyType; + +@SuppressWarnings("RedundantThrows") +@Params(template = StatusAnnotationTemplate.class) +public interface PrivateActivityResources extends PrivateResources { + + @GET("/activity/about_me.json") + ResponseList getActivitiesAboutMe(@Query Paging paging) throws MicroBlogException; + + @Queries({}) + @GET("/activity/about_me/unread.json") + CursorTimestampResponse getActivitiesAboutMeUnread(@Query("cursor") boolean cursor) throws MicroBlogException; + + @Queries({}) + @POST("/activity/about_me/unread.json") + @BodyType(BodyType.FORM) + CursorTimestampResponse setActivitiesAboutMeUnread(@Param("cursor") long cursor) throws MicroBlogException; + + +} diff --git a/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateDirectMessagesResources.java b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateDirectMessagesResources.java new file mode 100644 index 000000000..8aedefa37 --- /dev/null +++ b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateDirectMessagesResources.java @@ -0,0 +1,102 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright 2012-2017 Mariotaku Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.mariotaku.microblog.library.twitter.api; + +import android.support.annotation.Nullable; + +import org.mariotaku.microblog.library.MicroBlogException; +import org.mariotaku.microblog.library.twitter.model.ConversationTimeline; +import org.mariotaku.microblog.library.twitter.model.DMResponse; +import org.mariotaku.microblog.library.twitter.model.NewDm; +import org.mariotaku.microblog.library.twitter.model.Paging; +import org.mariotaku.microblog.library.twitter.model.ResponseCode; +import org.mariotaku.microblog.library.twitter.model.UserEvents; +import org.mariotaku.microblog.library.twitter.model.UserInbox; +import org.mariotaku.microblog.library.twitter.template.DMAnnotationTemplate; +import org.mariotaku.restfu.annotation.method.GET; +import org.mariotaku.restfu.annotation.method.POST; +import org.mariotaku.restfu.annotation.param.Param; +import org.mariotaku.restfu.annotation.param.Params; +import org.mariotaku.restfu.annotation.param.Path; +import org.mariotaku.restfu.annotation.param.Query; +import org.mariotaku.restfu.http.BodyType; + +@Params(template = DMAnnotationTemplate.class) +public interface PrivateDirectMessagesResources extends PrivateResources { + + @POST("/dm/conversation/{conversation_id}/delete.json") + @BodyType(BodyType.FORM) + ResponseCode deleteDmConversation(@Path("conversation_id") String conversationId) + throws MicroBlogException; + + @POST("/dm/conversation/{conversation_id}/mark_read.json") + @BodyType(BodyType.FORM) + ResponseCode markDmRead(@Path("conversation_id") String conversationId, + @Param("last_read_event_id") String lastReadEventId) throws MicroBlogException; + + @POST("/dm/update_last_seen_event_id.json") + @BodyType(BodyType.FORM) + ResponseCode updateLastSeenEventId(@Param("last_seen_event_id") String lastSeenEventId) throws MicroBlogException; + + @POST("/dm/conversation/{conversation_id}/update_name.json") + @BodyType(BodyType.FORM) + ResponseCode updateDmConversationName(@Path("conversation_id") String conversationId, + @Param("name") String name) throws MicroBlogException; + + /** + * Update DM conversation avatar + * + * @param conversationId DM conversation ID + * @param avatarId Avatar media ID, null for removing avatar + * @return HTTP response code + */ + @POST("/dm/conversation/{conversation_id}/update_avatar.json") + @BodyType(BodyType.FORM) + ResponseCode updateDmConversationAvatar(@Path("conversation_id") String conversationId, + @Param(value = "avatar_id", ignoreOnNull = true) @Nullable String avatarId) throws MicroBlogException; + + @POST("/dm/conversation/{conversation_id}/disable_notifications.json") + ResponseCode disableDmConversations(@Path("conversation_id") String conversationId) + throws MicroBlogException; + + @POST("/dm/conversation/{conversation_id}/enable_notifications.json") + ResponseCode enableDmConversations(@Path("conversation_id") String conversationId) + throws MicroBlogException; + + @POST("/dm/new.json") + DMResponse sendDm(@Param NewDm newDm) throws MicroBlogException; + + @POST("/dm/conversation/{conversation_id}/add_participants.json") + DMResponse addParticipants(@Path("conversation_id") String conversationId, + @Param(value = "participant_ids", arrayDelimiter = ',') String[] participantIds) + throws MicroBlogException; + + @POST("/dm/destroy.json") + ResponseCode destroyDm(@Param("dm_id") String id) throws MicroBlogException; + + @GET("/dm/user_inbox.json") + UserInbox getUserInbox(@Query Paging paging) throws MicroBlogException; + + @GET("/dm/user_updates.json") + UserEvents getUserUpdates(@Query("cursor") String cursor) throws MicroBlogException; + + @GET("/dm/conversation/{conversation_id}.json") + ConversationTimeline getDmConversation(@Path("conversation_id") String conversationId, + @Query Paging paging) throws MicroBlogException; +} diff --git a/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateFriendsFollowersResources.java b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateFriendsFollowersResources.java new file mode 100644 index 000000000..82cbedbd7 --- /dev/null +++ b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateFriendsFollowersResources.java @@ -0,0 +1,43 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright 2012-2017 Mariotaku Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.mariotaku.microblog.library.twitter.api; + +import org.mariotaku.microblog.library.MicroBlogException; +import org.mariotaku.microblog.library.twitter.model.User; +import org.mariotaku.restfu.annotation.method.POST; +import org.mariotaku.restfu.annotation.param.KeyValue; +import org.mariotaku.restfu.annotation.param.Param; +import org.mariotaku.restfu.annotation.param.Queries; + +@Queries({@KeyValue(key = "include_entities", valueKey = "include_entities")}) +public interface PrivateFriendsFollowersResources extends PrivateResources { + + @POST("/friendships/accept.json") + User acceptFriendship(@Param("user_id") String userId) throws MicroBlogException; + + @POST("/friendships/accept.json") + User acceptFriendshipByScreenName(@Param("screen_name") String screenName) throws MicroBlogException; + + @POST("/friendships/deny.json") + User denyFriendship(@Param("user_id") String userId) throws MicroBlogException; + + @POST("/friendships/deny.json") + User denyFriendshipByScreenName(@Param("screen_name") String screenName) throws MicroBlogException; + +} diff --git a/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateMutesResources.java b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateMutesResources.java new file mode 100644 index 000000000..deb4e98dc --- /dev/null +++ b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateMutesResources.java @@ -0,0 +1,42 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright 2012-2017 Mariotaku Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.mariotaku.microblog.library.twitter.api; + +import org.mariotaku.microblog.library.MicroBlogException; +import org.mariotaku.microblog.library.twitter.model.IDs; +import org.mariotaku.microblog.library.twitter.model.MutedKeyword; +import org.mariotaku.microblog.library.twitter.model.PageableResponseList; +import org.mariotaku.microblog.library.twitter.model.Paging; +import org.mariotaku.microblog.library.twitter.template.UserAnnotationTemplate; +import org.mariotaku.restfu.annotation.method.GET; +import org.mariotaku.restfu.annotation.param.Params; +import org.mariotaku.restfu.annotation.param.Query; + +/** + * Created by mariotaku on 2017/3/26. + */ +@Params(template = UserAnnotationTemplate.class) +public interface PrivateMutesResources { + + @GET("/mutes/keywords/ids.json") + IDs getMutesKeywordsIDs(Paging paging) throws MicroBlogException; + + @GET("/mutes/keywords/list.json") + PageableResponseList getMutesKeywordsList(@Query Paging paging) throws MicroBlogException; +} diff --git a/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateResources.java b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateResources.java new file mode 100644 index 000000000..03a6f8c98 --- /dev/null +++ b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateResources.java @@ -0,0 +1,23 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright 2012-2017 Mariotaku Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.mariotaku.microblog.library.twitter.api; + +public interface PrivateResources { + +} diff --git a/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateSearchResources.java b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateSearchResources.java new file mode 100644 index 000000000..750fd4dac --- /dev/null +++ b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateSearchResources.java @@ -0,0 +1,38 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright 2012-2017 Mariotaku Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.mariotaku.microblog.library.twitter.api; + +import org.mariotaku.microblog.library.MicroBlogException; +import org.mariotaku.microblog.library.twitter.model.UniversalSearchQuery; +import org.mariotaku.microblog.library.twitter.model.UniversalSearchResult; +import org.mariotaku.microblog.library.twitter.template.StatusAnnotationTemplate; +import org.mariotaku.restfu.annotation.method.GET; +import org.mariotaku.restfu.annotation.param.Params; +import org.mariotaku.restfu.annotation.param.Query; + +/** + * Created by mariotaku on 15/10/21. + */ +public interface PrivateSearchResources extends PrivateResources { + + @GET("/search/universal.json") + @Params(template = StatusAnnotationTemplate.class) + UniversalSearchResult universalSearch(@Query UniversalSearchQuery query) throws MicroBlogException; + +} diff --git a/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateTimelineResources.java b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateTimelineResources.java new file mode 100644 index 000000000..3b589dac1 --- /dev/null +++ b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateTimelineResources.java @@ -0,0 +1,39 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright 2012-2017 Mariotaku Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.mariotaku.microblog.library.twitter.api; + +import org.mariotaku.microblog.library.MicroBlogException; +import org.mariotaku.microblog.library.twitter.model.Paging; +import org.mariotaku.microblog.library.twitter.model.ResponseList; +import org.mariotaku.microblog.library.twitter.model.Status; +import org.mariotaku.microblog.library.twitter.template.StatusAnnotationTemplate; +import org.mariotaku.restfu.annotation.method.GET; +import org.mariotaku.restfu.annotation.param.Params; +import org.mariotaku.restfu.annotation.param.Query; + +@SuppressWarnings("RedundantThrows") +@Params(template = StatusAnnotationTemplate.class) +public interface PrivateTimelineResources extends PrivateResources { + + @GET("/statuses/media_timeline.json") + ResponseList getMediaTimeline(@Query("user_id") String userId, @Query Paging paging) throws MicroBlogException; + + @GET("/statuses/media_timeline.json") + ResponseList getMediaTimelineByScreenName(@Query("screen_name") String screenName, @Query Paging paging) throws MicroBlogException; +} diff --git a/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateTweetResources.java b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateTweetResources.java new file mode 100644 index 000000000..de7a033d4 --- /dev/null +++ b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateTweetResources.java @@ -0,0 +1,45 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright 2012-2017 Mariotaku Lee + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.mariotaku.microblog.library.twitter.api; + +import org.mariotaku.microblog.library.MicroBlogException; +import org.mariotaku.microblog.library.twitter.model.Paging; +import org.mariotaku.microblog.library.twitter.model.ResponseList; +import org.mariotaku.microblog.library.twitter.model.Status; +import org.mariotaku.microblog.library.twitter.model.StatusActivitySummary; +import org.mariotaku.microblog.library.twitter.model.TranslationResult; +import org.mariotaku.microblog.library.twitter.template.StatusAnnotationTemplate; +import org.mariotaku.restfu.annotation.method.GET; +import org.mariotaku.restfu.annotation.param.Params; +import org.mariotaku.restfu.annotation.param.Path; +import org.mariotaku.restfu.annotation.param.Query; + +@SuppressWarnings("RedundantThrows") +@Params(template = StatusAnnotationTemplate.class) +public interface PrivateTweetResources extends PrivateResources { + + @GET("/statuses/{id}/activity/summary.json") + StatusActivitySummary getStatusActivitySummary(@Path("id") String statusId) throws MicroBlogException; + + @GET("/conversation/show.json") + ResponseList showConversation(@Query("id") String statusId, @Query Paging paging) throws MicroBlogException; + + @GET("/translations/show.json") + TranslationResult showTranslation(@Query("id") String statusId, @Query("dest") String dest) throws MicroBlogException; +} diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ConsumerKeyType.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ConsumerKeyType.java index 549db2714..944d023b0 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ConsumerKeyType.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/ConsumerKeyType.java @@ -24,7 +24,8 @@ import androidx.annotation.NonNull; * Created by mariotaku on 15/4/20. */ public enum ConsumerKeyType { - UNKNOWN; + TWITTER_FOR_ANDROID, TWITTER_FOR_IPHONE, TWITTER_FOR_IPAD, TWITTER_FOR_MAC, + TWITTER_FOR_WINDOWS_PHONE, TWITTER_FOR_GOOGLE_TV, TWEETDECK, UNKNOWN; @NonNull public static ConsumerKeyType parse(String type) { diff --git a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/account/TwitterAccountExtras.java b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/account/TwitterAccountExtras.java index 0e516b757..7eb204877 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/twidere/model/account/TwitterAccountExtras.java +++ b/twidere.component.common/src/main/java/org/mariotaku/twidere/model/account/TwitterAccountExtras.java @@ -21,8 +21,10 @@ package org.mariotaku.twidere.model.account; import android.os.Parcel; import android.os.Parcelable; +import com.bluelinelabs.logansquare.annotation.JsonField; import com.bluelinelabs.logansquare.annotation.JsonObject; import com.hannesdorfmann.parcelableplease.annotation.ParcelablePlease; +import com.hannesdorfmann.parcelableplease.annotation.ParcelableThisPlease; /** * Created by mariotaku on 16/2/26. @@ -45,6 +47,17 @@ public class TwitterAccountExtras implements Parcelable, AccountExtras { } }; + @JsonField(name = "official_credentials") + @ParcelableThisPlease + boolean officialCredentials; + + public boolean isOfficialCredentials() { + return officialCredentials; + } + + public void setIsOfficialCredentials(boolean officialCredentials) { + this.officialCredentials = officialCredentials; + } @Override public int describeContents() { @@ -59,6 +72,7 @@ public class TwitterAccountExtras implements Parcelable, AccountExtras { @Override public String toString() { return "TwitterAccountExtras{" + + "officialCredentials=" + officialCredentials + '}'; } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/model/util/AccountUtils.java b/twidere/src/main/java/org/mariotaku/twidere/model/util/AccountUtils.java index 7a7a01985..65161e0e8 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/model/util/AccountUtils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/model/util/AccountUtils.java @@ -129,6 +129,23 @@ public class AccountUtils { return null; } + public static boolean isOfficial(@NonNull final Context context, @NonNull final UserKey accountKey) { + AccountManager am = AccountManager.get(context); + Account account = AccountUtils.findByAccountKey(am, accountKey); + if (account == null) return false; + return AccountExtensionsKt.isOfficial(account, am, context); + } + + public static boolean hasOfficialKeyAccount(Context context) { + final AccountManager am = AccountManager.get(context); + for (Account account : getAccounts(am)) { + if (AccountExtensionsKt.isOfficial(account, am, context)) { + return true; + } + } + return false; + } + public static int getAccountTypeIcon(@Nullable String accountType) { if (accountType == null) return R.drawable.ic_account_logo_twitter; switch (accountType) { diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/InternalTwitterContentUtils.java b/twidere/src/main/java/org/mariotaku/twidere/util/InternalTwitterContentUtils.java index 8690cdbfd..fdef0f3b5 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/InternalTwitterContentUtils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/InternalTwitterContentUtils.java @@ -9,9 +9,14 @@ import org.mariotaku.microblog.library.twitter.model.DirectMessage; import org.mariotaku.microblog.library.twitter.model.MediaEntity; import org.mariotaku.microblog.library.twitter.model.UrlEntity; import org.mariotaku.microblog.library.twitter.model.User; +import org.mariotaku.twidere.R; import org.mariotaku.twidere.extension.model.api.StatusExtensionsKt; +import org.mariotaku.twidere.model.ConsumerKeyType; import org.mariotaku.twidere.model.SpanItem; +import java.nio.charset.Charset; +import java.util.zip.CRC32; + import kotlin.Pair; /** @@ -96,4 +101,55 @@ public class InternalTwitterContentUtils { } + public static boolean isOfficialKey(final Context context, final String consumerKey, + final String consumerSecret) { + if (context == null || consumerKey == null || consumerSecret == null) return false; + final String[] keySecrets = context.getResources().getStringArray(R.array.values_official_consumer_secret_crc32); + final CRC32 crc32 = new CRC32(); + final byte[] consumerSecretBytes = consumerSecret.getBytes(Charset.forName("UTF-8")); + crc32.update(consumerSecretBytes, 0, consumerSecretBytes.length); + final long value = crc32.getValue(); + crc32.reset(); + for (final String keySecret : keySecrets) { + if (Long.parseLong(keySecret, 16) == value) return true; + } + return false; + } + + public static String getOfficialKeyName(final Context context, final String consumerKey, + final String consumerSecret) { + if (context == null || consumerKey == null || consumerSecret == null) return null; + final String[] keySecrets = context.getResources().getStringArray(R.array.values_official_consumer_secret_crc32); + final String[] keyNames = context.getResources().getStringArray(R.array.names_official_consumer_secret); + final CRC32 crc32 = new CRC32(); + final byte[] consumerSecretBytes = consumerSecret.getBytes(Charset.forName("UTF-8")); + crc32.update(consumerSecretBytes, 0, consumerSecretBytes.length); + final long value = crc32.getValue(); + crc32.reset(); + for (int i = 0, j = keySecrets.length; i < j; i++) { + if (Long.parseLong(keySecrets[i], 16) == value) return keyNames[i]; + } + return null; + } + + @NonNull + public static ConsumerKeyType getOfficialKeyType(final Context context, final String consumerKey, + final String consumerSecret) { + if (context == null || consumerKey == null || consumerSecret == null) { + return ConsumerKeyType.UNKNOWN; + } + final String[] keySecrets = context.getResources().getStringArray(R.array.values_official_consumer_secret_crc32); + final String[] keyNames = context.getResources().getStringArray(R.array.types_official_consumer_secret); + final CRC32 crc32 = new CRC32(); + final byte[] consumerSecretBytes = consumerSecret.getBytes(Charset.forName("UTF-8")); + crc32.update(consumerSecretBytes, 0, consumerSecretBytes.length); + final long value = crc32.getValue(); + crc32.reset(); + for (int i = 0, j = keySecrets.length; i < j; i++) { + if (Long.parseLong(keySecrets[i], 16) == value) { + return ConsumerKeyType.parse(keyNames[i]); + } + } + return ConsumerKeyType.UNKNOWN; + } } diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/MicroBlogAPIFactory.java b/twidere/src/main/java/org/mariotaku/twidere/util/MicroBlogAPIFactory.java index 456a15849..bca1bf941 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/MicroBlogAPIFactory.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/MicroBlogAPIFactory.java @@ -166,6 +166,21 @@ public class MicroBlogAPIFactory implements TwidereConstants { @WorkerThread @Nullable public static ExtraHeaders getExtraHeaders(Context context, ConsumerKeyType type) { + switch (type) { + case TWITTER_FOR_ANDROID: { + return TwitterAndroidExtraHeaders.INSTANCE; + } + case TWITTER_FOR_IPHONE: + case TWITTER_FOR_IPAD: { + return new UserAgentExtraHeaders("Twitter/6.75.2 CFNetwork/811.4.18 Darwin/16.5.0"); + } + case TWITTER_FOR_MAC: { + return TwitterMacExtraHeaders.INSTANCE; + } + case TWEETDECK: { + return new UserAgentExtraHeaders(UserAgentUtils.getDefaultUserAgentStringSafe(context)); + } + } return null; } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/SignInActivity.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/SignInActivity.kt index 32375b2d5..04efc0f8f 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/activity/SignInActivity.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/activity/SignInActivity.kt @@ -70,6 +70,7 @@ import org.mariotaku.microblog.library.mastodon.annotation.AuthScope import org.mariotaku.microblog.library.twitter.TwitterOAuth import org.mariotaku.microblog.library.twitter.auth.BasicAuthorization import org.mariotaku.microblog.library.twitter.auth.EmptyAuthorization +import org.mariotaku.microblog.library.twitter.model.Paging import org.mariotaku.microblog.library.twitter.model.User import org.mariotaku.restfu.http.Endpoint import org.mariotaku.restfu.oauth.OAuthToken @@ -410,7 +411,7 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher, result.addAccount(am, preferences[randomizeAccountNameKey]) Analyzer.log(SignIn(true, accountType = result.type, credentialsType = apiConfig.credentialsType, - officialKey = false)) + officialKey = result.extras?.official == true)) finishSignIn() } } @@ -1220,7 +1221,17 @@ class SignInActivity : BaseActivity(), OnClickListener, TextWatcher, } private fun getTwitterAccountExtras(twitter: MicroBlog): TwitterAccountExtras { - return TwitterAccountExtras() + val extras = TwitterAccountExtras() + try { + // Get Twitter official only resource + val paging = Paging() + paging.count(1) + twitter.getActivitiesAboutMe(paging) + extras.setIsOfficialCredentials(true) + } catch (e: MicroBlogException) { + // Ignore + } + return extras } private fun getMastodonAccountExtras(mastodon: Mastodon): MastodonAccountExtras { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountDetailsExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountDetailsExtensions.kt index 00ef776f2..ed59385da 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountDetailsExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountDetailsExtensions.kt @@ -16,6 +16,27 @@ import org.mariotaku.twidere.util.text.FanfouValidator import org.mariotaku.twidere.util.text.MastodonValidator import org.mariotaku.twidere.util.text.TwitterValidator +fun AccountDetails.isOfficial(context: Context): Boolean { + val extra = this.extras + if (extra is TwitterAccountExtras) { + return extra.isOfficialCredentials + } + val credentials = this.credentials + if (credentials is OAuthCredentials) { + return InternalTwitterContentUtils.isOfficialKey(context, + credentials.consumer_key, credentials.consumer_secret) + } + return false +} + +val AccountExtras.official: Boolean + get() { + if (this is TwitterAccountExtras) { + return isOfficialCredentials + } + return false + } + fun AccountDetails.newMicroBlogInstance(context: Context, cls: Class): T { return credentials.newMicroBlogInstance(context, type, cls) } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountExtensions.kt index c0be5d256..930b3e128 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountExtensions.kt @@ -100,6 +100,19 @@ fun Account.setPosition(am: AccountManager, position: Int) { am.setUserData(this, ACCOUNT_USER_DATA_POSITION, position.toString()) } +fun Account.isOfficial(am: AccountManager, context: Context): Boolean { + val extras = getAccountExtras(am) + if (extras is TwitterAccountExtras) { + return extras.isOfficialCredentials + } + val credentials = getCredentials(am) + if (credentials is OAuthCredentials) { + return InternalTwitterContentUtils.isOfficialKey(context, credentials.consumer_key, + credentials.consumer_secret) + } + return false +} + fun AccountManager.hasInvalidAccount(): Boolean { val accounts = AccountUtils.getAccounts(this) if (accounts.isEmpty()) return false diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/CredentialsExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/CredentialsExtensions.kt index 135c5e442..3a4b864be 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/CredentialsExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/CredentialsExtensions.kt @@ -23,7 +23,6 @@ import org.mariotaku.restfu.oauth.OAuthToken import org.mariotaku.restfu.oauth2.OAuth2Authorization import org.mariotaku.twidere.TwidereConstants.DEFAULT_TWITTER_API_URL_FORMAT import org.mariotaku.twidere.annotation.AccountType -import org.mariotaku.twidere.model.ConsumerKeyType import org.mariotaku.twidere.model.account.cred.* import org.mariotaku.twidere.util.HttpClientFactory import org.mariotaku.twidere.util.InternalTwitterContentUtils @@ -149,7 +148,9 @@ fun newMicroBlogInstance(context: Context, endpoint: Endpoint, auth: Authori val factory = RestAPIFactory() val extraHeaders = run { if (auth !is OAuthAuthorization) return@run null - return@run MicroBlogAPIFactory.getExtraHeaders(context, ConsumerKeyType.UNKNOWN) + val officialKeyType = InternalTwitterContentUtils.getOfficialKeyType(context, + auth.consumerKey, auth.consumerSecret) + return@run MicroBlogAPIFactory.getExtraHeaders(context, officialKeyType) } ?: UserAgentExtraHeaders(MicroBlogAPIFactory.getTwidereUserAgent(context)) val holder = DependencyHolder.get(context) var extraRequestParams: Map? = null diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsActivitiesFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsActivitiesFragment.kt index f90216a0e..ac0ef2730 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsActivitiesFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/AbsActivitiesFragment.kt @@ -306,10 +306,13 @@ abstract class AbsActivitiesFragment protected constructor() : override fun onGapClick(holder: GapViewHolder, position: Int) { val activity = adapter.getActivity(position) DebugLog.v(msg = "Load activity gap $activity") - if (activity.action !in Activity.Action.MENTION_ACTIONS) { - adapter.removeGapLoadingId(ObjectId(activity.account_key, activity.id)) - adapter.notifyItemChanged(position) - return + if (!AccountUtils.isOfficial(context, activity.account_key)) { + // Skip if item is not a status + if (activity.action !in Activity.Action.MENTION_ACTIONS) { + adapter.removeGapLoadingId(ObjectId(activity.account_key, activity.id)) + adapter.notifyItemChanged(position) + return + } } val accountKeys = arrayOf(activity.account_key) val pagination = arrayOf(SinceMaxPagination.maxId(activity.min_position, diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/CustomTabsFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/CustomTabsFragment.kt index 1631299c9..c42d028db 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/CustomTabsFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/CustomTabsFragment.kt @@ -60,6 +60,7 @@ import org.mariotaku.twidere.adapter.ArrayAdapter import org.mariotaku.twidere.annotation.CustomTabType import org.mariotaku.twidere.annotation.TabAccountFlags import org.mariotaku.twidere.extension.applyTheme +import org.mariotaku.twidere.extension.model.isOfficial import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.Tab import org.mariotaku.twidere.model.tab.DrawableHolder @@ -289,9 +290,9 @@ class CustomTabsFragment : BaseFragment(), LoaderCallbacks, MultiChoice if (!accountRequired) { accountsAdapter.add(AccountDetails.dummy()) } - val officialKeyOnly = currentArguments.getBoolean(EXTRA_OFFICIAL_KEY_ONLY, false) - accountsAdapter.addAll(AccountUtils.getAllAccountDetails(AccountManager.get(currentContext), true).filter { - if (officialKeyOnly) { + val officialKeyOnly = arguments.getBoolean(EXTRA_OFFICIAL_KEY_ONLY, false) + accountsAdapter.addAll(AccountUtils.getAllAccountDetails(AccountManager.get(context), true).filter { + if (officialKeyOnly && !it.isOfficial(context)) { return@filter false } return@filter conf.checkAccountAvailability(it) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessageConversationInfoFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessageConversationInfoFragment.kt index 6086b446e..6af87206c 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessageConversationInfoFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessageConversationInfoFragment.kt @@ -61,6 +61,7 @@ import org.mariotaku.ktextension.spannable import org.mariotaku.library.objectcursor.ObjectCursor import org.mariotaku.microblog.library.MicroBlog import org.mariotaku.microblog.library.MicroBlogException +import org.mariotaku.microblog.library.twitter.TwitterUpload import org.mariotaku.pickncrop.library.MediaPickerActivity import org.mariotaku.sqliteqb.library.Expression import org.mariotaku.twidere.R @@ -90,6 +91,8 @@ import org.mariotaku.twidere.model.ParcelableMessageConversation.ConversationTyp import org.mariotaku.twidere.model.ParcelableMessageConversation.ExtrasType import org.mariotaku.twidere.model.util.AccountUtils import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations +import org.mariotaku.twidere.task.twitter.UpdateStatusTask +import org.mariotaku.twidere.task.twitter.message.AddParticipantsTask import org.mariotaku.twidere.task.twitter.message.ClearMessagesTask import org.mariotaku.twidere.task.twitter.message.DestroyConversationTask import org.mariotaku.twidere.task.twitter.message.SetConversationNotificationDisabledTask @@ -184,6 +187,12 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment, override fun onActivityResult(requestCode: Int, resultCode: Int, data: Intent?) { when (requestCode) { + REQUEST_CONVERSATION_ADD_USER -> { + if (resultCode == Activity.RESULT_OK && data != null) { + val user = data.getParcelableExtra(EXTRA_USER) + performAddParticipant(user) + } + } REQUEST_PICK_MEDIA -> { when (resultCode) { Activity.RESULT_OK -> { @@ -318,6 +327,19 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment, TaskStarter.execute(task) } + private fun performAddParticipant(user: ParcelableUser) { + ProgressDialogFragment.show(childFragmentManager, "add_participant_progress") + val weakThis = WeakReference(this) + val task = AddParticipantsTask(context, accountKey, conversationId, listOf(user)) + task.callback = callback@ { succeed -> + val f = weakThis.get() ?: return@callback + f.dismissDialogThen("add_participant_progress") { + loaderManager.restartLoader(0, null, this) + } + } + TaskStarter.execute(task) + } + private fun performSetNotificationDisabled(disabled: Boolean) { ProgressDialogFragment.show(childFragmentManager, "set_notifications_disabled_progress") val weakThis = WeakReference(this) @@ -361,6 +383,9 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment, val context = fragment.context when (account.type) { AccountType.TWITTER -> { + if (account.isOfficial(context)) { + return@updateAction microBlog.updateDmConversationName(conversationId, name).isSuccessful + } } } throw UnsupportedOperationException() @@ -370,6 +395,53 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment, } private fun performSetConversationAvatar(uri: Uri?) { + val conversationId = this.conversationId + performUpdateInfo("set_avatar_progress", updateAction = updateAction@ { fragment, account, microBlog -> + val context = fragment.context + when (account.type) { + AccountType.TWITTER -> { + if (account.isOfficial(context)) { + val upload = account.newMicroBlogInstance(context, cls = TwitterUpload::class.java) + if (uri == null) { + val result = microBlog.updateDmConversationAvatar(conversationId, null) + if (result.isSuccessful) { + val dmResponse = microBlog.getDmConversation(conversationId, null).conversationTimeline + return@updateAction dmResponse.conversations[conversationId]?.avatarImageHttps + } + throw MicroBlogException("Error ${result.responseCode}") + } + var deleteAlways: List? = null + try { + val media = arrayOf(ParcelableMediaUpdate().apply { + this.uri = uri.toString() + this.delete_always = true + }) + val uploadResult = UpdateStatusTask.uploadMicroBlogMediaShared(context, + upload, account, media, null, null, true, null) + deleteAlways = uploadResult.deleteAlways + val avatarId = uploadResult.ids.first() + val result = microBlog.updateDmConversationAvatar(conversationId, avatarId) + if (result.isSuccessful) { + uploadResult.deleteOnSuccess.forEach { it.delete(context) } + val dmResponse = microBlog.getDmConversation(conversationId, null).conversationTimeline + return@updateAction dmResponse.conversations[conversationId]?.avatarImageHttps + } + throw MicroBlogException("Error ${result.responseCode}") + } catch (e: UpdateStatusTask.UploadException) { + e.deleteAlways?.forEach { + it.delete(context) + } + throw e + } finally { + deleteAlways?.forEach { it.delete(context) } + } + } + } + } + throw UnsupportedOperationException() + }, successAction = { uri -> + put(Conversations.CONVERSATION_AVATAR, uri) + }) } private inline fun performUpdateInfo( diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessageNewConversationFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessageNewConversationFragment.kt index 63d320280..25480b318 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessageNewConversationFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessageNewConversationFragment.kt @@ -44,6 +44,7 @@ import org.mariotaku.twidere.R import org.mariotaku.twidere.adapter.SelectableUsersAdapter import org.mariotaku.twidere.constant.IntentConstants.* import org.mariotaku.twidere.constant.nameFirstKey +import org.mariotaku.twidere.extension.model.isOfficial import org.mariotaku.twidere.extension.queryOne import org.mariotaku.twidere.extension.text.appendCompat import org.mariotaku.twidere.fragment.BaseFragment @@ -242,7 +243,11 @@ class MessageNewConversationFragment : BaseFragment(), LoaderCallbacks maxParticipants) { editParticipants.error = getString(R.string.error_message_message_too_many_participants) return diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/PinStatusDialogFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/PinStatusDialogFragment.kt new file mode 100644 index 000000000..0657636ba --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/PinStatusDialogFragment.kt @@ -0,0 +1,55 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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. + * + * This program 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 this program. If not, see . + */ + +package org.mariotaku.twidere.fragment.status + +import android.os.Bundle +import android.support.v4.app.FragmentManager +import org.mariotaku.abstask.library.TaskStarter +import org.mariotaku.twidere.R +import org.mariotaku.twidere.constant.IntentConstants.EXTRA_STATUS +import org.mariotaku.twidere.model.ParcelableStatus +import org.mariotaku.twidere.task.status.PinStatusTask + +class PinStatusDialogFragment : AbsSimpleStatusOperationDialogFragment() { + + override val title: String? + get() = getString(R.string.title_pin_status_confirm) + override val message: String + get() = getString(R.string.message_pin_status_confirm) + + override fun onPerformAction(status: ParcelableStatus) { + val task = PinStatusTask(context, status.account_key, status.id) + TaskStarter.execute(task) + } + + companion object { + + val FRAGMENT_TAG = "pin_status" + + fun show(fm: FragmentManager, status: ParcelableStatus): PinStatusDialogFragment { + val args = Bundle() + args.putParcelable(EXTRA_STATUS, status) + val f = PinStatusDialogFragment() + f.arguments = args + f.show(fm, FRAGMENT_TAG) + return f + } + } +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/StatusFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/StatusFragment.kt index 058a157e2..4461bb170 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/StatusFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/StatusFragment.kt @@ -123,6 +123,7 @@ class StatusFragment : BaseFragment(), LoaderCallbacks + private var loadTranslationTask: LoadTranslationTask? = null // Data fields private var conversationLoaderInitialized: Boolean = false @@ -505,9 +506,18 @@ class StatusFragment : BaseFragment(), LoaderCallbacks?) { @@ -673,6 +683,37 @@ class StatusFragment : BaseFragment(), LoaderCallbacks(fragment.context, status.account_key) { + + private val weakFragment = WeakReference(fragment) + + override fun onExecute(account: AccountDetails, params: Any?): TranslationResult { + val twitter = account.newMicroBlogInstance(context, MicroBlog::class.java) + val prefDest = preferences.getString(KEY_TRANSLATION_DESTINATION, null).orEmpty() + val dest: String + if (TextUtils.isEmpty(prefDest)) { + dest = twitter.accountSettings.language + val editor = preferences.edit() + editor.putString(KEY_TRANSLATION_DESTINATION, dest) + editor.apply() + } else { + dest = prefDest + } + return twitter.showTranslation(status.originalId, dest) + } + + override fun onSucceed(callback: Any?, result: TranslationResult) { + val fragment = weakFragment.get() ?: return + fragment.displayTranslation(result) + } + + override fun onException(callback: Any?, exception: MicroBlogException) { + Toast.makeText(context, exception.getErrorMessage(context), Toast.LENGTH_SHORT).show() + } + } + + class StatusActivitySummaryLoader( context: Context, private val accountKey: UserKey, diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/UnpinStatusDialogFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/UnpinStatusDialogFragment.kt new file mode 100644 index 000000000..1d1190140 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/UnpinStatusDialogFragment.kt @@ -0,0 +1,55 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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. + * + * This program 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 this program. If not, see . + */ + +package org.mariotaku.twidere.fragment.status + +import android.os.Bundle +import android.support.v4.app.FragmentManager +import org.mariotaku.abstask.library.TaskStarter +import org.mariotaku.twidere.R +import org.mariotaku.twidere.constant.IntentConstants.EXTRA_STATUS +import org.mariotaku.twidere.model.ParcelableStatus +import org.mariotaku.twidere.task.status.UnpinStatusTask + +class UnpinStatusDialogFragment : AbsSimpleStatusOperationDialogFragment() { + + override val title: String? + get() = getString(R.string.title_unpin_status_confirm) + override val message: String + get() = getString(R.string.message_unpin_status_confirm) + + override fun onPerformAction(status: ParcelableStatus) { + val task = UnpinStatusTask(context, status.account_key, status.id) + TaskStarter.execute(task) + } + + companion object { + + val FRAGMENT_TAG = "unpin_status" + + fun show(fm: FragmentManager, status: ParcelableStatus): UnpinStatusDialogFragment { + val args = Bundle() + args.putParcelable(EXTRA_STATUS, status) + val f = UnpinStatusDialogFragment() + f.arguments = args + f.show(fm, FRAGMENT_TAG) + return f + } + } +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/users/IncomingFriendshipsFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/users/IncomingFriendshipsFragment.kt index c28fa8977..50183a989 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/users/IncomingFriendshipsFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/users/IncomingFriendshipsFragment.kt @@ -32,6 +32,7 @@ import org.mariotaku.twidere.loader.users.AbsRequestUsersLoader import org.mariotaku.twidere.loader.users.IncomingFriendshipsLoader import org.mariotaku.twidere.model.UserKey import org.mariotaku.twidere.model.event.FriendshipTaskEvent +import org.mariotaku.twidere.model.util.AccountUtils import org.mariotaku.twidere.view.holder.UserViewHolder class IncomingFriendshipsFragment : ParcelableUsersFragment(), IUsersAdapter.RequestClickListener { @@ -48,6 +49,8 @@ class IncomingFriendshipsFragment : ParcelableUsersFragment(), IUsersAdapter.Req val accountKey = arguments?.getParcelable(EXTRA_ACCOUNT_KEY) ?: return adapter if (USER_TYPE_FANFOU_COM == accountKey.host) { adapter.requestClickListener = this + } else if (AccountUtils.isOfficial(context, accountKey)) { + adapter.requestClickListener = this } return adapter } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/ConversationLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/ConversationLoader.kt index 975b74ccd..4a5d87030 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/ConversationLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/ConversationLoader.kt @@ -38,6 +38,7 @@ import org.mariotaku.twidere.extension.atto.filter import org.mariotaku.twidere.extension.atto.firstElementOrNull import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable import org.mariotaku.twidere.extension.model.api.toParcelable +import org.mariotaku.twidere.extension.model.isOfficial import org.mariotaku.twidere.extension.model.makeOriginal import org.mariotaku.twidere.extension.model.newMicroBlogInstance import org.mariotaku.twidere.model.AccountDetails @@ -92,6 +93,11 @@ class ConversationLoader( // val isOfficial = account.isOfficial(context) val isOfficial = false canLoadAllReplies = isOfficial + if (isOfficial) { + return microBlog.showConversation(status.id, paging).mapMicroBlogToPaginated { + it.toParcelable(account, profileImageSize) + } + } return showConversationCompat(microBlog, account, status, true) } AccountType.STATUSNET -> { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/MediaStatusesSearchLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/MediaStatusesSearchLoader.kt index 26077f5fc..7e0ef7027 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/MediaStatusesSearchLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/MediaStatusesSearchLoader.kt @@ -27,10 +27,12 @@ import org.mariotaku.microblog.library.MicroBlogException import org.mariotaku.microblog.library.twitter.model.Paging import org.mariotaku.microblog.library.twitter.model.SearchQuery import org.mariotaku.microblog.library.twitter.model.Status +import org.mariotaku.microblog.library.twitter.model.UniversalSearchQuery import org.mariotaku.twidere.annotation.AccountType import org.mariotaku.twidere.annotation.FilterScope import org.mariotaku.twidere.extension.model.api.toParcelable import org.mariotaku.twidere.extension.model.newMicroBlogInstance +import org.mariotaku.twidere.extension.model.official import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.UserKey @@ -76,6 +78,9 @@ open class MediaStatusesSearchLoader( protected open fun processQuery(details: AccountDetails, query: String): String { if (details.type == AccountType.TWITTER) { + if (details.extras?.official == true) { + return TweetSearchLoader.smQuery("$query filter:media", pagination) + } return "$query filter:media exclude:retweets" } return query @@ -87,6 +92,15 @@ open class MediaStatusesSearchLoader( val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java) when (account.type) { AccountType.TWITTER -> { + if (account.extras?.official == true) { + val universalQuery = UniversalSearchQuery(queryText) + universalQuery.setModules(UniversalSearchQuery.Module.TWEET) + universalQuery.setResultType(UniversalSearchQuery.ResultType.RECENT) + universalQuery.setPaging(paging) + val searchResult = microBlog.universalSearch(universalQuery) + return searchResult.modules.mapNotNull { it.status?.data } + } + val searchQuery = SearchQuery(queryText) searchQuery.paging(paging) return microBlog.search(searchQuery) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/MediaTimelineLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/MediaTimelineLoader.kt index 359c644d1..f432a39b4 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/MediaTimelineLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/MediaTimelineLoader.kt @@ -34,6 +34,7 @@ import org.mariotaku.twidere.extension.model.api.mastodon.mapToPaginated import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable import org.mariotaku.twidere.extension.model.api.toParcelable import org.mariotaku.twidere.extension.model.api.updateFilterInfoForUserTimeline +import org.mariotaku.twidere.extension.model.isOfficial import org.mariotaku.twidere.extension.model.newMicroBlogInstance import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.ParcelableStatus @@ -89,23 +90,32 @@ class MediaTimelineLoader( val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java) when (account.type) { AccountType.TWITTER -> { - val screenName = this.screenName ?: run { - return@run this.user ?: run fetchUser@ { - if (userKey == null) throw MicroBlogException("Invalid parameters") - val user = microBlog.tryShowUser(userKey.id, null, account.type) - this.user = user - return@fetchUser user - }.screenName + if (account.isOfficial(context)) { + if (userKey != null) { + return microBlog.getMediaTimeline(userKey.id, paging) + } + if (screenName != null) { + return microBlog.getMediaTimelineByScreenName(screenName, paging) + } + } else { + val screenName = this.screenName ?: run { + return@run this.user ?: run fetchUser@ { + if (userKey == null) throw MicroBlogException("Invalid parameters") + val user = microBlog.tryShowUser(userKey.id, null, account.type) + this.user = user + return@fetchUser user + }.screenName + } + val query = SearchQuery("from:$screenName filter:media exclude:retweets") + query.paging(paging) + val result = ResponseList() + microBlog.search(query).filterTo(result) { status -> + val user = status.user + return@filterTo user.id == userKey?.id + || user.screenName.equals(this.screenName, ignoreCase = true) + } + return result } - val query = SearchQuery("from:$screenName filter:media exclude:retweets") - query.paging(paging) - val result = ResponseList() - microBlog.search(query).filterTo(result) { status -> - val user = status.user - return@filterTo user.id == userKey?.id - || user.screenName.equals(this.screenName, ignoreCase = true) - } - return result throw MicroBlogException("Wrong user") } AccountType.FANFOU -> { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/TweetSearchLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/TweetSearchLoader.kt index bafec138d..f91b735f9 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/TweetSearchLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/TweetSearchLoader.kt @@ -34,6 +34,7 @@ import org.mariotaku.twidere.extension.model.api.mastodon.mapToPaginated import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable import org.mariotaku.twidere.extension.model.api.toParcelable import org.mariotaku.twidere.extension.model.newMicroBlogInstance +import org.mariotaku.twidere.extension.model.official import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.UserKey @@ -75,6 +76,9 @@ open class TweetSearchLoader( protected open fun processQuery(details: AccountDetails, query: String): String { if (details.type == AccountType.TWITTER) { + if (details.extras?.official == true) { + return smQuery(query, pagination) + } return "$query exclude:retweets" } return query @@ -104,6 +108,15 @@ open class TweetSearchLoader( val queryText = processQuery(account, query) when (account.type) { AccountType.TWITTER -> { + if (account.extras?.official == true) { + val universalQuery = UniversalSearchQuery(queryText) + universalQuery.setModules(UniversalSearchQuery.Module.TWEET) + universalQuery.setResultType(UniversalSearchQuery.ResultType.RECENT) + universalQuery.setPaging(paging) + val searchResult = microBlog.universalSearch(universalQuery) + return searchResult.modules.mapNotNull { it.status?.data } + } + val searchQuery = SearchQuery(queryText) searchQuery.paging(paging) return microBlog.search(searchQuery) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserMentionsLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserMentionsLoader.kt index e716e559d..e8075a7f0 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserMentionsLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/statuses/UserMentionsLoader.kt @@ -21,6 +21,7 @@ package org.mariotaku.twidere.loader.statuses import android.content.Context import org.mariotaku.twidere.annotation.AccountType +import org.mariotaku.twidere.extension.model.official import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.ParcelableStatus import org.mariotaku.twidere.model.UserKey @@ -42,6 +43,9 @@ class UserMentionsLoader( override fun processQuery(details: AccountDetails, query: String): String { val screenName = query.substringAfter("@") if (details.type == AccountType.TWITTER) { + if (details.extras?.official == true) { + return smQuery("to:$screenName", pagination) + } return "to:$screenName exclude:retweets" } return "@$screenName -RT" diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/users/StatusFavoritersLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/users/StatusFavoritersLoader.kt index 7e99ae2be..5054298b4 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/users/StatusFavoritersLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/users/StatusFavoritersLoader.kt @@ -36,6 +36,7 @@ import org.mariotaku.twidere.extension.api.lookupUsersMapPaginated import org.mariotaku.twidere.extension.model.api.mastodon.mapToPaginated import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable import org.mariotaku.twidere.extension.model.api.toParcelable +import org.mariotaku.twidere.extension.model.isOfficial import org.mariotaku.twidere.extension.model.newMicroBlogInstance import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.ParcelableUser @@ -62,7 +63,9 @@ class StatusFavoritersLoader( } AccountType.TWITTER -> { val microBlog = details.newMicroBlogInstance(context, MicroBlog::class.java) - val ids = run { + val ids = if (details.isOfficial(context)) { + microBlog.getStatusActivitySummary(statusId).favoriters + } else { val web = details.newMicroBlogInstance(context, TwitterWeb::class.java) val htmlUsers = web.getFavoritedPopup(statusId).htmlUsers IDsAccessor.setIds(IDs(), parseUserIds(htmlUsers)) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/model/tab/impl/InteractionsTabConfiguration.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/model/tab/impl/InteractionsTabConfiguration.kt index 8c6890acd..050e5158c 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/model/tab/impl/InteractionsTabConfiguration.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/model/tab/impl/InteractionsTabConfiguration.kt @@ -29,6 +29,7 @@ import org.mariotaku.twidere.annotation.AccountType import org.mariotaku.twidere.annotation.TabAccountFlags import org.mariotaku.twidere.constant.IntentConstants.EXTRA_MENTIONS_ONLY import org.mariotaku.twidere.constant.IntentConstants.EXTRA_MY_FOLLOWING_ONLY +import org.mariotaku.twidere.extension.model.isOfficial import org.mariotaku.twidere.fragment.InteractionsTimelineFragment import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.Tab @@ -104,11 +105,11 @@ class InteractionsTabConfiguration : TabConfiguration() { val am = AccountManager.get(context) val accounts = AccountUtils.getAllAccountDetails(am, false) interactionsAvailable = accounts.any { it.supportsInteractions } - requiresStreaming = accounts.all { true } + requiresStreaming = accounts.all { it.requiresStreaming } } else when (account.type) { AccountType.TWITTER -> { interactionsAvailable = true - requiresStreaming = true + requiresStreaming = !account.isOfficial(context) } AccountType.MASTODON -> { interactionsAvailable = true @@ -160,7 +161,7 @@ class InteractionsTabConfiguration : TabConfiguration() { get() = type == AccountType.TWITTER || type == AccountType.MASTODON private val AccountDetails.requiresStreaming: Boolean - get() = true + get() = !isOfficial(context) } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/service/StreamingService.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/service/StreamingService.kt index 885631828..8c1a7716d 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/service/StreamingService.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/service/StreamingService.kt @@ -194,6 +194,11 @@ class StreamingService : BaseService() { } private fun newStreamingRunnable(account: AccountDetails, preferences: AccountPreferences): StreamingRunnable<*>? { + when (account.type) { + AccountType.TWITTER -> { + return TwitterStreamingRunnable(this, account, preferences) + } + } return null } @@ -232,6 +237,236 @@ class StreamingService : BaseService() { abstract fun onCancelled() } + internal inner class TwitterStreamingRunnable( + context: Context, + account: AccountDetails, + accountPreferences: AccountPreferences + ) : StreamingRunnable(context, account, accountPreferences) { + + private val profileImageSize = context.getString(R.string.profile_image_size) + private val isOfficial = account.isOfficial(context) + + private var canGetInteractions: Boolean = true + private var canGetMessages: Boolean = true + + private val interactionsTimeoutRunnable = Runnable { + canGetInteractions = true + } + + private val messagesTimeoutRunnable = Runnable { + canGetMessages = true + } + + val callback = object : TwitterTimelineStreamCallback(account.key.id) { + + private var lastStatusTimestamps = LongArray(2) + + private var homeInsertGap = false + private var interactionsInsertGap = false + + private var lastActivityAboutMe: ParcelableActivity? = null + + override fun onConnected(): Boolean { + homeInsertGap = true + interactionsInsertGap = true + return true + } + + override fun onHomeTimeline(status: Status): Boolean { + if (!accountPreferences.isStreamHomeTimelineEnabled) { + homeInsertGap = true + return false + } + val parcelableStatus = status.toParcelable(account, profileImageSize = profileImageSize) + parcelableStatus.is_gap = homeInsertGap + + val currentTimeMillis = System.currentTimeMillis() + if (lastStatusTimestamps[0] >= parcelableStatus.timestamp) { + val extraValue = (currentTimeMillis - lastStatusTimestamps[1]).coerceAtMost(499) + parcelableStatus.position_key = parcelableStatus.timestamp + extraValue + } else { + parcelableStatus.position_key = parcelableStatus.timestamp + } + parcelableStatus.inserted_date = currentTimeMillis + + lastStatusTimestamps[0] = parcelableStatus.position_key + lastStatusTimestamps[1] = parcelableStatus.inserted_date + + val values = ObjectCursor.valuesCreatorFrom(ParcelableStatus::class.java) + .create(parcelableStatus) + context.contentResolver.insert(Statuses.CONTENT_URI, values) + homeInsertGap = false + return true + } + + override fun onActivityAboutMe(activity: Activity): Boolean { + if (!accountPreferences.isStreamInteractionsEnabled) { + interactionsInsertGap = true + return false + } + if (isOfficial) { + // Wait for 30 seconds to avoid rate limit + if (canGetInteractions) { + handler.post { getInteractions() } + canGetInteractions = false + handler.postDelayed(interactionsTimeoutRunnable, TimeUnit.SECONDS.toMillis(30)) + } + } else { + val insertGap: Boolean + if (activity.action in Activity.Action.MENTION_ACTIONS) { + insertGap = interactionsInsertGap + interactionsInsertGap = false + } else { + insertGap = false + } + val curActivity = activity.toParcelable(account, insertGap, profileImageSize) + curActivity.account_color = account.color + curActivity.position_key = curActivity.timestamp + var updateId = -1L + if (curActivity.action !in Activity.Action.MENTION_ACTIONS) { + /* Merge two activities if: + * * Not mention/reply/quote + * * Same action + * * Same source or target or target object + */ + val lastActivity = this.lastActivityAboutMe + if (lastActivity != null && curActivity.action == lastActivity.action) { + if (curActivity.reachedCountLimit) { + // Skip if more than 10 sources/targets/target_objects + } else if (curActivity.isSameSources(lastActivity)) { + curActivity.prependTargets(lastActivity) + curActivity.prependTargetObjects(lastActivity) + updateId = lastActivity._id + } else if (curActivity.isSameTarget(lastActivity)) { + curActivity.prependSources(lastActivity) + curActivity.prependTargets(lastActivity) + updateId = lastActivity._id + } else if (curActivity.isSameTargetObject(lastActivity)) { + curActivity.prependSources(lastActivity) + curActivity.prependTargets(lastActivity) + updateId = lastActivity._id + } + if (updateId > 0) { + curActivity.min_position = lastActivity.min_position + curActivity.min_sort_position = lastActivity.min_sort_position + } + } + } + val values = ObjectCursor.valuesCreatorFrom(ParcelableActivity::class.java) + .create(curActivity) + val resolver = context.contentResolver + if (updateId > 0) { + val where = Expression.equals(Activities._ID, updateId).sql + resolver.update(Activities.AboutMe.CONTENT_URI, values, where, null) + curActivity._id = updateId + } else { + val uri = resolver.insert(Activities.AboutMe.CONTENT_URI, values) + if (uri != null) { + curActivity._id = uri.lastPathSegment.toLongOr(-1L) + } + } + lastActivityAboutMe = curActivity + } + return true + } + + @WorkerThread + override fun onDirectMessage(directMessage: DirectMessage): Boolean { + if (!accountPreferences.isStreamDirectMessagesEnabled) { + return false + } + if (canGetMessages) { + handler.post { getMessages() } + canGetMessages = false + val timeout = TimeUnit.SECONDS.toMillis(if (isOfficial) 30 else 90) + handler.postDelayed(messagesTimeoutRunnable, timeout) + } + return true + } + + override fun onAllStatus(status: Status) { + if (!accountPreferences.isStreamNotificationUsersEnabled) { + return + } + val user = status.user ?: return + val userKey = user.key + val where = Expression.and(Expression.equalsArgs(CachedRelationships.ACCOUNT_KEY), + Expression.equalsArgs(CachedRelationships.USER_KEY), + Expression.equals(CachedRelationships.NOTIFICATIONS_ENABLED, 1)).sql + val whereArgs = arrayOf(account.key.toString(), userKey.toString()) + if (context.contentResolver.queryCount(CachedRelationships.CONTENT_URI, + where, whereArgs) <= 0) return + + contentNotificationManager.showUserNotification(account.key, status, userKey) + } + + override fun onStatusDeleted(event: DeletionEvent): Boolean { + val deleteWhere = Expression.and(Expression.likeRaw(Columns.Column(Statuses.ACCOUNT_KEY), "'%@'||?"), + Expression.equalsArgs(Columns.Column(Statuses.ID))).sql + val deleteWhereArgs = arrayOf(account.key.host, event.id) + context.contentResolver.delete(Statuses.CONTENT_URI, deleteWhere, deleteWhereArgs) + return true + } + + override fun onDisconnectNotice(code: Int, reason: String?): Boolean { + disconnect() + return true + } + + override fun onException(ex: Throwable): Boolean { + DebugLog.w(LOGTAG, msg = "Exception for ${account.key}", tr = ex) + return true + } + + override fun onUnhandledEvent(obj: TwitterStreamObject, json: String) { + DebugLog.d(LOGTAG, msg = "Unhandled event ${obj.determine()} for ${account.key}: $json") + } + + @UiThread + private fun getInteractions() { + val task = GetActivitiesAboutMeTask(context) + task.params = object : RefreshTaskParam { + override val accountKeys: Array = arrayOf(account.key) + + override val pagination by lazy { + val keys = accountKeys.toNulls() + val sinceIds = DataStoreUtils.getRefreshNewestActivityMaxPositions(context, + Activities.AboutMe.CONTENT_URI, keys) + val sinceSortIds = DataStoreUtils.getRefreshNewestActivityMaxSortPositions(context, + Activities.AboutMe.CONTENT_URI, keys) + return@lazy Array(keys.size) { idx -> + SinceMaxPagination.sinceId(sinceIds[idx], sinceSortIds[idx]) + } + } + + } + TaskStarter.execute(task) + } + + @UiThread + private fun getMessages() { + val task = GetMessagesTask(context) + task.params = object : GetMessagesTask.RefreshMessagesTaskParam(context) { + override val accountKeys: Array = arrayOf(account.key) + } + TaskStarter.execute(task) + } + } + + override fun createStreamingInstance(): TwitterUserStream { + return account.newMicroBlogInstance(context, cls = TwitterUserStream::class.java) + } + + override fun TwitterUserStream.beginStreaming() { + getUserStream(StreamWith.USER, callback) + } + + override fun onCancelled() { + callback.disconnect() + } + + } + companion object { private val NOTIFICATION_SERVICE_STARTED = 1 diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/AcceptFriendshipTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/AcceptFriendshipTask.kt index 40580b5f7..6202d9394 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/AcceptFriendshipTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/AcceptFriendshipTask.kt @@ -15,7 +15,6 @@ import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.ParcelableUser import org.mariotaku.twidere.model.event.FriendshipTaskEvent import org.mariotaku.twidere.util.Utils -import java.lang.UnsupportedOperationException /** * Created by mariotaku on 16/3/11. @@ -36,7 +35,9 @@ class AcceptFriendshipTask(context: Context) : AbsFriendshipOperationTask(contex return mastodon.getAccount(args.userKey.id).toParcelable(details) } else -> { - throw UnsupportedOperationException() + val twitter = details.newMicroBlogInstance(context, MicroBlog::class.java) + return twitter.acceptFriendship(args.userKey.id).toParcelable(details, + profileImageSize = profileImageSize) } } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/DenyFriendshipTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/DenyFriendshipTask.kt index 81e7492f4..b7b8f2e2b 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/DenyFriendshipTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/DenyFriendshipTask.kt @@ -15,7 +15,6 @@ import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.ParcelableUser import org.mariotaku.twidere.model.event.FriendshipTaskEvent import org.mariotaku.twidere.util.Utils -import java.lang.UnsupportedOperationException /** * Created by mariotaku on 16/3/11. @@ -36,7 +35,9 @@ class DenyFriendshipTask(context: Context) : AbsFriendshipOperationTask(context, return mastodon.getAccount(args.userKey.id).toParcelable(details) } else -> { - throw UnsupportedOperationException() + val twitter = details.newMicroBlogInstance(context, MicroBlog::class.java) + return twitter.denyFriendship(args.userKey.id).toParcelable(details, + profileImageSize = profileImageSize) } } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/status/PinStatusTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/status/PinStatusTask.kt new file mode 100644 index 000000000..49e1b2dcf --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/status/PinStatusTask.kt @@ -0,0 +1,52 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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. + * + * This program 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 this program. If not, see . + */ + +package org.mariotaku.twidere.task.status + +import android.content.Context +import android.widget.Toast +import org.mariotaku.microblog.library.MicroBlog +import org.mariotaku.microblog.library.twitter.model.PinTweetResult +import org.mariotaku.twidere.R +import org.mariotaku.twidere.extension.model.newMicroBlogInstance +import org.mariotaku.twidere.model.AccountDetails +import org.mariotaku.twidere.model.UserKey +import org.mariotaku.twidere.model.event.StatusPinEvent +import org.mariotaku.twidere.task.AbsAccountRequestTask + +/** + * Created by mariotaku on 2017/4/28. + */ + +class PinStatusTask(context: Context, accountKey: UserKey, val id: String) : AbsAccountRequestTask(context, accountKey) { + + override fun onExecute(account: AccountDetails, params: Any?): PinTweetResult { + val twitter = account.newMicroBlogInstance(context, MicroBlog::class.java) + return twitter.pinTweet(id) + } + + override fun onSucceed(callback: Any?, result: PinTweetResult) { + super.onSucceed(callback, result) + Toast.makeText(context, R.string.message_toast_status_pinned, Toast.LENGTH_SHORT).show() + if (accountKey != null) { + bus.post(StatusPinEvent(accountKey, true)) + } + } +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/status/UnpinStatusTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/status/UnpinStatusTask.kt new file mode 100644 index 000000000..0bcc94aa7 --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/status/UnpinStatusTask.kt @@ -0,0 +1,52 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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. + * + * This program 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 this program. If not, see . + */ + +package org.mariotaku.twidere.task.status + +import android.content.Context +import android.widget.Toast +import org.mariotaku.microblog.library.MicroBlog +import org.mariotaku.microblog.library.twitter.model.PinTweetResult +import org.mariotaku.twidere.R +import org.mariotaku.twidere.extension.model.newMicroBlogInstance +import org.mariotaku.twidere.model.AccountDetails +import org.mariotaku.twidere.model.UserKey +import org.mariotaku.twidere.model.event.StatusPinEvent +import org.mariotaku.twidere.task.AbsAccountRequestTask + +/** + * Created by mariotaku on 2017/4/28. + */ + +class UnpinStatusTask(context: Context, accountKey: UserKey, val id: String) : AbsAccountRequestTask(context, accountKey) { + + override fun onExecute(account: AccountDetails, params: Any?): PinTweetResult { + val twitter = account.newMicroBlogInstance(context, MicroBlog::class.java) + return twitter.unpinTweet(id) + } + + override fun onSucceed(callback: Any?, result: PinTweetResult) { + super.onSucceed(callback, result) + Toast.makeText(context, R.string.message_toast_status_unpinned, Toast.LENGTH_SHORT).show() + if (accountKey != null) { + bus.post(StatusPinEvent(accountKey, false)) + } + } +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesAboutMeTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesAboutMeTask.kt index eead73e86..41901ef6a 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesAboutMeTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetActivitiesAboutMeTask.kt @@ -36,6 +36,7 @@ import org.mariotaku.twidere.extension.api.batchGetRelationships import org.mariotaku.twidere.extension.model.api.mastodon.toParcelable import org.mariotaku.twidere.extension.model.api.microblog.toParcelable import org.mariotaku.twidere.extension.model.extractFanfouHashtags +import org.mariotaku.twidere.extension.model.isOfficial import org.mariotaku.twidere.extension.model.newMicroBlogInstance import org.mariotaku.twidere.fragment.InteractionsTimelineFragment import org.mariotaku.twidere.model.AccountDetails @@ -81,6 +82,28 @@ class GetActivitiesAboutMeTask(context: Context) : GetActivitiesTask(context) { notification.status?.tags?.map { it.name }.orEmpty() }) } + AccountType.TWITTER -> { + val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java) + if (account.isOfficial(context)) { + val timeline = microBlog.getActivitiesAboutMe(paging) + val activities = timeline.map { + it.toParcelable(account, profileImageSize = profileImageSize) + } + + return GetTimelineResult(account, activities, activities.flatMap { + it.sources?.toList().orEmpty() + }, timeline.flatMapTo(HashSet()) { activity -> + val mapResult = mutableSetOf() + activity.targetStatuses?.flatMapTo(mapResult) { status -> + status.entities?.hashtags?.map { it.text }.orEmpty() + } + activity.targetObjectStatuses?.flatMapTo(mapResult) { status -> + status.entities?.hashtags?.map { it.text }.orEmpty() + } + return@flatMapTo mapResult + }) + } + } AccountType.FANFOU -> { val microBlog = account.newMicroBlogInstance(context, MicroBlog::class.java) val activities = microBlog.getMentions(paging).map { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetStatusesTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetStatusesTask.kt index cb96ba1a6..458c041c1 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetStatusesTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/GetStatusesTask.kt @@ -244,7 +244,7 @@ abstract class GetStatusesTask( if (result == null) return@forEach val account = result.account val task = CacheTimelineResultTask(context, result, - account.type == AccountType.STATUSNET) + account.type == AccountType.STATUSNET || account.isOfficial(context)) TaskStarter.execute(task) } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/AddParticipantsTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/AddParticipantsTask.kt new file mode 100644 index 000000000..8c8983e7f --- /dev/null +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/AddParticipantsTask.kt @@ -0,0 +1,97 @@ +/* + * Twidere - Twitter client for Android + * + * Copyright (C) 2012-2017 Mariotaku Lee + * + * 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. + * + * This program 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 this program. If not, see . + */ + +package org.mariotaku.twidere.task.twitter.message + +import android.accounts.AccountManager +import android.content.Context +import org.mariotaku.ktextension.mapToArray +import org.mariotaku.microblog.library.MicroBlog +import org.mariotaku.microblog.library.MicroBlogException +import org.mariotaku.twidere.R +import org.mariotaku.twidere.annotation.AccountType +import org.mariotaku.twidere.extension.model.addParticipants +import org.mariotaku.twidere.extension.model.isOfficial +import org.mariotaku.twidere.extension.model.newMicroBlogInstance +import org.mariotaku.twidere.model.AccountDetails +import org.mariotaku.twidere.model.ParcelableMessageConversation +import org.mariotaku.twidere.model.ParcelableUser +import org.mariotaku.twidere.model.UserKey +import org.mariotaku.twidere.model.util.AccountUtils +import org.mariotaku.twidere.task.ExceptionHandlingAbstractTask +import org.mariotaku.twidere.util.DataStoreUtils + +/** + * Created by mariotaku on 2017/2/25. + */ + +class AddParticipantsTask( + context: Context, + val accountKey: UserKey, + val conversationId: String, + val participants: Collection +) : ExceptionHandlingAbstractTask Unit)?>(context) { + + private val profileImageSize: String = context.getString(R.string.profile_image_size) + + override val exceptionClass = MicroBlogException::class.java + + override fun onExecute(params: Unit?): Boolean { + val account = AccountUtils.getAccountDetails(AccountManager.get(context), accountKey, true) ?: + throw MicroBlogException("No account") + val conversation = DataStoreUtils.findMessageConversation(context, accountKey, conversationId) + if (conversation != null && conversation.is_temp) { + val addData = GetMessagesTask.DatabaseUpdateData(listOf(conversation), emptyList()) + conversation.addParticipants(participants) + GetMessagesTask.storeMessages(context, addData, account, showNotification = false) + // Don't finish too fast + Thread.sleep(300L) + return true + } + val microBlog = account.newMicroBlogInstance(context, cls = MicroBlog::class.java) + val addData = requestAddParticipants(microBlog, account, conversation) + GetMessagesTask.storeMessages(context, addData, account, showNotification = false) + return true + } + + override fun afterExecute(callback: ((Boolean) -> Unit)?, result: Boolean?, exception: MicroBlogException?) { + callback?.invoke(result ?: false) + } + + private fun requestAddParticipants(microBlog: MicroBlog, account: AccountDetails, conversation: ParcelableMessageConversation?): + GetMessagesTask.DatabaseUpdateData { + when (account.type) { + AccountType.TWITTER -> { + if (account.isOfficial(context)) { + val ids = participants.mapToArray { it.key.id } + val response = microBlog.addParticipants(conversationId, ids) + if (conversation != null) { + conversation.addParticipants(participants) + return GetMessagesTask.DatabaseUpdateData(listOf(conversation), emptyList()) + } + return GetMessagesTask.createDatabaseUpdateData(context, account, response, + profileImageSize) + } + } + + } + throw MicroBlogException("Adding participants is not supported") + } + +} diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/DestroyConversationTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/DestroyConversationTask.kt index 38975aee1..0dc989fe2 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/DestroyConversationTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/DestroyConversationTask.kt @@ -25,6 +25,7 @@ import org.mariotaku.microblog.library.MicroBlog import org.mariotaku.microblog.library.MicroBlogException import org.mariotaku.sqliteqb.library.Expression import org.mariotaku.twidere.annotation.AccountType +import org.mariotaku.twidere.extension.model.isOfficial import org.mariotaku.twidere.extension.model.newMicroBlogInstance import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.ParcelableMessageConversation @@ -88,6 +89,9 @@ class DestroyConversationTask( private fun requestDestroyConversation(microBlog: MicroBlog, account: AccountDetails): Boolean { when (account.type) { AccountType.TWITTER -> { + if (account.isOfficial(context)) { + return microBlog.deleteDmConversation(conversationId).isSuccessful + } } } return false diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/DestroyMessageTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/DestroyMessageTask.kt index 7c6fbeb4c..dd0779e6a 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/DestroyMessageTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/DestroyMessageTask.kt @@ -25,6 +25,7 @@ import org.mariotaku.microblog.library.MicroBlog import org.mariotaku.microblog.library.MicroBlogException import org.mariotaku.sqliteqb.library.Expression import org.mariotaku.twidere.annotation.AccountType +import org.mariotaku.twidere.extension.model.isOfficial import org.mariotaku.twidere.extension.model.newMicroBlogInstance import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.UserKey @@ -74,6 +75,9 @@ class DestroyMessageTask( account: AccountDetails, messageId: String): Boolean { when (account.type) { AccountType.TWITTER -> { + if (account.isOfficial(context)) { + return microBlog.destroyDm(messageId).isSuccessful + } } } microBlog.destroyDirectMessage(messageId) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/GetMessagesTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/GetMessagesTask.kt index 39fb4d195..4d2b215a7 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/GetMessagesTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/GetMessagesTask.kt @@ -99,11 +99,27 @@ class GetMessagesTask( // Use fanfou DM api, disabled since it's conversation api is not suitable for paging // return getFanfouMessages(microBlog, details, param, index) } + AccountType.TWITTER -> { + // Use official DM api + if (details.isOfficial(context)) { + return getTwitterOfficialMessages(microBlog, details, param, index) + } + } } // Use default method return getDefaultMessages(microBlog, details, param, index) } + private fun getTwitterOfficialMessages(microBlog: MicroBlog, details: AccountDetails, + param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData { + val conversationId = param.conversationId + if (conversationId == null) { + return getTwitterOfficialUserInbox(microBlog, details, param, index) + } else { + return getTwitterOfficialConversation(microBlog, details, conversationId, param, index) + } + } + private fun getFanfouMessages(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData { val conversationId = param.conversationId if (conversationId == null) { @@ -179,6 +195,35 @@ class GetMessagesTask( } + private fun getTwitterOfficialConversation(microBlog: MicroBlog, details: AccountDetails, + conversationId: String, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData { + val maxId = (param.pagination?.get(index) as? SinceMaxPagination)?.maxId + ?: return DatabaseUpdateData(emptyList(), emptyList()) + val paging = Paging().apply { + maxId(maxId) + } + + val response = microBlog.getDmConversation(conversationId, paging).conversationTimeline + return createDatabaseUpdateData(context, details, response, profileImageSize) + } + + private fun getTwitterOfficialUserInbox(microBlog: MicroBlog, details: AccountDetails, + param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData { + val maxId = (param.pagination?.get(index) as? SinceMaxPagination)?.maxId + val cursor = (param.pagination?.get(index) as? CursorPagination)?.cursor + val response = if (cursor != null) { + microBlog.getUserUpdates(cursor).userEvents + } else { + microBlog.getUserInbox(Paging().apply { + if (maxId != null) { + maxId(maxId) + } + }).userInbox + } ?: throw MicroBlogException("Null response data") + return createDatabaseUpdateData(context, details, response, profileImageSize) + } + + private fun getFanfouConversations(microBlog: MicroBlog, details: AccountDetails, param: RefreshMessagesTaskParam, index: Int): DatabaseUpdateData { val accountKey = details.key @@ -230,10 +275,16 @@ class GetMessagesTask( defaultKeys, false) val outgoingIds = DataStoreUtils.getNewestMessageIds(context, Messages.CONTENT_URI, defaultKeys, true) + val cursors = DataStoreUtils.getNewestConversations(context, Conversations.CONTENT_URI, + twitterOfficialKeys).mapToArray { it?.request_cursor } accounts.forEachIndexed { index, details -> if (details == null) return@forEachIndexed - result[index] = SinceMaxPagination.sinceId(incomingIds[index], -1) - result[accounts.size + index] = SinceMaxPagination.sinceId(outgoingIds[index], -1) + if (details.isOfficial(context)) { + result[index] = CursorPagination.valueOf(cursors[index]) + } else { + result[index] = SinceMaxPagination.sinceId(incomingIds[index], -1) + result[accounts.size + index] = SinceMaxPagination.sinceId(outgoingIds[index], -1) + } } return@lazy result } @@ -250,7 +301,7 @@ class GetMessagesTask( val outgoingIds = DataStoreUtils.getOldestMessageIds(context, Messages.CONTENT_URI, defaultKeys, true) val oldestConversations = DataStoreUtils.getOldestConversations(context, - Conversations.CONTENT_URI, emptyArray()) + Conversations.CONTENT_URI, twitterOfficialKeys) oldestConversations.forEachIndexed { i, conversation -> val extras = conversation?.conversation_extras as? TwitterOfficialConversationExtras ?: return@forEachIndexed incomingIds[i] = extras.maxEntryId @@ -291,6 +342,19 @@ class GetMessagesTask( protected val defaultKeys: Array by lazy { return@lazy accounts.map { account -> account ?: return@map null + if (account.isOfficial(context)) { + return@map null + } + return@map account.key + }.toTypedArray() + } + + protected val twitterOfficialKeys: Array by lazy { + return@lazy accounts.map { account -> + account ?: return@map null + if (!account.isOfficial(context)) { + return@map null + } return@map account.key }.toTypedArray() } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/MarkMessageReadTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/MarkMessageReadTask.kt index ff1d398a2..5cd678c6c 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/MarkMessageReadTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/MarkMessageReadTask.kt @@ -28,6 +28,7 @@ import org.mariotaku.microblog.library.MicroBlogException import org.mariotaku.sqliteqb.library.Expression import org.mariotaku.sqliteqb.library.OrderBy import org.mariotaku.twidere.annotation.AccountType +import org.mariotaku.twidere.extension.model.isOfficial import org.mariotaku.twidere.extension.model.newMicroBlogInstance import org.mariotaku.twidere.extension.model.timestamp import org.mariotaku.twidere.extension.queryOne @@ -80,6 +81,23 @@ class MarkMessageReadTask( internal fun performMarkRead(context: Context, microBlog: MicroBlog, account: AccountDetails, conversation: ParcelableMessageConversation): Pair? { val cr = context.contentResolver + when (account.type) { + AccountType.TWITTER -> { + if (account.isOfficial(context)) { + val event = (conversation.conversation_extras as? TwitterOfficialConversationExtras)?.maxReadEvent ?: run { + val message = cr.findRecentMessage(account.key, conversation.id) ?: return null + return@run Pair(message.id, message.timestamp) + } + if (conversation.last_read_timestamp > event.second) { + // Local is newer, ignore network request + return event + } + if (microBlog.markDmRead(conversation.id, event.first).isSuccessful) { + return event + } + } + } + } val message = cr.findRecentMessage(account.key, conversation.id) ?: return null return Pair(message.id, message.timestamp) } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/SendMessageTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/SendMessageTask.kt index e4f38be0b..9f9095913 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/SendMessageTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/SendMessageTask.kt @@ -31,6 +31,7 @@ import org.mariotaku.sqliteqb.library.Expression import org.mariotaku.twidere.R import org.mariotaku.twidere.annotation.AccountType import org.mariotaku.twidere.extension.model.api.* +import org.mariotaku.twidere.extension.model.isOfficial import org.mariotaku.twidere.extension.model.newMicroBlogInstance import org.mariotaku.twidere.model.AccountDetails import org.mariotaku.twidere.model.ParcelableMedia @@ -41,6 +42,7 @@ import org.mariotaku.twidere.model.util.ParcelableMessageUtils import org.mariotaku.twidere.provider.TwidereDataStore.Messages.Conversations import org.mariotaku.twidere.task.ExceptionHandlingAbstractTask import org.mariotaku.twidere.task.twitter.UpdateStatusTask +import org.mariotaku.twidere.task.twitter.message.GetMessagesTask import org.mariotaku.twidere.task.twitter.message.GetMessagesTask.Companion.addConversation import org.mariotaku.twidere.task.twitter.message.GetMessagesTask.Companion.addLocalConversations @@ -84,7 +86,11 @@ class SendMessageTask( message: ParcelableNewMessage): GetMessagesTask.DatabaseUpdateData { when (account.type) { AccountType.TWITTER -> { - return sendTwitterMessageEvent(microBlog, account, message) + if (account.isOfficial(context)) { + return sendTwitterOfficialDM(microBlog, account, message) + } else { + return sendTwitterMessageEvent(microBlog, account, message) + } } AccountType.FANFOU -> { return sendFanfouDM(microBlog, account, message) @@ -93,6 +99,47 @@ class SendMessageTask( return sendDefaultDM(microBlog, account, message) } + private fun sendTwitterOfficialDM(microBlog: MicroBlog, account: AccountDetails, + message: ParcelableNewMessage): GetMessagesTask.DatabaseUpdateData { + var deleteOnSuccess: List? = null + var deleteAlways: List? = null + val sendResponse = try { + val conversationId = message.conversation_id + val tempConversation = message.is_temp_conversation + + val newDm = NewDm() + if (!tempConversation && conversationId != null) { + newDm.setConversationId(conversationId) + } else { + newDm.setRecipientIds(message.recipient_ids) + } + newDm.setText(message.text) + + if (message.media.isNotNullOrEmpty()) { + val upload = account.newMicroBlogInstance(context, cls = TwitterUpload::class.java) + val uploadResult = UpdateStatusTask.uploadMicroBlogMediaShared(context, + upload, account, message.media, null, null, true, null) + newDm.setMediaId(uploadResult.ids[0]) + deleteAlways = uploadResult.deleteAlways + deleteOnSuccess = uploadResult.deleteOnSuccess + } + microBlog.sendDm(newDm) + } catch (e: UpdateStatusTask.UploadException) { + e.deleteAlways?.forEach { + it.delete(context) + } + throw MicroBlogException(e) + } finally { + deleteAlways?.forEach { it.delete(context) } + } + deleteOnSuccess?.forEach { it.delete(context) } + val conversationId = sendResponse.entries?.firstOrNull { + it.message != null + }?.message?.conversationId + val response = microBlog.getDmConversation(conversationId, null).conversationTimeline + return GetMessagesTask.createDatabaseUpdateData(context, account, response, profileImageSize) + } + private fun sendTwitterMessageEvent(microBlog: MicroBlog, account: AccountDetails, message: ParcelableNewMessage): GetMessagesTask.DatabaseUpdateData { val recipientId = message.recipient_ids.singleOrNull() ?: throw MicroBlogException("No recipient") diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/SetConversationNotificationDisabledTask.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/SetConversationNotificationDisabledTask.kt index b8bbc7b85..0239b4c20 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/SetConversationNotificationDisabledTask.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/task/twitter/message/SetConversationNotificationDisabledTask.kt @@ -24,6 +24,7 @@ import android.content.Context import org.mariotaku.microblog.library.MicroBlog import org.mariotaku.microblog.library.MicroBlogException import org.mariotaku.twidere.annotation.AccountType +import org.mariotaku.twidere.extension.model.isOfficial import org.mariotaku.twidere.extension.model.newMicroBlogInstance import org.mariotaku.twidere.extension.model.notificationDisabled import org.mariotaku.twidere.model.AccountDetails @@ -60,6 +61,24 @@ class SetConversationNotificationDisabledTask( private fun requestSetNotificationDisabled(microBlog: MicroBlog, account: AccountDetails): GetMessagesTask.DatabaseUpdateData { + when (account.type) { + AccountType.TWITTER -> { + if (account.isOfficial(context)) { + val response = if (notificationDisabled) { + microBlog.disableDmConversations(conversationId) + } else { + microBlog.enableDmConversations(conversationId) + } + val conversation = DataStoreUtils.findMessageConversation(context, accountKey, + conversationId) ?: return GetMessagesTask.DatabaseUpdateData(emptyList(), emptyList()) + if (response.isSuccessful) { + conversation.notificationDisabled = notificationDisabled + } + return GetMessagesTask.DatabaseUpdateData(listOf(conversation), emptyList()) + } + } + } + val conversation = DataStoreUtils.findMessageConversation(context, accountKey, conversationId) ?: return GetMessagesTask.DatabaseUpdateData(emptyList(), emptyList()) conversation.notificationDisabled = notificationDisabled diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/AsyncTwitterWrapper.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/AsyncTwitterWrapper.kt index 740627656..5d38d5134 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/AsyncTwitterWrapper.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/AsyncTwitterWrapper.kt @@ -377,6 +377,18 @@ class AsyncTwitterWrapper( } fun setActivitiesAboutMeUnreadAsync(accountKeys: Array, cursor: Long) { + val task = object : ExceptionHandlingAbstractTask(context) { + override val exceptionClass = MicroBlogException::class.java + + override fun onExecute(params: Any?) { + for (accountKey in accountKeys) { + val microBlog = MicroBlogAPIFactory.getInstance(context, accountKey) ?: continue + if (!AccountUtils.isOfficial(context, accountKey)) continue + microBlog.setActivitiesAboutMeUnread(cursor) + } + } + } + TaskStarter.execute(task) } fun addUpdatingRelationshipId(accountKey: UserKey, userKey: UserKey) { diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreUtils.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreUtils.kt index 02c63c229..dd6a742e8 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreUtils.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/DataStoreUtils.kt @@ -998,8 +998,23 @@ object DataStoreUtils { private fun getOfficialSeparatedIds(context: Context, getFromDatabase: (Array, Boolean) -> T, mergeResult: (T, T) -> T, accountKeys: Array): T { - val officialMaxPositions = getFromDatabase(emptyArray(), true) - val notOfficialMaxPositions = getFromDatabase(accountKeys, false) + val officialKeys = Array(accountKeys.size) { + val key = accountKeys[it] ?: return@Array null + if (AccountUtils.isOfficial(context, key)) { + return@Array key + } + return@Array null + } + val notOfficialKeys = Array(accountKeys.size) { + val key = accountKeys[it] ?: return@Array null + if (AccountUtils.isOfficial(context, key)) { + return@Array null + } + return@Array key + } + + val officialMaxPositions = getFromDatabase(officialKeys, true) + val notOfficialMaxPositions = getFromDatabase(notOfficialKeys, false) return mergeResult(officialMaxPositions, notOfficialMaxPositions) } } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/util/MenuUtils.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/util/MenuUtils.kt index e6b678b73..9800a6f40 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/util/MenuUtils.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/util/MenuUtils.kt @@ -54,6 +54,7 @@ import org.mariotaku.twidere.app.TwidereApplication import org.mariotaku.twidere.constant.favoriteConfirmationKey import org.mariotaku.twidere.constant.iWantMyStarsBackKey import org.mariotaku.twidere.constant.nameFirstKey +import org.mariotaku.twidere.extension.model.isOfficial import org.mariotaku.twidere.fragment.AbsStatusesFragment import org.mariotaku.twidere.fragment.AddStatusFilterDialogFragment import org.mariotaku.twidere.fragment.BaseFragment @@ -188,7 +189,11 @@ object MenuUtils { favorite.setTitle(if (isFavorite) R.string.action_undo_like else R.string.action_like) } } - menu.setItemAvailability(R.id.translate, false) + val translate = menu.findItem(R.id.translate) + if (translate != null) { + val isOfficialKey = details.isOfficial(context) + menu.setItemAvailability(R.id.translate, isOfficialKey) + } menu.removeGroup(MENU_GROUP_STATUS_EXTENSION) addIntentToMenuForExtension(context, menu, MENU_GROUP_STATUS_EXTENSION, INTENT_ACTION_EXTENSION_OPEN_STATUS, EXTRA_STATUS, EXTRA_STATUS_JSON, status) @@ -279,8 +284,10 @@ object MenuUtils { DestroyStatusDialogFragment.show(fm, status) } R.id.pin -> { + PinStatusDialogFragment.show(fm, status) } R.id.unpin -> { + UnpinStatusDialogFragment.show(fm, status) } R.id.add_to_filter -> { AddStatusFilterDialogFragment.show(fm, status) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/DetailStatusViewHolder.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/DetailStatusViewHolder.kt index 883813851..55c43b55d 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/DetailStatusViewHolder.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/view/holder/status/DetailStatusViewHolder.kt @@ -339,8 +339,26 @@ class DetailStatusViewHolder( val lang = status.lang - translateLabelView.setText(R.string.unknown_language) - translateContainer.visibility = View.GONE + if (CheckUtils.isValidLocale(lang) && account.isOfficial(context)) { + translateContainer.visibility = View.VISIBLE + if (translation != null) { + val locale = Locale(translation.translatedLang) + translateLabelView.text = context.getString(R.string.label_translated_to_language, + locale.displayLanguage) + translateResultView.visibility = View.VISIBLE + translateChangeLanguageView.visibility = View.VISIBLE + translateResultView.text = translation.text + } else { + val locale = Locale(lang) + translateLabelView.text = context.getString(R.string.label_translate_from_language, + locale.displayLanguage) + translateResultView.visibility = View.GONE + translateChangeLanguageView.visibility = View.GONE + } + } else { + translateLabelView.setText(R.string.unknown_language) + translateContainer.visibility = View.GONE + } textView.setTextIsSelectable(true) translateResultView.setTextIsSelectable(true) diff --git a/twidere/src/main/res/values/arrays_no_translate.xml b/twidere/src/main/res/values/arrays_no_translate.xml index c5a1030bc..70f30d522 100644 --- a/twidere/src/main/res/values/arrays_no_translate.xml +++ b/twidere/src/main/res/values/arrays_no_translate.xml @@ -25,6 +25,40 @@ true + + + Twitter for Android + + Twitter for iPhone + + Twitter for iPad + + Twitter for Mac + + Twitter for Windows Phone + + Twitter for Google TV + + TweetDeck + + + + + TWITTER_FOR_ANDROID + + TWITTER_FOR_IPHONE + + TWITTER_FOR_IPAD + + TWITTER_FOR_MAC + + TWITTER_FOR_WINDOWS_PHONE + + TWITTER_FOR_GOOGLE_TV + + TWEETDECK + + camera gallery @@ -65,6 +99,23 @@ mentions inbox + + + + 6ce85096 + + deffe9c7 + + 9f00e0cb + + df27640e + + 62bd0d33 + + 56d8f9ff + + ac602936 + round square From ab6ce518728217b74662d2af9ef7ba187251f803 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 3 Apr 2020 17:17:41 +0800 Subject: [PATCH 17/21] Migrate PrivateDirectMessagesResources to AndroidX --- .../library/twitter/api/PrivateDirectMessagesResources.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateDirectMessagesResources.java b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateDirectMessagesResources.java index 8aedefa37..a88e96bde 100644 --- a/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateDirectMessagesResources.java +++ b/twidere.component.common/src/main/java/org/mariotaku/microblog/library/twitter/api/PrivateDirectMessagesResources.java @@ -18,7 +18,7 @@ package org.mariotaku.microblog.library.twitter.api; -import android.support.annotation.Nullable; +import androidx.annotation.Nullable; import org.mariotaku.microblog.library.MicroBlogException; import org.mariotaku.microblog.library.twitter.model.ConversationTimeline; From 1a0ff60215621bb7b47bb2d7e881a3bcc76379cd Mon Sep 17 00:00:00 2001 From: Tlaster Date: Fri, 3 Apr 2020 17:49:35 +0800 Subject: [PATCH 18/21] Fix import && null safety --- .../java/org/mariotaku/twidere/model/util/AccountUtils.java | 5 ++++- .../mariotaku/twidere/util/InternalTwitterContentUtils.java | 1 + .../java/org/mariotaku/twidere/util/MicroBlogAPIFactory.java | 3 +++ .../twidere/extension/model/AccountDetailsExtensions.kt | 5 ++++- .../org/mariotaku/twidere/fragment/CustomTabsFragment.kt | 2 +- .../fragment/message/MessageConversationInfoFragment.kt | 4 ++-- .../twidere/fragment/status/PinStatusDialogFragment.kt | 4 ++-- .../org/mariotaku/twidere/fragment/status/StatusFragment.kt | 2 +- .../twidere/fragment/status/UnpinStatusDialogFragment.kt | 4 ++-- 9 files changed, 20 insertions(+), 10 deletions(-) diff --git a/twidere/src/main/java/org/mariotaku/twidere/model/util/AccountUtils.java b/twidere/src/main/java/org/mariotaku/twidere/model/util/AccountUtils.java index 65161e0e8..9b721a867 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/model/util/AccountUtils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/model/util/AccountUtils.java @@ -129,7 +129,10 @@ public class AccountUtils { return null; } - public static boolean isOfficial(@NonNull final Context context, @NonNull final UserKey accountKey) { + public static boolean isOfficial(@Nullable final Context context, @NonNull final UserKey accountKey) { + if (context == null) { + return false; + } AccountManager am = AccountManager.get(context); Account account = AccountUtils.findByAccountKey(am, accountKey); if (account == null) return false; diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/InternalTwitterContentUtils.java b/twidere/src/main/java/org/mariotaku/twidere/util/InternalTwitterContentUtils.java index fdef0f3b5..717727ebe 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/InternalTwitterContentUtils.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/InternalTwitterContentUtils.java @@ -2,6 +2,7 @@ package org.mariotaku.twidere.util; import androidx.annotation.NonNull; import androidx.annotation.Nullable; +import android.content.Context; import android.text.TextUtils; import org.mariotaku.microblog.library.twitter.model.DMResponse; diff --git a/twidere/src/main/java/org/mariotaku/twidere/util/MicroBlogAPIFactory.java b/twidere/src/main/java/org/mariotaku/twidere/util/MicroBlogAPIFactory.java index bca1bf941..61ed2b846 100644 --- a/twidere/src/main/java/org/mariotaku/twidere/util/MicroBlogAPIFactory.java +++ b/twidere/src/main/java/org/mariotaku/twidere/util/MicroBlogAPIFactory.java @@ -25,6 +25,9 @@ import org.mariotaku.twidere.model.ConsumerKeyType; import org.mariotaku.twidere.model.UserKey; import org.mariotaku.twidere.model.account.cred.Credentials; import org.mariotaku.twidere.model.util.AccountUtils; +import org.mariotaku.twidere.util.api.TwitterAndroidExtraHeaders; +import org.mariotaku.twidere.util.api.TwitterMacExtraHeaders; +import org.mariotaku.twidere.util.api.UserAgentExtraHeaders; import java.util.List; import java.util.Locale; diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountDetailsExtensions.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountDetailsExtensions.kt index ed59385da..92998a6aa 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountDetailsExtensions.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/extension/model/AccountDetailsExtensions.kt @@ -16,7 +16,10 @@ import org.mariotaku.twidere.util.text.FanfouValidator import org.mariotaku.twidere.util.text.MastodonValidator import org.mariotaku.twidere.util.text.TwitterValidator -fun AccountDetails.isOfficial(context: Context): Boolean { +fun AccountDetails.isOfficial(context: Context?): Boolean { + if (context == null) { + return false + } val extra = this.extras if (extra is TwitterAccountExtras) { return extra.isOfficialCredentials diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/CustomTabsFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/CustomTabsFragment.kt index c42d028db..1bc4678fb 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/CustomTabsFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/CustomTabsFragment.kt @@ -290,7 +290,7 @@ class CustomTabsFragment : BaseFragment(), LoaderCallbacks, MultiChoice if (!accountRequired) { accountsAdapter.add(AccountDetails.dummy()) } - val officialKeyOnly = arguments.getBoolean(EXTRA_OFFICIAL_KEY_ONLY, false) + val officialKeyOnly = arguments?.getBoolean(EXTRA_OFFICIAL_KEY_ONLY, false) ?: false accountsAdapter.addAll(AccountUtils.getAllAccountDetails(AccountManager.get(context), true).filter { if (officialKeyOnly && !it.isOfficial(context)) { return@filter false diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessageConversationInfoFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessageConversationInfoFragment.kt index 6af87206c..e6b05aa4c 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessageConversationInfoFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/message/MessageConversationInfoFragment.kt @@ -330,7 +330,7 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment, private fun performAddParticipant(user: ParcelableUser) { ProgressDialogFragment.show(childFragmentManager, "add_participant_progress") val weakThis = WeakReference(this) - val task = AddParticipantsTask(context, accountKey, conversationId, listOf(user)) + val task = AddParticipantsTask(context!!, accountKey, conversationId, listOf(user)) task.callback = callback@ { succeed -> val f = weakThis.get() ?: return@callback f.dismissDialogThen("add_participant_progress") { @@ -400,7 +400,7 @@ class MessageConversationInfoFragment : BaseFragment(), IToolBarSupportFragment, val context = fragment.context when (account.type) { AccountType.TWITTER -> { - if (account.isOfficial(context)) { + if (account.isOfficial(context) && context != null) { val upload = account.newMicroBlogInstance(context, cls = TwitterUpload::class.java) if (uri == null) { val result = microBlog.updateDmConversationAvatar(conversationId, null) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/PinStatusDialogFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/PinStatusDialogFragment.kt index 0657636ba..6ae94d09b 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/PinStatusDialogFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/PinStatusDialogFragment.kt @@ -20,7 +20,7 @@ package org.mariotaku.twidere.fragment.status import android.os.Bundle -import android.support.v4.app.FragmentManager +import androidx.fragment.app.FragmentManager import org.mariotaku.abstask.library.TaskStarter import org.mariotaku.twidere.R import org.mariotaku.twidere.constant.IntentConstants.EXTRA_STATUS @@ -35,7 +35,7 @@ class PinStatusDialogFragment : AbsSimpleStatusOperationDialogFragment() { get() = getString(R.string.message_pin_status_confirm) override fun onPerformAction(status: ParcelableStatus) { - val task = PinStatusTask(context, status.account_key, status.id) + val task = PinStatusTask(context!!, status.account_key, status.id) TaskStarter.execute(task) } diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/StatusFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/StatusFragment.kt index 4461bb170..691dcc39a 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/StatusFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/StatusFragment.kt @@ -684,7 +684,7 @@ class StatusFragment : BaseFragment(), LoaderCallbacks(fragment.context, status.account_key) { + AbsAccountRequestTask(fragment.context!!, status.account_key) { private val weakFragment = WeakReference(fragment) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/UnpinStatusDialogFragment.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/UnpinStatusDialogFragment.kt index 1d1190140..49171f021 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/UnpinStatusDialogFragment.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/fragment/status/UnpinStatusDialogFragment.kt @@ -20,7 +20,7 @@ package org.mariotaku.twidere.fragment.status import android.os.Bundle -import android.support.v4.app.FragmentManager +import androidx.fragment.app.FragmentManager import org.mariotaku.abstask.library.TaskStarter import org.mariotaku.twidere.R import org.mariotaku.twidere.constant.IntentConstants.EXTRA_STATUS @@ -35,7 +35,7 @@ class UnpinStatusDialogFragment : AbsSimpleStatusOperationDialogFragment() { get() = getString(R.string.message_unpin_status_confirm) override fun onPerformAction(status: ParcelableStatus) { - val task = UnpinStatusTask(context, status.account_key, status.id) + val task = UnpinStatusTask(context!!, status.account_key, status.id) TaskStarter.execute(task) } From 353e1e89ef86e378abf933c4f711e0961ab53fa0 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 7 Apr 2020 14:11:39 +0800 Subject: [PATCH 19/21] Update DEFAULT_API_CONFIGS_URL --- .../org/mariotaku/twidere/loader/DefaultAPIConfigLoader.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/DefaultAPIConfigLoader.kt b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/DefaultAPIConfigLoader.kt index 4357d11b9..78061f709 100644 --- a/twidere/src/main/kotlin/org/mariotaku/twidere/loader/DefaultAPIConfigLoader.kt +++ b/twidere/src/main/kotlin/org/mariotaku/twidere/loader/DefaultAPIConfigLoader.kt @@ -62,6 +62,6 @@ class DefaultAPIConfigLoader(context: Context) : FixedAsyncTaskLoader Date: Tue, 7 Apr 2020 15:00:58 +0800 Subject: [PATCH 20/21] Bump Version --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index 94720ed43..f5d970268 100644 --- a/build.gradle +++ b/build.gradle @@ -17,8 +17,8 @@ allprojects { ext { projectGroupId = 'org.mariotaku.twidere' - projectVersionCode = 503 - projectVersionName = '4.0.4' + projectVersionCode = 504 + projectVersionName = '4.0.5' globalCompileSdkVersion = 29 globalBuildToolsVersion = '29.0.2' From c9ec379f706d875284da01f064a5eb3d8c8cc8f3 Mon Sep 17 00:00:00 2001 From: Tlaster Date: Tue, 7 Apr 2020 16:32:06 +0800 Subject: [PATCH 21/21] Bump Version --- build.gradle | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/build.gradle b/build.gradle index f5d970268..8962660e6 100644 --- a/build.gradle +++ b/build.gradle @@ -17,8 +17,8 @@ allprojects { ext { projectGroupId = 'org.mariotaku.twidere' - projectVersionCode = 504 - projectVersionName = '4.0.5' + projectVersionCode = 505 + projectVersionName = '4.0.6' globalCompileSdkVersion = 29 globalBuildToolsVersion = '29.0.2'