Browse Source

initial commit

tags/v1.0.3
tom79 4 years ago
commit
c4ea4943d6
100 changed files with 8808 additions and 0 deletions
  1. +9
    -0
      .gitignore
  2. +22
    -0
      .idea/compiler.xml
  3. +3
    -0
      .idea/copyright/profiles_settings.xml
  4. +18
    -0
      .idea/gradle.xml
  5. +3
    -0
      .idea/markdown-navigator/profiles_settings.xml
  6. +129
    -0
      .idea/misc.xml
  7. +9
    -0
      .idea/modules.xml
  8. +12
    -0
      .idea/runConfigurations.xml
  9. +7
    -0
      .idea/vcs.xml
  10. +1
    -0
      app/.gitignore
  11. +37
    -0
      app/build.gradle
  12. BIN
      app/mastodon-etalab-v1.0.1.apk
  13. BIN
      app/mastodon-etalab-v1.0.2.apk
  14. BIN
      app/mastodon-etalab-v1.0.apk
  15. +25
    -0
      app/proguard-rules.pro
  16. +26
    -0
      app/src/androidTest/java/mastodon/etalab/gouv/fr/mastodon/ExampleInstrumentedTest.java
  17. +88
    -0
      app/src/main/AndroidManifest.xml
  18. BIN
      app/src/main/ic_launcher-web.png
  19. +75
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/activities/AboutActivity.java
  20. +108
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/activities/LoginActivity.java
  21. +424
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/activities/MainActivity.java
  22. +37
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/activities/MainApplication.java
  23. +307
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/activities/ShowAccountActivity.java
  24. +116
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/activities/ShowConversationActivity.java
  25. +36
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/activities/SplashActivity.java
  26. +529
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java
  27. +195
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/activities/WebviewActivity.java
  28. +74
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/PostActionAsyncTask.java
  29. +56
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveAccountAsyncTask.java
  30. +88
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveAccountsAsyncTask.java
  31. +53
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveContextAsyncTask.java
  32. +56
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveFeedsAccountAsyncTask.java
  33. +112
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveFeedsAsyncTask.java
  34. +62
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveNotificationsAsyncTask.java
  35. +55
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveRelationshipAsyncTask.java
  36. +56
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveSearchAsyncTask.java
  37. +101
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/UpdateAccountInfoAsyncTask.java
  38. +58
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/UploadActionAsyncTask.java
  39. +1192
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/client/API.java
  40. +178
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Account.java
  41. +25
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Application.java
  42. +77
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Attachment.java
  43. +28
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Card.java
  44. +44
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Context.java
  45. +32
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Error.java
  46. +59
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Mention.java
  47. +71
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Notification.java
  48. +78
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Relationship.java
  49. +39
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Results.java
  50. +220
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Status.java
  51. +42
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Tag.java
  52. +42
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/client/Error.java
  53. +68
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/client/OauthClient.java
  54. +247
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/drawers/AccountsListAdapter.java
  55. +118
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/drawers/AccountsSearchAdapter.java
  56. +249
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/drawers/NotificationsListAdapter.java
  57. +718
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/drawers/StatusListAdapter.java
  58. +188
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayAccountsFragment.java
  59. +170
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayNotificationsFragment.java
  60. +201
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayStatusFragment.java
  61. +153
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/fragments/SettingsNotificationsFragment.java
  62. +172
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/fragments/SettingsOptimizationFragment.java
  63. +99
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/fragments/TabLayoutSettingsFragment.java
  64. +359
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java
  65. +26
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnPostActionInterface.java
  66. +25
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveAccountInterface.java
  67. +28
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveAccountsInterface.java
  68. +25
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveAttachmentInterface.java
  69. +26
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveContextInterface.java
  70. +28
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveFeedsAccountInterface.java
  71. +28
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveFeedsInterface.java
  72. +28
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveNotificationsInterface.java
  73. +26
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveRelationshipInterface.java
  74. +25
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveSearchInterface.java
  75. +24
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnUpdateAccountInfoInterface.java
  76. +39
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/jobs/ApplicationJob.java
  77. +251
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/jobs/NotificationsSyncJob.java
  78. +261
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/sqlite/AccountDAO.java
  79. +112
    -0
      app/src/main/java/fr/gouv/etalab/mastodon/sqlite/Sqlite.java
  80. BIN
      app/src/main/res/drawable-hdpi-v11/notification_icon.png
  81. BIN
      app/src/main/res/drawable-hdpi-v9/notification_icon.png
  82. BIN
      app/src/main/res/drawable-hdpi/ic_action_camera.png
  83. BIN
      app/src/main/res/drawable-hdpi/ic_action_clock.png
  84. BIN
      app/src/main/res/drawable-hdpi/ic_action_edit.png
  85. BIN
      app/src/main/res/drawable-hdpi/ic_action_gear.png
  86. BIN
      app/src/main/res/drawable-hdpi/ic_action_globe.png
  87. BIN
      app/src/main/res/drawable-hdpi/ic_action_home.png
  88. BIN
      app/src/main/res/drawable-hdpi/ic_action_lock_closed.png
  89. BIN
      app/src/main/res/drawable-hdpi/ic_action_lock_open.png
  90. BIN
      app/src/main/res/drawable-hdpi/ic_action_more.png
  91. BIN
      app/src/main/res/drawable-hdpi/ic_action_search.png
  92. BIN
      app/src/main/res/drawable-hdpi/ic_action_star.png
  93. BIN
      app/src/main/res/drawable-hdpi/ic_action_users.png
  94. BIN
      app/src/main/res/drawable-hdpi/ic_action_volume_mute.png
  95. BIN
      app/src/main/res/drawable-hdpi/ic_block.png
  96. BIN
      app/src/main/res/drawable-hdpi/ic_block_white.png
  97. BIN
      app/src/main/res/drawable-hdpi/ic_close.png
  98. BIN
      app/src/main/res/drawable-hdpi/ic_email.png
  99. BIN
      app/src/main/res/drawable-hdpi/ic_fav_black.png
  100. BIN
      app/src/main/res/drawable-hdpi/ic_fav_yellow.png

+ 9
- 0
.gitignore View File

@@ -0,0 +1,9 @@
*.iml
.gradle
/local.properties
/.idea/workspace.xml
/.idea/libraries
.DS_Store
/build
/captures
.externalNativeBuild

+ 22
- 0
.idea/compiler.xml View File

@@ -0,0 +1,22 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="CompilerConfiguration">
<resourceExtensions />
<wildcardResourcePatterns>
<entry name="!?*.java" />
<entry name="!?*.form" />
<entry name="!?*.class" />
<entry name="!?*.groovy" />
<entry name="!?*.scala" />
<entry name="!?*.flex" />
<entry name="!?*.kt" />
<entry name="!?*.clj" />
<entry name="!?*.aj" />
</wildcardResourcePatterns>
<annotationProcessing>
<profile default="true" name="Default" enabled="false">
<processorPath useClasspath="true" />
</profile>
</annotationProcessing>
</component>
</project>

+ 3
- 0
.idea/copyright/profiles_settings.xml View File

@@ -0,0 +1,3 @@
<component name="CopyrightManager">
<settings default="" />
</component>

+ 18
- 0
.idea/gradle.xml View File

@@ -0,0 +1,18 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="GradleSettings">
<option name="linkedExternalProjectsSettings">
<GradleProjectSettings>
<option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="modules">
<set>
<option value="$PROJECT_DIR$" />
<option value="$PROJECT_DIR$/app" />
</set>
</option>
<option name="resolveModulePerSourceSet" value="false" />
</GradleProjectSettings>
</option>
</component>
</project>

+ 3
- 0
.idea/markdown-navigator/profiles_settings.xml View File

@@ -0,0 +1,3 @@
<component name="MarkdownNavigator.ProfileManager">
<settings default="" pdf-export="" />
</component>

+ 129
- 0
.idea/misc.xml View File

@@ -0,0 +1,129 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="EntryPointsManager">
<entry_points version="2.0" />
</component>
<component name="MarkdownProjectSettings">
<PreviewSettings splitEditorLayout="SPLIT" splitEditorPreview="PREVIEW" useGrayscaleRendering="false" zoomFactor="1.0" maxImageWidth="0" showGitHubPageIfSynced="false" allowBrowsingInPreview="false" synchronizePreviewPosition="true" highlightPreviewType="LINE" highlightFadeOut="5" highlightOnTyping="true" synchronizeSourcePosition="true" verticallyAlignSourceAndPreviewSyncPosition="true" showSearchHighlightsInPreview="true" showSelectionInPreview="true">
<PanelProvider>
<provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.panel" providerName="Default - Swing" />
</PanelProvider>
</PreviewSettings>
<ParserSettings>
<PegdownExtensions>
<option name="ABBREVIATIONS" value="false" />
<option name="ANCHORLINKS" value="true" />
<option name="ASIDE" value="false" />
<option name="ATXHEADERSPACE" value="true" />
<option name="AUTOLINKS" value="true" />
<option name="DEFINITIONS" value="false" />
<option name="FENCED_CODE_BLOCKS" value="true" />
<option name="FOOTNOTES" value="false" />
<option name="HARDWRAPS" value="false" />
<option name="INSERTED" value="false" />
<option name="QUOTES" value="false" />
<option name="RELAXEDHRULES" value="true" />
<option name="SMARTS" value="false" />
<option name="STRIKETHROUGH" value="true" />
<option name="SUBSCRIPT" value="false" />
<option name="SUPERSCRIPT" value="false" />
<option name="SUPPRESS_HTML_BLOCKS" value="false" />
<option name="SUPPRESS_INLINE_HTML" value="false" />
<option name="TABLES" value="true" />
<option name="TASKLISTITEMS" value="true" />
<option name="TOC" value="false" />
<option name="WIKILINKS" value="true" />
</PegdownExtensions>
<ParserOptions>
<option name="COMMONMARK_LISTS" value="false" />
<option name="DUMMY" value="false" />
<option name="EMOJI_SHORTCUTS" value="true" />
<option name="FLEXMARK_FRONT_MATTER" value="false" />
<option name="GFM_LOOSE_BLANK_LINE_AFTER_ITEM_PARA" value="false" />
<option name="GFM_TABLE_RENDERING" value="true" />
<option name="GITBOOK_URL_ENCODING" value="false" />
<option name="GITHUB_EMOJI_URL" value="false" />
<option name="GITHUB_LISTS" value="true" />
<option name="GITHUB_WIKI_LINKS" value="true" />
<option name="JEKYLL_FRONT_MATTER" value="false" />
<option name="SIM_TOC_BLANK_LINE_SPACER" value="true" />
</ParserOptions>
</ParserSettings>
<HtmlSettings headerTopEnabled="false" headerBottomEnabled="false" bodyTopEnabled="false" bodyBottomEnabled="false" embedUrlContent="false" addPageHeader="true">
<GeneratorProvider>
<provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.generator" providerName="Default Swing HTML Generator" />
</GeneratorProvider>
<headerTop />
<headerBottom />
<bodyTop />
<bodyBottom />
</HtmlSettings>
<CssSettings previewScheme="UI_SCHEME" cssUri="" isCssUriEnabled="false" isCssTextEnabled="false" isDynamicPageWidth="true">
<StylesheetProvider>
<provider providerId="com.vladsch.idea.multimarkdown.editor.swing.html.css" providerName="Default Swing Stylesheet" />
</StylesheetProvider>
<ScriptProviders />
<cssText />
</CssSettings>
<HtmlExportSettings updateOnSave="false" parentDir="$ProjectFileDir$" targetDir="$ProjectFileDir$" cssDir="" scriptDir="" plainHtml="false" imageDir="" copyLinkedImages="false" imageUniquifyType="0" targetExt="" useTargetExt="false" noCssNoScripts="false" linkToExportedHtml="true" exportOnSettingsChange="true" regenerateOnProjectOpen="false" />
<LinkMapSettings>
<textMaps />
</LinkMapSettings>
</component>
<component name="NullableNotNullManager">
<option name="myDefaultNullable" value="android.support.annotation.Nullable" />
<option name="myDefaultNotNull" value="android.support.annotation.NonNull" />
<option name="myNullables">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.Nullable" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nullable" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.Nullable" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.Nullable" />
</list>
</value>
</option>
<option name="myNotNulls">
<value>
<list size="4">
<item index="0" class="java.lang.String" itemvalue="org.jetbrains.annotations.NotNull" />
<item index="1" class="java.lang.String" itemvalue="javax.annotation.Nonnull" />
<item index="2" class="java.lang.String" itemvalue="edu.umd.cs.findbugs.annotations.NonNull" />
<item index="3" class="java.lang.String" itemvalue="android.support.annotation.NonNull" />
</list>
</value>
</option>
</component>
<component name="ProjectLevelVcsManager" settingsEditedManually="false">
<OptionsSetting value="true" id="Add" />
<OptionsSetting value="true" id="Remove" />
<OptionsSetting value="true" id="Checkout" />
<OptionsSetting value="true" id="Update" />
<OptionsSetting value="true" id="Status" />
<OptionsSetting value="true" id="Edit" />
<ConfirmationsSetting value="0" id="Add" />
<ConfirmationsSetting value="0" id="Remove" />
</component>
<component name="ProjectRootManager" version="2" languageLevel="JDK_1_7" default="true" assert-keyword="true" jdk-15="true" project-jdk-name="1.8" project-jdk-type="JavaSDK">
<output url="file://$PROJECT_DIR$/build/classes" />
</component>
<component name="ProjectType">
<option name="id" value="Android" />
</component>
<component name="masterDetails">
<states>
<state key="ProjectJDKs.UI">
<settings>
<last-edited>1.8</last-edited>
<splitter-proportions>
<option name="proportions">
<list>
<option value="0.2" />
</list>
</option>
</splitter-proportions>
</settings>
</state>
</states>
</component>
</project>

+ 9
- 0
.idea/modules.xml View File

@@ -0,0 +1,9 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="ProjectModuleManager">
<modules>
<module fileurl="file://$PROJECT_DIR$/Mastodon.iml" filepath="$PROJECT_DIR$/Mastodon.iml" />
<module fileurl="file://$PROJECT_DIR$/app/app.iml" filepath="$PROJECT_DIR$/app/app.iml" />
</modules>
</component>
</project>

+ 12
- 0
.idea/runConfigurations.xml View File

@@ -0,0 +1,12 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="RunConfigurationProducerService">
<option name="ignoredProducers">
<set>
<option value="org.jetbrains.plugins.gradle.execution.test.runner.AllInPackageGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestClassGradleConfigurationProducer" />
<option value="org.jetbrains.plugins.gradle.execution.test.runner.TestMethodGradleConfigurationProducer" />
</set>
</option>
</component>
</project>

+ 7
- 0
.idea/vcs.xml View File

@@ -0,0 +1,7 @@
<?xml version="1.0" encoding="UTF-8"?>
<project version="4">
<component name="VcsDirectoryMappings">
<mapping directory="" vcs="Git" />
<mapping directory="$PROJECT_DIR$" vcs="Git" />
</component>
</project>

+ 1
- 0
app/.gitignore View File

@@ -0,0 +1 @@
/build

+ 37
- 0
app/build.gradle View File

@@ -0,0 +1,37 @@
apply plugin: 'com.android.application'

android {
compileSdkVersion 25
buildToolsVersion "25.0.2"
defaultConfig {
applicationId "fr.gouv.etalab.mastodon"
minSdkVersion 15
targetSdkVersion 25
versionCode 2
versionName "1.0.2"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
buildTypes {
release {
minifyEnabled false
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.pro'
}
}
}

dependencies {
compile fileTree(dir: 'libs', include: ['*.jar'])
androidTestCompile('com.android.support.test.espresso:espresso-core:2.2.2', {
exclude group: 'com.android.support', module: 'support-annotations'
})
compile 'com.android.support:appcompat-v7:25.3.1'
compile 'com.android.support:design:25.3.1'
compile 'com.android.support:support-v4:25.3.1'
compile 'com.android.support.constraint:constraint-layout:1.0.0-beta4'
compile 'com.loopj.android:android-async-http:1.4.9'
compile 'com.google.code.gson:gson:2.8.0'
compile 'com.squareup.retrofit2:retrofit:2.2.0'
compile 'com.nostra13.universalimageloader:universal-image-loader:1.9.5'
compile 'com.evernote:android-job:1.1.9'
testCompile 'junit:junit:4.12'
}

BIN
app/mastodon-etalab-v1.0.1.apk View File


BIN
app/mastodon-etalab-v1.0.2.apk View File


BIN
app/mastodon-etalab-v1.0.apk View File


+ 25
- 0
app/proguard-rules.pro View File

@@ -0,0 +1,25 @@
# Add project specific ProGuard rules here.
# By default, the flags in this file are appended to flags specified
# in /Users/Thomas/Library/Android/sdk/tools/proguard/proguard-android.txt
# You can edit the include path and order by changing the proguardFiles
# directive in build.gradle.
#
# For more details, see
# http://developer.android.com/guide/developing/tools/proguard.html

# Add any project specific keep options here:

# If your project uses WebView with JS, uncomment the following
# and specify the fully qualified class name to the JavaScript interface
# class:
#-keepclassmembers class fqcn.of.javascript.interface.for.webview {
# public *;
#}

# Uncomment this to preserve the line number information for
# debugging stack traces.
#-keepattributes SourceFile,LineNumberTable

# If you keep the line number information, uncomment this to
# hide the original source file name.
#-renamesourcefileattribute SourceFile

+ 26
- 0
app/src/androidTest/java/mastodon/etalab/gouv/fr/mastodon/ExampleInstrumentedTest.java View File

@@ -0,0 +1,26 @@
package mastodon.etalab.gouv.fr.mastodon;

import android.content.Context;
import android.support.test.InstrumentationRegistry;
import android.support.test.runner.AndroidJUnit4;

import org.junit.Test;
import org.junit.runner.RunWith;

import static org.junit.Assert.*;

/**
* Instrumentation test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() throws Exception {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getTargetContext();

assertEquals("mastodon.etalab.gouv.fr.mastodon", appContext.getPackageName());
}
}

+ 88
- 0
app/src/main/AndroidManifest.xml View File

@@ -0,0 +1,88 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2017 Thomas Schneider

This file is a part of Mastodon Etalab for mastodon.etalab.gouv.fr

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.

Mastodon Etalab 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 Thomas Schneider; if not,
see <http://www.gnu.org/licenses>
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
android:installLocation="auto"
package="mastodon.etalab.gouv.fr.mastodon">


<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE" />
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE" />

<application
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:hardwareAccelerated="true"
android:name="fr.gouv.etalab.mastodon.activities.MainApplication"
android:roundIcon="@mipmap/ic_launcher"
android:supportsRtl="true"
android:theme="@style/AppTheme">
<activity
android:name="fr.gouv.etalab.mastodon.activities.MainActivity"
android:label="@string/app_name"
android:launchMode="singleTop"
android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="keyboardHidden|orientation|screenSize"
android:theme="@style/AppTheme.NoActionBar">
</activity>
<activity android:name="fr.gouv.etalab.mastodon.activities.WebviewActivity"
android:label="@string/app_name"
android:launchMode="singleTask"
/>
<activity android:name="fr.gouv.etalab.mastodon.activities.LoginActivity"
android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
/>
<activity android:name="fr.gouv.etalab.mastodon.activities.ShowAccountActivity"
android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="orientation|screenSize"
android:noHistory="true"
android:label="@string/app_name"
/>
<activity android:name="fr.gouv.etalab.mastodon.activities.ShowConversationActivity"
android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="orientation|screenSize"
android:noHistory="true"
android:label="@string/app_name"
/>
<activity android:name="fr.gouv.etalab.mastodon.activities.AboutActivity"
android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="orientation|screenSize"
android:noHistory="true"
android:label="@string/app_name"
/>
<activity android:name="fr.gouv.etalab.mastodon.activities.TootActivity"
android:windowSoftInputMode="adjustResize"
android:fitsSystemWindows="true"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
/>
<activity
android:name="fr.gouv.etalab.mastodon.activities.SplashActivity"
android:theme="@style/SplashTheme">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>

</manifest>

BIN
app/src/main/ic_launcher-web.png View File

Before After
Width: 512  |  Height: 512  |  Size: 52 KiB

+ 75
- 0
app/src/main/java/fr/gouv/etalab/mastodon/activities/AboutActivity.java View File

@@ -0,0 +1,75 @@
/* Copyright 2017 Thomas Schneider
*
* This file is a part of Mastodon Etalab for mastodon.etalab.gouv.fr
*
* 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.
*
* Mastodon Etalab 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 Thomas Schneider; if not,
* see <http://www.gnu.org/licenses>. */
package fr.gouv.etalab.mastodon.activities;

import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.text.Html;
import android.text.method.LinkMovementMethod;
import android.view.MenuItem;
import android.widget.TextView;

import mastodon.etalab.gouv.fr.mastodon.R;


/**
* Created by Thomas on 05/05/2017.
* About activity
*/

public class AboutActivity extends AppCompatActivity {
@SuppressWarnings("deprecation")
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if( getSupportActionBar() != null)
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
setContentView(R.layout.activity_about);
TextView about_version = (TextView) findViewById(R.id.about_version);
try {
PackageInfo pInfo = getPackageManager().getPackageInfo(getPackageName(), 0);
String version = pInfo.versionName;
about_version.setText(getResources().getString(R.string.about_vesrion, version));
} catch (PackageManager.NameNotFoundException e) {
e.printStackTrace();
}

TextView about_developer = (TextView) findViewById(R.id.about_developer);
TextView about_license = (TextView) findViewById(R.id.about_license);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
about_developer.setText(Html.fromHtml(getString(R.string.about_developer), Html.FROM_HTML_MODE_COMPACT));
about_license.setText(Html.fromHtml(getString(R.string.about_license), Html.FROM_HTML_MODE_COMPACT));

}else {
about_developer.setText(Html.fromHtml(getString(R.string.about_developer)));
about_license.setText(Html.fromHtml(getString(R.string.about_license)));
}
}


@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}
}

+ 108
- 0
app/src/main/java/fr/gouv/etalab/mastodon/activities/LoginActivity.java View File

@@ -0,0 +1,108 @@
/* Copyright 2017 Thomas Schneider
*
* This file is a part of Mastodon Etalab for mastodon.etalab.gouv.fr
*
* 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.
*
* Mastodon Etalab 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 Thomas Schneider; if not,
* see <http://www.gnu.org/licenses>. */
package fr.gouv.etalab.mastodon.activities;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.View;
import android.widget.Button;
import android.widget.Toast;

import com.loopj.android.http.AsyncHttpResponseHandler;

import org.json.JSONException;
import org.json.JSONObject;

import java.util.HashMap;

import cz.msebera.android.httpclient.Header;
import fr.gouv.etalab.mastodon.client.OauthClient;
import fr.gouv.etalab.mastodon.helper.Helper;
import mastodon.etalab.gouv.fr.mastodon.R;


/**
* Created by Thomas on 23/04/2017.
* Login activity class which handles the connection
*/

public class LoginActivity extends AppCompatActivity {


@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_login);

final Button connectionButton = (Button) findViewById(R.id.login_button);
final Intent webviewIntent = new Intent(this, WebviewActivity.class);


connectionButton.setEnabled(false);

String action = "/api/v1/apps";
HashMap<String,String> parameters = new HashMap<>();
parameters.put(Helper.CLIENT_NAME, Helper.OAUTH_REDIRECT_HOST);
parameters.put(Helper.REDIRECT_URIS,"https://" + Helper.INSTANCE + Helper.REDIRECT_CONTENT);
parameters.put(Helper.SCOPES, Helper.OAUTH_SCOPES);
parameters.put(Helper.WEBSITE,"https://" + Helper.INSTANCE);
new OauthClient().post(action, parameters, new AsyncHttpResponseHandler() {
@Override
public void onSuccess(int statusCode, Header[] headers, byte[] responseBody) {
String response = new String(responseBody);
JSONObject resobj;
try {
resobj = new JSONObject(response);
String client_id = resobj.get(Helper.CLIENT_ID).toString();
String client_secret = resobj.get(Helper.CLIENT_SECRET).toString();

String id = resobj.get(Helper.ID).toString();
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.CLIENT_ID, client_id);
editor.putString(Helper.CLIENT_SECRET, client_secret);
editor.putString(Helper.ID, id);
editor.apply();
connectionButton.setEnabled(true);
} catch (JSONException e) {
e.printStackTrace();
}

}

@Override
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
error.printStackTrace();
Toast.makeText(LoginActivity.this,R.string.client_error, Toast.LENGTH_LONG).show();
}
});

connectionButton.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
startActivity(webviewIntent);
finish();
}
});

}




}

+ 424
- 0
app/src/main/java/fr/gouv/etalab/mastodon/activities/MainActivity.java View File

@@ -0,0 +1,424 @@
/* Copyright 2017 Thomas Schneider
*
* This file is a part of Mastodon Etalab for mastodon.etalab.gouv.fr
*
* 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.
*
* Mastodon Etalab 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 Thomas Schneider; if not,
* see <http://www.gnu.org/licenses>. */
package fr.gouv.etalab.mastodon.activities;

import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.annotation.NonNull;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.app.FragmentManager;
import android.util.Log;
import android.view.View;
import android.support.design.widget.NavigationView;
import android.support.v4.view.GravityCompat;
import android.support.v4.widget.DrawerLayout;
import android.support.v7.app.ActionBarDrawerToggle;
import android.support.v7.app.AppCompatActivity;
import android.support.v7.widget.Toolbar;
import android.view.Menu;
import android.view.MenuItem;
import android.view.inputmethod.InputMethodManager;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;

import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import com.nostra13.universalimageloader.core.display.RoundedBitmapDisplayer;

import java.io.File;
import java.util.HashMap;

import fr.gouv.etalab.mastodon.client.Entities.Account;
import fr.gouv.etalab.mastodon.fragments.DisplayAccountsFragment;
import fr.gouv.etalab.mastodon.fragments.DisplayNotificationsFragment;
import fr.gouv.etalab.mastodon.helper.Helper;
import fr.gouv.etalab.mastodon.interfaces.OnUpdateAccountInfoInterface;
import fr.gouv.etalab.mastodon.sqlite.Sqlite;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveAccountsAsyncTask;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveFeedsAsyncTask;
import fr.gouv.etalab.mastodon.asynctasks.UpdateAccountInfoAsyncTask;
import fr.gouv.etalab.mastodon.fragments.DisplayStatusFragment;
import fr.gouv.etalab.mastodon.fragments.TabLayoutSettingsFragment;
import fr.gouv.etalab.mastodon.sqlite.AccountDAO;
import mastodon.etalab.gouv.fr.mastodon.R;

import static fr.gouv.etalab.mastodon.helper.Helper.INTENT_ACTION;
import static fr.gouv.etalab.mastodon.helper.Helper.INTENT_NOTIFICATION;

public class MainActivity extends AppCompatActivity
implements NavigationView.OnNavigationItemSelectedListener, OnUpdateAccountInfoInterface {

private FloatingActionButton toot;
private boolean first = true;
private HashMap<String, String> tagTile = new HashMap<>();
private HashMap<String, Integer> tagItem = new HashMap<>();
private Toolbar toolbar;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

setContentView(R.layout.activity_main);

//Test if user is still log in
if( ! Helper.isLoggedIn(getApplicationContext())) {
//It is not, the user is redirected to the login page
Intent myIntent = new Intent(MainActivity.this, LoginActivity.class);
startActivity(myIntent);
finish();
return;
}
//Here, the user is authenticated
toolbar = (Toolbar) findViewById(R.id.toolbar);
setSupportActionBar(toolbar);

toot = (FloatingActionButton) findViewById(R.id.toot);
toot.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View view) {
Intent intent = new Intent(getApplicationContext(), TootActivity.class);
startActivity(intent);
}
});

final DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
ActionBarDrawerToggle toggle = new ActionBarDrawerToggle(
this, drawer, toolbar, R.string.navigation_drawer_open, R.string.navigation_drawer_close);
drawer.setDrawerListener(toggle);
toggle.syncState();

final NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setNavigationItemSelectedListener(this);


//Image loader configuration
ImageLoader imageLoader;
imageLoader = ImageLoader.getInstance();
File cacheDir = new File(getCacheDir(), getString(R.string.app_name));
ImageLoaderConfiguration configImg = new ImageLoaderConfiguration.Builder(this)
.threadPoolSize(5)
.threadPriority(Thread.MIN_PRIORITY + 3)
.denyCacheImageMultipleSizesInMemory()
.diskCache(new UnlimitedDiskCache(cacheDir))
.build();
imageLoader.init(configImg);
SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
DisplayImageOptions options = new DisplayImageOptions.Builder().displayer(new RoundedBitmapDisplayer(90)).cacheInMemory(false)
.cacheOnDisk(true).resetViewBeforeLoading(true).build();


View headerLayout = navigationView.getHeaderView(0);

SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
String prefKeyOauthTokenT = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null);
Account account = new AccountDAO(getApplicationContext(), db).getAccountByToken(prefKeyOauthTokenT);
ImageView profilePicture = (ImageView) headerLayout.findViewById(R.id.profilePicture);
TextView username = (TextView) headerLayout.findViewById(R.id.username);
TextView displayedName = (TextView) headerLayout.findViewById(R.id.displayedName);
TextView ownerStatus = (TextView) headerLayout.findViewById(R.id.owner_status);
TextView ownerFollowing = (TextView) headerLayout.findViewById(R.id.owner_following);
TextView ownerFollowers = (TextView) headerLayout.findViewById(R.id.owner_followers);

ownerStatus.setText(String.valueOf(account.getStatuses_count()));
ownerFollowers.setText(String.valueOf(account.getFollowers_count()));
ownerFollowing.setText(String.valueOf(account.getFollowing_count()));
username.setText(String.format("@%s",account.getUsername()));
displayedName.setText(account.getDisplay_name());
imageLoader.displayImage(account.getAvatar(), profilePicture, options);
if (savedInstanceState == null) {
navigationView.setCheckedItem(R.id.nav_home);
navigationView.getMenu().performIdentifierAction(R.id.nav_home, 0);

}
//Title and menu selection when back pressed
getSupportFragmentManager().addOnBackStackChangedListener(
new FragmentManager.OnBackStackChangedListener() {
public void onBackStackChanged() {
FragmentManager fm = getSupportFragmentManager();
if( fm != null && fm.getBackStackEntryCount() > 0) {
String fragmentTag = fm.getBackStackEntryAt(fm.getBackStackEntryCount() - 1).getName();
if( fragmentTag != null) {
if( tagTile.get(fragmentTag) != null)
setTitle(tagTile.get(fragmentTag));
if( tagItem.get(fragmentTag) != null) {
unCheckAllMenuItems(navigationView.getMenu());
if( navigationView.getMenu().findItem(tagItem.get(fragmentTag)) != null)
navigationView.getMenu().findItem(tagItem.get(fragmentTag)).setChecked(true);
}
}
}
}
});

}

private void unCheckAllMenuItems(@NonNull final Menu menu) {
int size = menu.size();
for (int i = 0; i < size; i++) {
final MenuItem item = menu.getItem(i);
if(item.hasSubMenu()) {
unCheckAllMenuItems(item.getSubMenu());
} else {
item.setChecked(false);
}
}
}


@Override
protected void onNewIntent(Intent intent) {
super.onNewIntent(intent);
if( intent == null || intent.getExtras() == null )
return;
Bundle extras = intent.getExtras();
if( extras.containsKey(INTENT_ACTION) ){
if (extras.getInt(INTENT_ACTION) == INTENT_NOTIFICATION){
final NavigationView navigationView = (NavigationView) findViewById(R.id.nav_view);
navigationView.setCheckedItem(R.id.nav_notification);
navigationView.getMenu().performIdentifierAction(R.id.nav_notification, 0);
}
}
intent.replaceExtras(new Bundle());
intent.setAction("");
intent.setData(null);
intent.setFlags(0);
}

@Override
public void onBackPressed() {
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
if (drawer.isDrawerOpen(GravityCompat.START)) {
drawer.closeDrawer(GravityCompat.START);
} else {
super.onBackPressed();
}

}

@Override
public boolean onCreateOptionsMenu(Menu menu) {
// Inflate the menu; this adds items to the action bar if it is present.
getMenuInflater().inflate(R.menu.main, menu);
return true;
}


@Override
public boolean onOptionsItemSelected(MenuItem item) {
// Handle action bar item clicks here. The action bar will
// automatically handle clicks on the Home/Up button, so long
// as you specify a parent activity in AndroidManifest.xml.
int id = item.getItemId();

//noinspection SimplifiableIfStatement
if(id == R.id.action_logout) {
Helper.logout(getApplicationContext());
Intent myIntent = new Intent(MainActivity.this, LoginActivity.class);
startActivity(myIntent);
finish();
return true;
}else if(id == R.id.action_about){
Intent intent = new Intent(getApplicationContext(), AboutActivity.class);
startActivity(intent);
}else if(id == R.id.action_search){

if( toolbar.getChildCount() > 0){
for(int i = 0 ; i < toolbar.getChildCount() ; i++){
if(toolbar.getChildAt(i) instanceof EditText){
//Nothing in the search bar
if( ((EditText) toolbar.getChildAt(i)).getText().toString().trim().equals("")){
toolbar.removeViewAt(i);
return true;
}else{
String searchTag = ((EditText) toolbar.getChildAt(i)).getText().toString();
toot.setVisibility(View.VISIBLE);
DisplayStatusFragment statusFragment = new DisplayStatusFragment();
Bundle bundle = new Bundle();
bundle.putSerializable("type", RetrieveFeedsAsyncTask.Type.TAG);
bundle.putString("tag", searchTag);
statusFragment.setArguments(bundle);
FragmentManager fragmentManager = getSupportFragmentManager();
fragmentManager.beginTransaction()
.replace(R.id.main_app_container, statusFragment).commit();
View view = this.getCurrentFocus();
//Hide keyboard
if (view != null) {
InputMethodManager imm = (InputMethodManager)getSystemService(Context.INPUT_METHOD_SERVICE);
imm.hideSoftInputFromWindow(view.getWindowToken(), 0);
}
return true;
}

}
}
//Open the search bar
EditText search = new EditText(getApplicationContext());
search.setSingleLine(true);
search.setLayoutParams( new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT,1.0f));
toolbar.addView(search);
search.requestFocus();
InputMethodManager imm = (InputMethodManager) getSystemService(Context.INPUT_METHOD_SERVICE);
imm.toggleSoftInput(InputMethodManager.SHOW_FORCED,0);
}

}

return super.onOptionsItemSelected(item);
}

@Override
public void onResume(){
super.onResume();
//Proceeds to update of the authenticated account
if(Helper.isLoggedIn(getApplicationContext()))
new UpdateAccountInfoAsyncTask(getApplicationContext(), null, MainActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}




@SuppressWarnings("StatementWithEmptyBody")
@Override
public boolean onNavigationItemSelected(@NonNull MenuItem item) {
// Handle navigation view item clicks here.
int id = item.getItemId();
//Remove the search bar
if( toolbar.getChildCount() > 0) {
for (int i = 0; i < toolbar.getChildCount(); i++) {
if (toolbar.getChildAt(i) instanceof EditText) {
toolbar.removeViewAt(i);
break;
}
}
}
DisplayStatusFragment statusFragment;
DisplayAccountsFragment accountsFragment;
Bundle bundle = new Bundle();
FragmentManager fragmentManager = getSupportFragmentManager();
String fragmentTag = null;
if (id == R.id.nav_home) {
toot.setVisibility(View.VISIBLE);
statusFragment = new DisplayStatusFragment();
bundle.putSerializable("type", RetrieveFeedsAsyncTask.Type.HOME);
statusFragment.setArguments(bundle);
fragmentTag = "HOME_TIMELINE";
if(! first)
fragmentManager.beginTransaction()
.replace(R.id.main_app_container, statusFragment, fragmentTag).addToBackStack(fragmentTag).commit();
else{
fragmentManager.beginTransaction()
.replace(R.id.main_app_container, statusFragment, fragmentTag).commit();
first = false;
}
} else if (id == R.id.nav_local) {
toot.setVisibility(View.VISIBLE);
statusFragment = new DisplayStatusFragment();
bundle.putSerializable("type", RetrieveFeedsAsyncTask.Type.LOCAL);
statusFragment.setArguments(bundle);
fragmentTag = "LOCAL_TIMELINE";
fragmentManager.beginTransaction()
.replace(R.id.main_app_container, statusFragment, fragmentTag).addToBackStack(fragmentTag).commit();

} else if (id == R.id.nav_global) {
toot.setVisibility(View.VISIBLE);
statusFragment = new DisplayStatusFragment();
bundle.putSerializable("type", RetrieveFeedsAsyncTask.Type.PUBLIC);
statusFragment.setArguments(bundle);
fragmentTag = "PUBLIC_TIMELINE";
fragmentManager.beginTransaction()
.replace(R.id.main_app_container, statusFragment, fragmentTag).addToBackStack(fragmentTag).commit();
} else if (id == R.id.nav_settings) {
toot.setVisibility(View.GONE);
TabLayoutSettingsFragment tabLayoutSettingsFragment= new TabLayoutSettingsFragment();
fragmentTag = "TABLAYOUT_SETTINGS";
fragmentManager.beginTransaction()
.replace(R.id.main_app_container, tabLayoutSettingsFragment, fragmentTag).addToBackStack(fragmentTag).commit();

} else if (id == R.id.nav_favorites) {
toot.setVisibility(View.GONE);
statusFragment = new DisplayStatusFragment();
bundle.putSerializable("type", RetrieveFeedsAsyncTask.Type.FAVOURITES);
statusFragment.setArguments(bundle);
fragmentTag = "FAVOURITES";
fragmentManager.beginTransaction()
.replace(R.id.main_app_container, statusFragment, fragmentTag).addToBackStack(fragmentTag).commit();
} else if (id == R.id.nav_blocked) {
toot.setVisibility(View.GONE);
accountsFragment = new DisplayAccountsFragment();
bundle.putSerializable("type", RetrieveAccountsAsyncTask.Type.BLOCKED);
accountsFragment.setArguments(bundle);
fragmentTag = "BLOCKS";
fragmentManager.beginTransaction()
.replace(R.id.main_app_container, accountsFragment, fragmentTag).addToBackStack(fragmentTag).commit();
}else if (id == R.id.nav_muted) {
toot.setVisibility(View.GONE);
accountsFragment = new DisplayAccountsFragment();
bundle.putSerializable("type", RetrieveAccountsAsyncTask.Type.MUTED);
accountsFragment.setArguments(bundle);
fragmentTag = "BLOCKS";
fragmentManager.beginTransaction()
.replace(R.id.main_app_container, accountsFragment, fragmentTag).addToBackStack(fragmentTag).commit();
}else if( id == R.id.nav_notification){
toot.setVisibility(View.GONE);
DisplayNotificationsFragment notificationsFragment = new DisplayNotificationsFragment();
fragmentTag = "NOTIFICATIONS";
fragmentManager.beginTransaction()
.replace(R.id.main_app_container, notificationsFragment, fragmentTag).addToBackStack(fragmentTag).commit();
}


setTitle(item.getTitle());
populateTitleWithTag(fragmentTag, item.getTitle().toString(), item.getItemId());
DrawerLayout drawer = (DrawerLayout) findViewById(R.id.drawer_layout);
drawer.closeDrawer(GravityCompat.START);
return true;
}

private void populateTitleWithTag(String tag, String title, int index){
if( tag == null)
return;
if ( tagTile.get(tag) == null)
tagTile.put(tag, title);
if ( tagItem.get(tag) == null)
tagItem.put(tag, index);
}

@Override
public void setTitle(CharSequence title) {
if( getSupportActionBar() != null )
getSupportActionBar().setTitle(title);
}

@Override
public void onUpdateAccountInfo(boolean error) {
if( error){
//It is not, the user is redirected to the login page
Helper.logout(getApplicationContext());
Intent myIntent = new Intent(MainActivity.this, LoginActivity.class);
startActivity(myIntent);
finish();
}
}
}

+ 37
- 0
app/src/main/java/fr/gouv/etalab/mastodon/activities/MainApplication.java View File

@@ -0,0 +1,37 @@
package fr.gouv.etalab.mastodon.activities;
/* Copyright 2017 Thomas Schneider
*
* This file is a part of Mastodon Etalab for mastodon.etalab.gouv.fr
*
* 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.
*
* Mastodon Etalab 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 Thomas Schneider; if not,
* see <http://www.gnu.org/licenses>. */
import android.app.Application;

import com.evernote.android.job.JobManager;

import fr.gouv.etalab.mastodon.jobs.ApplicationJob;
import fr.gouv.etalab.mastodon.jobs.NotificationsSyncJob;

/**
* Created by Thomas on 29/04/2017.
*/

public class MainApplication extends Application{


@Override
public void onCreate() {
super.onCreate();
JobManager.create(this).addJobCreator(new ApplicationJob());
JobManager.instance().getConfig().setVerbose(false);
NotificationsSyncJob.schedule(getApplicationContext(), false);
}
}

+ 307
- 0
app/src/main/java/fr/gouv/etalab/mastodon/activities/ShowAccountActivity.java View File

@@ -0,0 +1,307 @@
/* Copyright 2017 Thomas Schneider
*
* This file is a part of Mastodon Etalab for mastodon.etalab.gouv.fr
*
* 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.
*
* Mastodon Etalab 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 Thomas Schneider; if not,
* see <http://www.gnu.org/licenses>. */
package fr.gouv.etalab.mastodon.activities;

import android.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.design.widget.TabLayout;
import android.support.v4.app.Fragment;
import android.support.v4.app.FragmentManager;
import android.support.v4.app.FragmentStatePagerAdapter;
import android.support.v4.view.PagerAdapter;
import android.support.v4.view.ViewPager;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import android.view.View;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;

import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer;

import java.util.ArrayList;
import java.util.List;

import fr.gouv.etalab.mastodon.asynctasks.PostActionAsyncTask;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveAccountAsyncTask;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveAccountsAsyncTask;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveFeedsAsyncTask;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveRelationshipAsyncTask;
import fr.gouv.etalab.mastodon.client.API;
import fr.gouv.etalab.mastodon.client.Entities.Account;
import fr.gouv.etalab.mastodon.client.Entities.Status;
import fr.gouv.etalab.mastodon.drawers.StatusListAdapter;
import fr.gouv.etalab.mastodon.fragments.DisplayAccountsFragment;
import fr.gouv.etalab.mastodon.fragments.DisplayStatusFragment;
import fr.gouv.etalab.mastodon.helper.Helper;
import fr.gouv.etalab.mastodon.interfaces.OnPostActionInterface;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveAccountInterface;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveFeedsAccountInterface;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveRelationshipInterface;
import mastodon.etalab.gouv.fr.mastodon.R;
import fr.gouv.etalab.mastodon.client.Entities.Relationship;


/**
* Created by Thomas on 01/05/2017.
* Show account activity class
*/

public class ShowAccountActivity extends AppCompatActivity implements OnPostActionInterface, OnRetrieveAccountInterface, OnRetrieveFeedsAccountInterface, OnRetrieveRelationshipInterface {


private ImageLoader imageLoader;
private DisplayImageOptions options;
private List<Status> statuses;
private StatusListAdapter statusListAdapter;
private Button account_follow;

private static final int NUM_PAGES = 3;
private ViewPager mPager;
private String accountId;
private TabLayout tabLayout;



public enum action{
FOLLOW,
UNFOLLOW,
UNBLOCK,
NOTHING
}

private action doAction;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_show_account);

SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
imageLoader = ImageLoader.getInstance();
statuses = new ArrayList<>();
boolean isOnWifi = Helper.isOnWIFI(getApplicationContext());
int behaviorWithAttachments = sharedpreferences.getInt(Helper.SET_ATTACHMENT_ACTION, Helper.ATTACHMENT_ALWAYS);
statusListAdapter = new StatusListAdapter(getApplicationContext(), RetrieveFeedsAsyncTask.Type.USER, isOnWifi, behaviorWithAttachments, this.statuses);
options = new DisplayImageOptions.Builder().displayer(new SimpleBitmapDisplayer()).cacheInMemory(false)
.cacheOnDisk(true).resetViewBeforeLoading(true).build();
account_follow = (Button) findViewById(R.id.account_follow);
account_follow.setEnabled(false);
Bundle b = getIntent().getExtras();
if(b != null){
accountId = b.getString("accountId");
new RetrieveRelationshipAsyncTask(getApplicationContext(), accountId,ShowAccountActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new RetrieveAccountAsyncTask(getApplicationContext(),accountId, ShowAccountActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null);
if( accountId != null && accountId.equals(userId)){
account_follow.setVisibility(View.GONE);
}
}else{
Toast.makeText(this,R.string.toast_error_loading_account,Toast.LENGTH_LONG).show();
}
if( getSupportActionBar() != null)
getSupportActionBar().setDisplayHomeAsUpEnabled(true);


tabLayout = (TabLayout) findViewById(R.id.account_tabLayout);
tabLayout.addTab(tabLayout.newTab().setText(getString(R.string.status)));
tabLayout.addTab(tabLayout.newTab().setText(getString(R.string.following)));
tabLayout.addTab(tabLayout.newTab().setText(getString(R.string.followers)));

mPager = (ViewPager) findViewById(R.id.account_viewpager);
PagerAdapter mPagerAdapter = new ScreenSlidePagerAdapter(getSupportFragmentManager());
mPager.setAdapter(mPagerAdapter);

mPager.addOnPageChangeListener(new ViewPager.OnPageChangeListener() {
@Override
public void onPageScrolled(int position, float positionOffset, int positionOffsetPixels) {

}

@Override
public void onPageSelected(int position) {
TabLayout.Tab tab = tabLayout.getTabAt(position);
if( tab != null)
tab.select();
}

@Override
public void onPageScrollStateChanged(int state) {

}
});

tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
mPager.setCurrentItem(tab.getPosition());
}

@Override
public void onTabUnselected(TabLayout.Tab tab) {

}

@Override
public void onTabReselected(TabLayout.Tab tab) {

}
});
//Follow button
account_follow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if( doAction == action.FOLLOW){
account_follow.setEnabled(false);
new PostActionAsyncTask(getApplicationContext(), API.StatusAction.FOLLOW, accountId, ShowAccountActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}else if( doAction == action.UNFOLLOW){
account_follow.setEnabled(false);
new PostActionAsyncTask(getApplicationContext(), API.StatusAction.UNFOLLOW, accountId, ShowAccountActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}else if( doAction == action.UNBLOCK){
account_follow.setEnabled(false);
new PostActionAsyncTask(getApplicationContext(), API.StatusAction.UNBLOCK, accountId, ShowAccountActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
});
}


@Override
public void onPostAction(int statusCode,API.StatusAction statusAction, String targetedId) {
Helper.manageMessageStatusCode(getApplicationContext(), statusCode, statusAction);
new RetrieveRelationshipAsyncTask(getApplicationContext(), accountId,ShowAccountActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}

@Override
public void onRetrieveAccount(Account account) {
ImageView account_pp = (ImageView) findViewById(R.id.account_pp);
TextView account_dn = (TextView) findViewById(R.id.account_dn);
TextView account_un = (TextView) findViewById(R.id.account_un);
TextView account_ac = (TextView) findViewById(R.id.account_ac);

if( account != null){
setTitle(account.getAcct());
account_dn.setText(account.getDisplay_name());
account_un.setText(account.getUsername());
if( account.getAcct().equals(account.getUsername()))
account_ac.setVisibility(View.GONE);
else
account_ac.setText(account.getAcct());
tabLayout.getTabAt(0).setText(getString(R.string.status) + "\n" + String.valueOf(account.getStatuses_count()));
tabLayout.getTabAt(1).setText(getString(R.string.following) + "\n" + String.valueOf(account.getFollowing_count()));
tabLayout.getTabAt(2).setText(getString(R.string.followers) + "\n" + String.valueOf(account.getFollowers_count()));
imageLoader.displayImage(account.getAvatar(), account_pp, options);
}
}

@Override
public void onRetrieveFeedsAccount(List<Status> statuses) {
if( statuses != null) {
for(Status tmpStatus: statuses){
this.statuses.add(tmpStatus);
}
statusListAdapter.notifyDataSetChanged();
}
}

@Override
public void onRetrieveRelationship(Relationship relationship) {
if( relationship.isBlocking()){
account_follow.setText(R.string.action_unblock);
doAction = action.UNBLOCK;
}else if( relationship.isRequested()){
account_follow.setText(R.string.request_sent);
doAction = action.NOTHING;
}else if( relationship.isFollowing()){
account_follow.setText(R.string.action_unfollow);
doAction = action.UNFOLLOW;
}else if( !relationship.isFollowing()){
account_follow.setText(R.string.action_follow);
doAction = action.FOLLOW;
}else{
doAction = action.NOTHING;
}
if( doAction == action.NOTHING){
account_follow.setEnabled(false);
}else {
account_follow.setEnabled(true);
}

//The authenticated account is followed by the account
if( relationship.isFollowed_by()){
TextView account_followed_by = (TextView) findViewById(R.id.account_followed_by);
account_followed_by.setVisibility(View.VISIBLE);
}

}


/**
* Pager adapter for the 3 fragments
*/
private class ScreenSlidePagerAdapter extends FragmentStatePagerAdapter {
ScreenSlidePagerAdapter(FragmentManager fm) {
super(fm);
}

@Override
public Fragment getItem(int position) {
Bundle bundle = new Bundle();
switch (position){
case 0:
DisplayStatusFragment displayStatusFragment = new DisplayStatusFragment();
bundle.putSerializable("type", RetrieveFeedsAsyncTask.Type.USER);
bundle.putString("targetedId", accountId);
displayStatusFragment.setArguments(bundle);
return displayStatusFragment;
case 1:
DisplayAccountsFragment displayAccountsFragment = new DisplayAccountsFragment();
bundle.putSerializable("type", RetrieveAccountsAsyncTask.Type.FOLLOWING);
bundle.putString("targetedId", accountId);
displayAccountsFragment.setArguments(bundle);
return displayAccountsFragment;
case 2:
displayAccountsFragment = new DisplayAccountsFragment();
bundle.putSerializable("type", RetrieveAccountsAsyncTask.Type.FOLLOWERS);
bundle.putString("targetedId", accountId);
displayAccountsFragment.setArguments(bundle);
return displayAccountsFragment;
}
return null;
}

@Override
public int getCount() {
return NUM_PAGES;
}
}

}

+ 116
- 0
app/src/main/java/fr/gouv/etalab/mastodon/activities/ShowConversationActivity.java View File

@@ -0,0 +1,116 @@
/* Copyright 2017 Thomas Schneider
*
* This file is a part of Mastodon Etalab for mastodon.etalab.gouv.fr
*
* 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.
*
* Mastodon Etalab 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 Thomas Schneider; if not,
* see <http://www.gnu.org/licenses>. */
package fr.gouv.etalab.mastodon.activities;


import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;
import android.view.MenuItem;
import android.view.View;
import android.widget.ListView;
import android.widget.RelativeLayout;

import java.util.ArrayList;
import java.util.List;

import fr.gouv.etalab.mastodon.asynctasks.RetrieveContextAsyncTask;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveFeedsAsyncTask;
import fr.gouv.etalab.mastodon.client.Entities.Context;
import fr.gouv.etalab.mastodon.client.Entities.Status;
import fr.gouv.etalab.mastodon.drawers.StatusListAdapter;
import fr.gouv.etalab.mastodon.helper.Helper;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveContextInterface;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveFeedsInterface;
import mastodon.etalab.gouv.fr.mastodon.R;


/**
* Created by Thomas on 04/05/2017.
* Show conversation activity class
*/

public class ShowConversationActivity extends AppCompatActivity implements OnRetrieveFeedsInterface, OnRetrieveContextInterface {


private String statusId;
private Status initialStatus;
public static int position;

@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_show_conversation);

if( getSupportActionBar() != null)
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
Bundle b = getIntent().getExtras();
if(b != null)
statusId = b.getString("statusId", null);
if( statusId == null)
finish();
setTitle(R.string.conversation);
new RetrieveFeedsAsyncTask(getApplicationContext(), RetrieveFeedsAsyncTask.Type.ONESTATUS, statusId,null, ShowConversationActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}

@Override
public void onRetrieveFeeds(List<Status> statuses) {
if( statuses != null && statuses.size() > 0 ){
initialStatus = statuses.get(0);
new RetrieveContextAsyncTask(getApplicationContext(), initialStatus.getId(), ShowConversationActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}

@Override
public void onRetrieveFeeds(Context context) {
boolean isOnWifi = Helper.isOnWIFI(getApplicationContext());
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, android.content.Context.MODE_PRIVATE);
int behaviorWithAttachments = sharedpreferences.getInt(Helper.SET_ATTACHMENT_ACTION, Helper.ATTACHMENT_ALWAYS);
position = 0;
List<Status> statuses = new ArrayList<>();
if( context.getAncestors() != null && context.getAncestors().size() > 0){
for(Status status: context.getAncestors()){
statuses.add(status);
position++;
}
}
statuses.add(initialStatus);
if( context.getDescendants() != null && context.getDescendants().size() > 0){
for(Status status: context.getDescendants()){
statuses.add(status);
}
}
RelativeLayout loader = (RelativeLayout) findViewById(R.id.loader);
ListView lv_status = (ListView) findViewById(R.id.lv_status);
StatusListAdapter statusListAdapter = new StatusListAdapter(ShowConversationActivity.this, RetrieveFeedsAsyncTask.Type.CONTEXT, isOnWifi, behaviorWithAttachments, statuses);
lv_status.setAdapter(statusListAdapter);
statusListAdapter.notifyDataSetChanged();
loader.setVisibility(View.GONE);
lv_status.setVisibility(View.VISIBLE);
lv_status.smoothScrollToPosition(position);
}
}

+ 36
- 0
app/src/main/java/fr/gouv/etalab/mastodon/activities/SplashActivity.java View File

@@ -0,0 +1,36 @@
/* Copyright 2017 Thomas Schneider
*
* This file is a part of Mastodon Etalab for mastodon.etalab.gouv.fr
*
* 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.
*
* Mastodon Etalab 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 Thomas Schneider; if not,
* see <http://www.gnu.org/licenses>. */
package fr.gouv.etalab.mastodon.activities;

import android.content.Intent;
import android.os.Bundle;
import android.support.v7.app.AppCompatActivity;


/**
* Created by Thomas on 24/12/2016.
* splash screen activity
*/

public class SplashActivity extends AppCompatActivity {
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);

Intent intent = new Intent(this, MainActivity.class);
startActivity(intent);
finish();
}
}

+ 529
- 0
app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java View File

@@ -0,0 +1,529 @@
/* Copyright 2017 Thomas Schneider
*
* This file is a part of Mastodon Etalab for mastodon.etalab.gouv.fr
*
* 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.
*
* Mastodon Etalab 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 Thomas Schneider; if not,
* see <http://www.gnu.org/licenses>. */
package fr.gouv.etalab.mastodon.activities;

import android.app.Activity;
import android.app.AlertDialog;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.support.v4.content.LocalBroadcastManager;
import android.support.v7.app.AppCompatActivity;
import android.text.Editable;
import android.text.Html;
import android.text.TextWatcher;
import android.util.Log;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.ArrayAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageButton;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.Toast;
import android.widget.VideoView;

import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer;

import java.io.FileNotFoundException;
import java.io.InputStream;
import java.util.ArrayList;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;

import fr.gouv.etalab.mastodon.asynctasks.PostActionAsyncTask;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveAccountsAsyncTask;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveFeedsAsyncTask;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveSearchAsyncTask;
import fr.gouv.etalab.mastodon.asynctasks.UploadActionAsyncTask;
import fr.gouv.etalab.mastodon.client.API;
import fr.gouv.etalab.mastodon.client.Entities.Account;
import fr.gouv.etalab.mastodon.client.Entities.Attachment;
import fr.gouv.etalab.mastodon.client.Entities.Results;
import fr.gouv.etalab.mastodon.client.Entities.Status;
import fr.gouv.etalab.mastodon.drawers.AccountsSearchAdapter;
import fr.gouv.etalab.mastodon.helper.Helper;
import fr.gouv.etalab.mastodon.interfaces.OnPostActionInterface;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveAccountsInterface;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveAttachmentInterface;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveFeedsInterface;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveSearchInterface;
import fr.gouv.etalab.mastodon.sqlite.AccountDAO;
import fr.gouv.etalab.mastodon.sqlite.Sqlite;
import mastodon.etalab.gouv.fr.mastodon.R;


/**
* Created by Thomas on 01/05/2017.
* Toot activity class
*/

public class TootActivity extends AppCompatActivity implements OnRetrieveSearchInterface, OnRetrieveAttachmentInterface, OnPostActionInterface, OnRetrieveFeedsInterface {


private String inReplyTo = null;
private int charsInCw;
private int charsInToot;
private int maxChar;
private String visibility;
private final int PICK_IMAGE = 56556;
private RelativeLayout loading_picture;
private ImageButton toot_picture;
private ImageLoader imageLoader;
private DisplayImageOptions options;
private LinearLayout toot_picture_container;
private List<Attachment> attachments;
private ImageButton toot_visibility;
private Button toot_it;
private EditText toot_content, toot_cw_content;
private LinearLayout toot_reply_content_container;
private TextView toot_reply_content;
private RelativeLayout toot_show_accounts;
private ListView toot_lv_accounts;
private BroadcastReceiver search_validate;

private String pattern = "^.*(@([a-zA-Z0-9_]{2,}))$";
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_toot);

if( getSupportActionBar() != null)
getSupportActionBar().setDisplayHomeAsUpEnabled(true);


Bundle b = getIntent().getExtras();
if(b != null)
inReplyTo = b.getString("inReplyTo", null);
if( inReplyTo != null) {
setTitle(R.string.toot_title_reply);
new RetrieveFeedsAsyncTask(getApplicationContext(), RetrieveFeedsAsyncTask.Type.ONESTATUS, inReplyTo,null, TootActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}else {
setTitle(R.string.toot_title);
}
attachments = new ArrayList<>();
charsInCw = 0;
charsInToot = 0;
maxChar = 500;

//Register LocalBroadcast to receive selected accounts after search
search_validate = new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String acct = intent.getStringExtra("acct");
if( acct != null){
acct = "@" + acct;
String content = toot_content.getText().toString();
String[] splitContent = content.split("@");
String newContent = "";
for(int i = 0 ; i < (splitContent.length -1) ; i++){
newContent += splitContent[i];
}
newContent += acct + " ";
toot_content.setText(newContent);
toot_content.setSelection(toot_content.getText().length());
}
toot_show_accounts.setVisibility(View.GONE);
}
};
LocalBroadcastManager.getInstance(this).registerReceiver(search_validate, new IntentFilter(Helper.SEARCH_VALIDATE_ACCOUNT));

toot_it = (Button) findViewById(R.id.toot_it);
Button toot_cw = (Button) findViewById(R.id.toot_cw);
final TextView toot_space_left = (TextView) findViewById(R.id.toot_space_left);
toot_visibility = (ImageButton) findViewById(R.id.toot_visibility);
toot_picture = (ImageButton) findViewById(R.id.toot_picture);
loading_picture = (RelativeLayout) findViewById(R.id.loading_picture);
toot_picture_container = (LinearLayout) findViewById(R.id.toot_picture_container);
toot_content = (EditText) findViewById(R.id.toot_content);
toot_cw_content = (EditText) findViewById(R.id.toot_cw_content);
toot_reply_content = (TextView) findViewById(R.id.toot_reply_content);
toot_reply_content_container = (LinearLayout) findViewById(R.id.toot_reply_content_container);
toot_show_accounts = (RelativeLayout) findViewById(R.id.toot_show_accounts);
toot_lv_accounts = (ListView) findViewById(R.id.toot_lv_accounts);
FloatingActionButton toot_close_accounts = (FloatingActionButton) findViewById(R.id.toot_close_accounts);
SQLiteDatabase db = Sqlite.getInstance(getApplicationContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null);
Account account = new AccountDAO(getApplicationContext(),db).getAccountByID(userId);
boolean isAccountPrivate = account.isLocked();


FloatingActionButton ic_close = (FloatingActionButton) findViewById(R.id.toot_close_reply);

toot_close_accounts.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
toot_show_accounts.setVisibility(View.GONE);
}
});

ic_close.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
toot_reply_content_container.setVisibility(View.GONE);
}
});

if(isAccountPrivate){
visibility = "private";
toot_visibility.setImageResource(R.drawable.ic_action_lock_closed);
}else {
visibility = "public";
toot_visibility.setImageResource(R.drawable.ic_action_globe);
}




toot_space_left.setText(String.valueOf((maxChar - (charsInToot + charsInCw))));
toot_cw.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if(toot_cw_content.getVisibility() == View.GONE) {
toot_cw_content.setVisibility(View.VISIBLE);
toot_cw_content.requestFocus();
}else {
toot_cw_content.setVisibility(View.GONE);
toot_cw_content.setText("");
toot_content.requestFocus();
}
}
});

toot_visibility.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
tootVisibilityDialog();
}
});
imageLoader = ImageLoader.getInstance();
options = new DisplayImageOptions.Builder().displayer(new SimpleBitmapDisplayer()).cacheInMemory(false)
.cacheOnDisk(true).resetViewBeforeLoading(true).build();

toot_it.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
toot_it.setEnabled(false);
if(toot_content.getText().toString().trim().length() == 0){
Toast.makeText(getApplicationContext(),R.string.toot_error_no_content, Toast.LENGTH_LONG).show();
toot_it.setEnabled(true);
return;
}
Status toot = new Status();
toot.setMedia_attachments(attachments);
if( toot_cw_content.getText().toString().trim().length() > 0)
toot.setSpoiler_text(toot_cw_content.getText().toString().trim());
toot.setVisibility(visibility);
toot.setContent(toot_content.getText().toString().trim());
if( inReplyTo != null)
toot.setIn_reply_to_id(inReplyTo);
new PostActionAsyncTask(getApplicationContext(), API.StatusAction.CREATESTATUS, null, toot, null, TootActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);

}
});

toot_picture.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent getIntent = new Intent(Intent.ACTION_GET_CONTENT);
getIntent.setType("image/*");

Intent pickIntent = new Intent(Intent.ACTION_PICK, android.provider.MediaStore.Images.Media.EXTERNAL_CONTENT_URI);
pickIntent.setType("image/*");

Intent chooserIntent = Intent.createChooser(getIntent, getString(R.string.toot_select_image));
chooserIntent.putExtra(Intent.EXTRA_INITIAL_INTENTS, new Intent[] {pickIntent});
startActivityForResult(chooserIntent, PICK_IMAGE);
}
});

toot_content.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {

Pattern sPattern = Pattern.compile(pattern);
Matcher m = sPattern.matcher(s.toString());
if(m.matches()) {
String search = m.group(2);
new RetrieveSearchAsyncTask(getApplicationContext(),search,TootActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}else{
toot_show_accounts.setVisibility(View.GONE);
}
if( s.length() + charsInCw > maxChar){
String content = s.toString().substring(0,(maxChar - charsInCw));
toot_content.setText(content);
charsInToot = content.length();
toot_content.setSelection(toot_content.getText().length());
Toast.makeText(getApplicationContext(),R.string.toot_no_space,Toast.LENGTH_LONG).show();
}
int totalChar = toot_cw_content.length() + toot_content.length();
toot_space_left.setText(String.valueOf((maxChar - totalChar)));
}
});

toot_cw_content.addTextChangedListener(new TextWatcher() {
@Override
public void beforeTextChanged(CharSequence s, int start, int count, int after) {}
@Override
public void onTextChanged(CharSequence s, int start, int before, int count) {}
@Override
public void afterTextChanged(Editable s) {
if( s.length() + charsInToot > maxChar){
String content = s.toString().substring(0,(maxChar - charsInToot));
toot_cw_content.setText(content);
toot_cw_content.setSelection(toot_cw_content.getText().length());
Toast.makeText(getApplicationContext(),R.string.toot_no_space,Toast.LENGTH_LONG).show();
}
int totalChar = toot_cw_content.length() + toot_content.length();
toot_space_left.setText(String.valueOf((maxChar - totalChar)));
}
});

}



@Override
public void onActivityResult(int requestCode, int resultCode, Intent data) {
super.onActivityResult(requestCode, resultCode, data);
if (requestCode == PICK_IMAGE && resultCode == Activity.RESULT_OK) {
if (data == null) {
Toast.makeText(getApplicationContext(),R.string.toot_select_image_error,Toast.LENGTH_LONG).show();
return;
}
try {

InputStream inputStream = getContentResolver().openInputStream(data.getData());
loading_picture.setVisibility(View.VISIBLE);
toot_picture.setEnabled(false);
new UploadActionAsyncTask(getApplicationContext(),inputStream,TootActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} catch (FileNotFoundException e) {
Toast.makeText(getApplicationContext(),R.string.toot_select_image_error,Toast.LENGTH_LONG).show();
loading_picture.setVisibility(View.GONE);
toot_picture.setEnabled(true);
e.printStackTrace();
}
}
}

@Override
public boolean onOptionsItemSelected(MenuItem item) {
switch (item.getItemId()) {
case android.R.id.home:
finish();
return true;
default:
return super.onOptionsItemSelected(item);
}
}

@Override
public void onDestroy(){
super.onDestroy();
LocalBroadcastManager.getInstance(this).unregisterReceiver(search_validate);
}
@Override
public void onRetrieveAttachment(final Attachment attachment) {
loading_picture.setVisibility(View.GONE);
toot_picture_container.setVisibility(View.VISIBLE);
if( attachment != null ){
String url = attachment.getPreview_url();
if( url == null || url.trim().equals(""))
url = attachment.getUrl();

final ImageView imageView = new ImageView(getApplicationContext());
imageView.setId(Integer.parseInt(attachment.getId()));
LinearLayout.LayoutParams imParams = new LinearLayout.LayoutParams(LinearLayout.LayoutParams.MATCH_PARENT, LinearLayout.LayoutParams.WRAP_CONTENT);
imParams.setMargins(20, 5, 20, 5);
imageLoader.displayImage(url, imageView, options);
imageView.setAdjustViewBounds(true);
imageView.setScaleType(ImageView.ScaleType.FIT_XY);
toot_picture_container.addView(imageView, imParams);
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showRemove(imageView.getId());
}
});
attachments.add(attachment);
if( attachments.size() < 4)
toot_picture.setEnabled(true);
}
}


/**
* Removes a media
* @param viewId String
*/
private void showRemove(final int viewId){

AlertDialog.Builder dialog = new AlertDialog.Builder(TootActivity.this);

dialog.setMessage(R.string.toot_delete_media);
dialog.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,int which) {
dialog.dismiss();
}
});
dialog.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,int which) {
List<Attachment> tmp_attachment = new ArrayList<>();
tmp_attachment.addAll(attachments);
attachments.removeAll(tmp_attachment);
tmp_attachment.clear();
View namebar = findViewById(viewId);
((ViewGroup) namebar.getParent()).removeView(namebar);
dialog.dismiss();
}
});
dialog.show();

}


private void tootVisibilityDialog(){


AlertDialog.Builder dialog = new AlertDialog.Builder(TootActivity.this);
dialog.setTitle(R.string.toot_visibility_tilte);
final String[] stringArray = getResources().getStringArray(R.array.toot_visibility);
final ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(TootActivity.this, android.R.layout.simple_list_item_1, stringArray);
dialog.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int position) {
dialog.dismiss();
}
});
dialog.setAdapter(arrayAdapter, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int position) {
switch (position){
case 0:
visibility = "public";
toot_visibility.setImageResource(R.drawable.ic_action_globe);
break;
case 1:
visibility = "unlisted";
toot_visibility.setImageResource(R.drawable.ic_action_lock_open);
break;
case 2:
visibility = "private";
toot_visibility.setImageResource(R.drawable.ic_action_lock_closed);
break;
case 3:
visibility = "direct";
toot_visibility.setImageResource(R.drawable.ic_local_post_office);
break;
}
dialog.dismiss();
}
});
dialog.show();
}

@Override