initial commit

This commit is contained in:
tom79 2017-05-05 16:36:04 +02:00
commit c4ea4943d6
336 changed files with 11461 additions and 0 deletions

9
.gitignore vendored Normal file
View File

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

22
.idea/compiler.xml Normal file
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>

View File

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

18
.idea/gradle.xml Normal file
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>

View File

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

129
.idea/misc.xml Normal file
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
.idea/modules.xml Normal file
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>

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
.idea/vcs.xml Normal file
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
app/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
/build

37
app/build.gradle Normal file
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'
}

Binary file not shown.

Binary file not shown.

Binary file not shown.

25
app/proguard-rules.pro vendored Normal file
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

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());
}
}

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>

Binary file not shown.

After

Width:  |  Height:  |  Size: 52 KiB

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);
}
}
}

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();
}
});
}
}

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();
}
}
}

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);
}
}

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;
}
}
}

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);
}
}

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();
}
}

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
public void onPostAction(int statusCode, API.StatusAction statusAction, String userId) {
if( statusCode == 200){
//Clear the toot
toot_content.setText("");
toot_cw_content.setText("");
if( attachments != null) {
for (Attachment attachment : attachments) {
View namebar = findViewById(Integer.parseInt(attachment.getId()));
if (namebar != null && namebar.getParent() != null)
((ViewGroup) namebar.getParent()).removeView(namebar);
}
List<Attachment> tmp_attachment = new ArrayList<>();
tmp_attachment.addAll(attachments);
attachments.removeAll(tmp_attachment);
tmp_attachment.clear();
}
Toast.makeText(TootActivity.this,R.string.toot_sent, Toast.LENGTH_LONG).show();
}else {
Toast.makeText(TootActivity.this,R.string.toast_error, Toast.LENGTH_LONG).show();
}
toot_it.setEnabled(true);
}
@Override
public void onRetrieveFeeds(List<Status> statuses) {
if( statuses != null && statuses.size() > 0 ){
toot_reply_content_container.setVisibility(View.VISIBLE);
String content = statuses.get(0).getContent();
if(statuses.get(0).isReblogged())
content = statuses.get(0).getReblog().getContent();
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
toot_reply_content.setText(Html.fromHtml(content, Html.FROM_HTML_MODE_COMPACT));
else
//noinspection deprecation
toot_reply_content.setText(Html.fromHtml(content));
switch (statuses.get(0).getVisibility()){
case "public":
visibility = "public";
toot_visibility.setImageResource(R.drawable.ic_action_globe);
break;
case "unlisted":
visibility = "unlisted";
toot_visibility.setImageResource(R.drawable.ic_action_lock_open);
break;
case "private":
visibility = "private";
toot_visibility.setImageResource(R.drawable.ic_action_lock_closed);
break;
case "direct":
visibility = "direct";
toot_visibility.setImageResource(R.drawable.ic_local_post_office);
break;
}
}
}
@Override
public void onRetrieveSearch(Results results) {
if( results != null && results.getAccounts() != null && results.getAccounts().size() > 0){
AccountsSearchAdapter accountsListAdapter = new AccountsSearchAdapter(TootActivity.this, results.getAccounts());
toot_lv_accounts.setAdapter(accountsListAdapter);
accountsListAdapter.notifyDataSetChanged();
toot_show_accounts.setVisibility(View.VISIBLE);
}
}
}

View File

@ -0,0 +1,195 @@
/* 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.content.Context;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.v7.app.AlertDialog;
import android.support.v7.app.AppCompatActivity;
import android.webkit.CookieManager;
import android.webkit.CookieSyncManager;
import android.webkit.WebChromeClient;
import android.webkit.WebView;
import android.webkit.WebViewClient;
import android.widget.ProgressBar;
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 mastodon.etalab.gouv.fr.mastodon.R;
import fr.gouv.etalab.mastodon.asynctasks.UpdateAccountInfoAsyncTask;
import fr.gouv.etalab.mastodon.client.OauthClient;
import fr.gouv.etalab.mastodon.helper.Helper;
/**
* Created by Thomas on 24/04/2017.
* Webview to connect accounts
*/
public class WebviewActivity extends AppCompatActivity {
private Activity activity;
private WebView webView;
private Context context;
private AlertDialog alert;
private String clientId, clientSecret;
public void onCreate(Bundle savedInstanceState)
{
super.onCreate(savedInstanceState);
setContentView(R.layout.activity_webview);
this.activity = this;
this.context = this;
this.context = this.getApplicationContext();
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
clientId = sharedpreferences.getString(Helper.CLIENT_ID, null);
clientSecret = sharedpreferences.getString(Helper.CLIENT_SECRET, null);
webView = (WebView) findViewById(R.id.webviewConnect);
clearCookies(getApplicationContext());
final ProgressBar pbar = (ProgressBar) findViewById(R.id.progress_bar);
webView.setWebChromeClient(new WebChromeClient() {
@Override
public void onProgressChanged(WebView view, int progress) {
if (progress < 100 && pbar.getVisibility() == ProgressBar.GONE) {
pbar.setVisibility(ProgressBar.VISIBLE);
}
pbar.setProgress(progress);
if (progress == 100) {
pbar.setVisibility(ProgressBar.GONE);
}
}
});
webView.setWebViewClient(new WebViewClient() {
@SuppressWarnings("deprecation")
@Override
public boolean shouldOverrideUrlLoading(WebView view, String url){
super.shouldOverrideUrlLoading(view,url);
if( url.contains(Helper.REDIRECT_CONTENT)){
String val[] = url.split("code=");
String code = val[1];
String action = "/oauth/token";
HashMap<String,String> parameters = new HashMap<>();
parameters.put(Helper.CLIENT_ID, clientId);
parameters.put(Helper.CLIENT_SECRET, clientSecret);
parameters.put(Helper.REDIRECT_URI,"https://" + Helper.INSTANCE + Helper.REDIRECT_CONTENT);
parameters.put("grant_type", "authorization_code");
parameters.put("code",code);
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 token = resobj.get("access_token").toString();
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.PREF_KEY_OAUTH_TOKEN, token);
editor.apply();
//Update the account with the token;
new UpdateAccountInfoAsyncTask(WebviewActivity.this, true, token).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
} catch (JSONException e) {
e.printStackTrace();
}
}
@Override
public void onFailure(int statusCode, Header[] headers, byte[] responseBody, Throwable error) {
error.printStackTrace();
}
});
return true;
}
return false;
}
});
webView.loadUrl(redirectUserToAuthorizeAndLogin());
}
@Override
public void onBackPressed() {
if (webView != null && webView.canGoBack()) {
webView.goBack();
} else {
super.onBackPressed();
}
}
private String redirectUserToAuthorizeAndLogin() {
String queryString = Helper.CLIENT_ID + "="+ clientId;
queryString += "&" + Helper.REDIRECT_URI + "="+ Uri.encode("https://" + Helper.INSTANCE + "/redirect_mastodon_api");
queryString += "&" + Helper.RESPONSE_TYPE +"=code";
queryString += "&" + Helper.SCOPE +"=" + Helper.OAUTH_SCOPES;
/*try {
queryString = URLEncoder.encode(queryString, "utf-8");
} catch (UnsupportedEncodingException ignored) {}*/
return "https://" + Helper.INSTANCE + Helper.EP_AUTHORIZE + "?" + queryString;
}
private String getOauthRedirectUri() {
return Helper.OAUTH_SCHEME + "://" + Helper.OAUTH_REDIRECT_HOST + "/";
}
@Override
public void onDestroy() {
super.onDestroy();
if (alert != null) {
alert.dismiss();
alert = null;
}
}
@SuppressWarnings("deprecation")
public static void clearCookies(Context context)
{
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.LOLLIPOP_MR1) {
CookieManager.getInstance().removeAllCookies(null);
CookieManager.getInstance().flush();
} else
{
CookieSyncManager cookieSyncMngr=CookieSyncManager.createInstance(context);
cookieSyncMngr.startSync();
CookieManager cookieManager=CookieManager.getInstance();
cookieManager.removeAllCookie();
cookieManager.removeSessionCookie();
cookieSyncMngr.stopSync();
cookieSyncMngr.sync();
}
}
}

View File

@ -0,0 +1,74 @@
/* 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.asynctasks;
import android.content.Context;
import android.os.AsyncTask;
import fr.gouv.etalab.mastodon.client.API;
import fr.gouv.etalab.mastodon.client.Entities.Account;
import fr.gouv.etalab.mastodon.interfaces.OnPostActionInterface;
/**
* Created by Thomas on 29/04/2017.
* Makes actions for post calls
*/
public class PostActionAsyncTask extends AsyncTask<Void, Void, Void> {
private Context context;
private OnPostActionInterface listener;
private int statusCode;
private API.StatusAction statusAction;
private String statusId;
private String comment;
private Account account;
private fr.gouv.etalab.mastodon.client.Entities.Status status;
public PostActionAsyncTask(Context context, API.StatusAction statusAction, String statusId, OnPostActionInterface onPostActionInterface){
this.context = context;
this.listener = onPostActionInterface;
this.statusAction = statusAction;
this.statusId = statusId;
}
public PostActionAsyncTask(Context context, API.StatusAction statusAction, String statusId, fr.gouv.etalab.mastodon.client.Entities.Status status, String comment, OnPostActionInterface onPostActionInterface){
this.context = context;
this.listener = onPostActionInterface;
this.statusAction = statusAction;
this.statusId = statusId;
this.comment = comment;
this.status = status;
}
@Override
protected Void doInBackground(Void... params) {
if(statusAction == API.StatusAction.REPORT)
statusCode = new API(context).reportAction(status, comment);
else if(statusAction == API.StatusAction.CREATESTATUS)
statusCode = new API(context).statusAction(status);
else
statusCode = new API(context).postAction(statusAction, statusId);
return null;
}
@Override
protected void onPostExecute(Void result) {
listener.onPostAction(statusCode, statusAction, statusId);
}
}

View File

@ -0,0 +1,56 @@
/* 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.asynctasks;
import android.content.Context;
import android.os.AsyncTask;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveAccountInterface;
import fr.gouv.etalab.mastodon.client.API;
import fr.gouv.etalab.mastodon.client.Entities.Account;
/**
* Created by Thomas on 27/04/2017.
* Retrieves accounts on the instance
*/
public class RetrieveAccountAsyncTask extends AsyncTask<Void, Void, Void> {
private Context context;
private String targetedId;
private Account account;
private OnRetrieveAccountInterface listener;
public RetrieveAccountAsyncTask(Context context, String targetedId, OnRetrieveAccountInterface onRetrieveAccountInterface){
this.context = context;
this.targetedId = targetedId;
this.listener = onRetrieveAccountInterface;
}
@Override
protected Void doInBackground(Void... params) {
account = new API(context).getAccount(targetedId);
return null;
}
@Override
protected void onPostExecute(Void result) {
listener.onRetrieveAccount(account);
}
}

View File

@ -0,0 +1,88 @@
/* 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.asynctasks;
import android.content.Context;
import android.os.AsyncTask;
import java.util.List;
import fr.gouv.etalab.mastodon.client.API;
import fr.gouv.etalab.mastodon.client.Entities.Account;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveAccountsInterface;
/**
* Created by Thomas on 27/04/2017.
* Retrieves accounts on the instance
*/
public class RetrieveAccountsAsyncTask extends AsyncTask<Void, Void, Void> {
private Context context;
private Type action;
private List<Account> accounts;
private String max_id;
private OnRetrieveAccountsInterface listener;
private String targetedId;
public enum Type{
BLOCKED,
MUTED,
FOLLOWING,
FOLLOWERS
}
public RetrieveAccountsAsyncTask(Context context, Type action, String targetedId, String max_id, OnRetrieveAccountsInterface onRetrieveAccountsInterface){
this.context = context;
this.action = action;
this.max_id = max_id;
this.listener = onRetrieveAccountsInterface;
this.targetedId = targetedId;
}
public RetrieveAccountsAsyncTask(Context context, Type action, String max_id, OnRetrieveAccountsInterface onRetrieveAccountsInterface){
this.context = context;
this.action = action;
this.max_id = max_id;
this.listener = onRetrieveAccountsInterface;
}
@Override
protected Void doInBackground(Void... params) {
switch (action){
case BLOCKED:
accounts = new API(context).getBlocks(max_id);
break;
case MUTED:
accounts = new API(context).getMuted(max_id);
break;
case FOLLOWING:
accounts = new API(context).getFollowing(targetedId, max_id);
break;
case FOLLOWERS:
accounts = new API(context).getFollowers(targetedId, max_id);
break;
}
return null;
}
@Override
protected void onPostExecute(Void result) {
listener.onRetrieveAccounts(accounts);
}
}

View File

@ -0,0 +1,53 @@
/* 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.asynctasks;
import android.content.Context;
import android.os.AsyncTask;
import fr.gouv.etalab.mastodon.client.API;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveContextInterface;
/**
* Created by Thomas on 23/04/2017.
* Retrieves context for a status
*/
public class RetrieveContextAsyncTask extends AsyncTask<Void, Void, Void> {
private Context context;
private String statusId;
private fr.gouv.etalab.mastodon.client.Entities.Context statusContext;
private OnRetrieveContextInterface listener;
public RetrieveContextAsyncTask(Context context, String statusId, OnRetrieveContextInterface onRetrieveContextInterface){
this.context = context;
this.statusId = statusId;
this.listener = onRetrieveContextInterface;
}
@Override
protected Void doInBackground(Void... params) {
statusContext = new API(context).getStatusContext(statusId);
return null;
}
@Override
protected void onPostExecute(Void result) {
listener.onRetrieveFeeds(statusContext);
}
}

View File

@ -0,0 +1,56 @@
/* 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.asynctasks;
import android.content.Context;
import android.os.AsyncTask;
import java.util.List;
import fr.gouv.etalab.mastodon.client.API;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveFeedsAccountInterface;
/**
* Created by Thomas on 01/05/2017.
* Retrieves toots for an account
*/
public class RetrieveFeedsAccountAsyncTask extends AsyncTask<Void, Void, Void> {
private Context context;
private String accountId;
private List<fr.gouv.etalab.mastodon.client.Entities.Status> statuses;
private OnRetrieveFeedsAccountInterface listener;
public RetrieveFeedsAccountAsyncTask(Context context, String accountId, OnRetrieveFeedsAccountInterface onRetrieveFeedsAccountInterface){
this.context = context;
this.listener = onRetrieveFeedsAccountInterface;
this.accountId = accountId;
}
@Override
protected Void doInBackground(Void... params) {
statuses = new API(context).getStatus(accountId);
return null;
}
@Override
protected void onPostExecute(Void result) {
listener.onRetrieveFeedsAccount(statuses);
}
}

View File

@ -0,0 +1,112 @@
/* 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.asynctasks;
import android.content.Context;
import android.os.AsyncTask;
import java.util.List;
import fr.gouv.etalab.mastodon.client.API;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveFeedsInterface;
/**
* Created by Thomas on 23/04/2017.
* Retrieves toots on the instance
*/
public class RetrieveFeedsAsyncTask extends AsyncTask<Void, Void, Void> {
private Context context;
private Type action;
private List<fr.gouv.etalab.mastodon.client.Entities.Status> statuses;
private String max_id;
private OnRetrieveFeedsInterface listener;
private String targetedID;
private fr.gouv.etalab.mastodon.client.Entities.Status status;
private String tag;
public enum Type{
HOME,
LOCAL,
PUBLIC,
HASHTAG,
USER,
FAVOURITES,
ONESTATUS,
CONTEXT,
TAG
}
public RetrieveFeedsAsyncTask(Context context, Type action, String max_id, OnRetrieveFeedsInterface onRetrieveFeedsInterface){
this.context = context;
this.action = action;
this.max_id = max_id;
this.listener = onRetrieveFeedsInterface;
}
public RetrieveFeedsAsyncTask(Context context, Type action, String targetedID, String max_id, OnRetrieveFeedsInterface onRetrieveFeedsInterface){
this.context = context;
this.action = action;
this.max_id = max_id;
this.listener = onRetrieveFeedsInterface;
this.targetedID = targetedID;
}
public RetrieveFeedsAsyncTask(Context context, Type action, String tag, String targetedID, String max_id, OnRetrieveFeedsInterface onRetrieveFeedsInterface){
this.context = context;
this.action = action;
this.max_id = max_id;
this.listener = onRetrieveFeedsInterface;
this.targetedID = targetedID;
this.tag = tag;
}
@Override
protected Void doInBackground(Void... params) {
switch (action){
case HOME:
statuses = new API(context).getHomeTimeline(max_id);
break;
case LOCAL:
statuses = new API(context).getPublicTimeline(true, max_id);
break;
case PUBLIC:
statuses = new API(context).getPublicTimeline(false, max_id);
break;
case FAVOURITES:
statuses = new API(context).getFavourites(max_id);
break;
case USER:
statuses = new API(context).getStatus(targetedID, max_id);
break;
case ONESTATUS:
statuses = new API(context).getStatusbyId(targetedID);
break;
case TAG:
statuses = new API(context).getPublicTimelineTag(tag, true, max_id);
break;
case HASHTAG:
break;
}
return null;
}
@Override
protected void onPostExecute(Void result) {
listener.onRetrieveFeeds(statuses);
}
}

View File

@ -0,0 +1,62 @@
/* 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.asynctasks;
import android.content.Context;
import android.os.AsyncTask;
import java.util.List;
import fr.gouv.etalab.mastodon.client.API;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveNotificationsInterface;
import fr.gouv.etalab.mastodon.client.Entities.Notification;
/**
* Created by Thomas on 28/04/2017.
* Retrieves notifications on the instance
*/
public class RetrieveNotificationsAsyncTask extends AsyncTask<Void, Void, Void> {
private Context context;
private List<Notification> notifications;
private String max_id;
private String acct;
private OnRetrieveNotificationsInterface listener;
public RetrieveNotificationsAsyncTask(Context context, String max_id, String acct, OnRetrieveNotificationsInterface onRetrieveNotificationsInterface){
this.context = context;
this.max_id = max_id;
this.listener = onRetrieveNotificationsInterface;
this.acct = acct;
}
@Override
protected Void doInBackground(Void... params) {
if( acct == null)
notifications = new API(context).getNotifications(max_id);
else
notifications = new API(context).getNotificationsSince(max_id);
return null;
}
@Override
protected void onPostExecute(Void result) {
listener.onRetrieveNotifications(notifications, acct);
}
}

View File

@ -0,0 +1,55 @@
/* 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.asynctasks;
import android.content.Context;
import android.os.AsyncTask;
import fr.gouv.etalab.mastodon.client.API;
import fr.gouv.etalab.mastodon.client.Entities.Relationship;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveRelationshipInterface;
/**
* Created by Thomas on 01/05/2017.
* Retrieves relationship between the authenticated user and another account
*/
public class RetrieveRelationshipAsyncTask extends AsyncTask<Void, Void, Void> {
private Context context;
private String accountId;
private Relationship relationship;
private OnRetrieveRelationshipInterface listener;
public RetrieveRelationshipAsyncTask(Context context, String accountId, OnRetrieveRelationshipInterface onRetrieveRelationshipInterface){
this.context = context;
this.listener = onRetrieveRelationshipInterface;
this.accountId = accountId;
}
@Override
protected Void doInBackground(Void... params) {
relationship = new API(context).getRelationship(accountId);
return null;
}
@Override
protected void onPostExecute(Void result) {
listener.onRetrieveRelationship(relationship);
}
}

View File

@ -0,0 +1,56 @@
/* 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.asynctasks;
import android.content.Context;
import android.os.AsyncTask;
import fr.gouv.etalab.mastodon.client.API;
import fr.gouv.etalab.mastodon.client.Entities.Results;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveSearchInterface;
/**
* Created by Thomas on 05/05/2017.
* Retrieves accounts from search (ie: starting with @ when writing a toot)
*/
public class RetrieveSearchAsyncTask extends AsyncTask<Void, Void, Void> {
private Context context;
private String query;
private Results results;
private OnRetrieveSearchInterface listener;
public RetrieveSearchAsyncTask(Context context, String query, OnRetrieveSearchInterface onRetrieveSearchInterface){
this.context = context;
this.query = query;
this.listener = onRetrieveSearchInterface;
}
@Override
protected Void doInBackground(Void... params) {
results = new API(context).search(query);
return null;
}
@Override
protected void onPostExecute(Void result) {
listener.onRetrieveSearch(results);
}
}

View File

@ -0,0 +1,101 @@
/* 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.asynctasks;
import android.app.Activity;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.util.Log;
import fr.gouv.etalab.mastodon.activities.MainActivity;
import fr.gouv.etalab.mastodon.client.API;
import fr.gouv.etalab.mastodon.client.Entities.Account;
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.sqlite.AccountDAO;
/**
* Created by Thomas on 23/04/2017.
* Manage the synchronization with the account and update the db
*/
public class UpdateAccountInfoAsyncTask extends AsyncTask<Void, Void, Void> {
private Context context;
private String token;
private boolean fromWebview;
private boolean error;
private OnUpdateAccountInfoInterface listener;
public UpdateAccountInfoAsyncTask(Context context, String token, OnUpdateAccountInfoInterface onUpdateAccountInfoInterface){
this.context = context;
this.token = token;
this.fromWebview = false;
this.error = false;
this.listener = onUpdateAccountInfoInterface;
}
public UpdateAccountInfoAsyncTask(Context context, boolean fromWebview, String token){
this.context = context;
this.token = token;
this.fromWebview = fromWebview;
}
@Override
protected Void doInBackground(Void... params) {
Account account = new API(context).verifyCredentials();
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
if( token == null) {
token = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null);
}
account.setToken(token);
//TODO: remove this static value to allow other instances
account.setInstance(Helper.INSTANCE);
SQLiteDatabase db = Sqlite.getInstance(context, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
boolean userExists = new AccountDAO(context, db).userExist(account);
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.PREF_KEY_ID, account.getId());
editor.apply();
if( userExists)
new AccountDAO(context, db).updateAccount(account);
else {
if( account.getUsername() != null && account.getCreated_at() != null)
new AccountDAO(context, db).insertAccount(account);
else //Here the user credential in db doesn't match the remote one (it will be disconnected)
error = true;
}
return null;
}
@Override
protected void onPostExecute(Void result) {
if( fromWebview){
Intent mainActivity = new Intent(context, MainActivity.class);
mainActivity.setFlags(Intent.FLAG_ACTIVITY_NEW_TASK);
context.startActivity(mainActivity);
((Activity) context).finish();
}else{
listener.onUpdateAccountInfo(error);
}
}
}

View File

@ -0,0 +1,58 @@
/* 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.asynctasks;
import android.content.Context;
import android.os.AsyncTask;
import java.io.InputStream;
import fr.gouv.etalab.mastodon.client.API;
import fr.gouv.etalab.mastodon.client.Entities.Attachment;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveAttachmentInterface;
/**
* Created by Thomas on 01/05/2017.
* Proceeds to file upload
*/
public class UploadActionAsyncTask extends AsyncTask<Void, Void, Void> {
private Context context;
private OnRetrieveAttachmentInterface listener;
private Attachment attachment;
private InputStream inputStream;
private fr.gouv.etalab.mastodon.client.Entities.Status status;
public UploadActionAsyncTask(Context context, InputStream inputStream, OnRetrieveAttachmentInterface onRetrieveAttachmentInterface){
this.context = context;
this.listener = onRetrieveAttachmentInterface;
this.inputStream = inputStream;
}
@Override
protected Void doInBackground(Void... params) {
attachment = new API(context).uploadMedia(inputStream);
return null;
}
@Override
protected void onPostExecute(Void result) {
listener.onRetrieveAttachment(attachment);
}
}

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,178 @@
/* 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.client.Entities;
import java.util.Date;
/**
* Created by Thomas on 23/04/2017.
*/
public class Account {
private String id;
private String username;
private String acct;
private String display_name;
private boolean locked;
private Date created_at;
private int followers_count;
private int following_count;
private int statuses_count;
private String note;
private String url;
private String avatar;
private String avatar_static;
private String header;
private String header_static;
private String token;
private String instance;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAcct() {
return acct;
}
public void setAcct(String acct) {
this.acct = acct;
}
public String getDisplay_name() {
return display_name;
}
public void setDisplay_name(String display_name) {
this.display_name = display_name;
}
public boolean isLocked() {
return locked;
}
public void setLocked(boolean locked) {
this.locked = locked;
}
public Date getCreated_at() {
return created_at;
}
public void setCreated_at(Date created_at) {
this.created_at = created_at;
}
public int getFollowers_count() {
return followers_count;
}
public void setFollowers_count(int followers_count) {
this.followers_count = followers_count;
}
public int getFollowing_count() {
return following_count;
}
public void setFollowing_count(int following_count) {
this.following_count = following_count;
}
public int getStatuses_count() {
return statuses_count;
}
public void setStatuses_count(int statuses_count) {
this.statuses_count = statuses_count;
}
public String getNote() {
return note;
}
public void setNote(String note) {
this.note = note;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getAvatar() {
return avatar;
}
public void setAvatar(String avatar) {
this.avatar = avatar;
}
public String getAvatar_static() {
return avatar_static;
}
public void setAvatar_static(String avatar_static) {
this.avatar_static = avatar_static;
}
public String getHeader() {
return header;
}
public void setHeader(String header) {
this.header = header;
}
public String getHeader_static() {
return header_static;
}
public void setHeader_static(String header_static) {
this.header_static = header_static;
}
public String getToken() {
return token;
}
public void setToken(String token) {
this.token = token;
}
public String getInstance() {
return instance;
}
public void setInstance(String instance) {
this.instance = instance;
}
}

View File

@ -0,0 +1,25 @@
/* 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.client.Entities;
/**
* Created by Thomas on 23/04/2017.
*/
public class Application {
public String name;
public String website;
}

View File

@ -0,0 +1,77 @@
/* 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.client.Entities;
/**
* Created by Thomas on 23/04/2017.
*/
public class Attachment {
private String id;
private String type;
private String url;
private String remote_url;
private String preview_url;
private String text_url;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getRemote_url() {
return remote_url;
}
public void setRemote_url(String remote_url) {
this.remote_url = remote_url;
}
public String getPreview_url() {
return preview_url;
}
public void setPreview_url(String preview_url) {
this.preview_url = preview_url;
}
public String getText_url() {
return text_url;
}
public void setText_url(String text_url) {
this.text_url = text_url;
}
}

View File

@ -0,0 +1,28 @@
/* 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.client.Entities;
/**
* Created by Thomas on 23/04/2017.
*/
public class Card {
public String url;
public String title;
public String description;
public String image;
}

View File

@ -0,0 +1,44 @@
/* 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.client.Entities;
import java.util.List;
/**
* Created by Thomas on 23/04/2017.
* Manage status Context
*/
public class Context {
private List<Status> ancestors;
private List<Status> descendants;
public List<Status> getAncestors() {
return ancestors;
}
public void setAncestors(List<Status> ancestors) {
this.ancestors = ancestors;
}
public List<Status> getDescendants() {
return descendants;
}
public void setDescendants(List<Status> descendants) {
this.descendants = descendants;
}
}

View File

@ -0,0 +1,32 @@
/* 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.client.Entities;
/**
* Created by Thomas on 23/04/2017.
*/
public class Error {
private String error;
public String getError() {
return error;
}
public void setError(String error) {
this.error = error;
}
}

View File

@ -0,0 +1,59 @@
/* 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.client.Entities;
/**
* Created by Thomas on 23/04/2017.
*/
public class Mention {
private String url;
private String username;
private String acct;
private String id;
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public String getUsername() {
return username;
}
public void setUsername(String username) {
this.username = username;
}
public String getAcct() {
return acct;
}
public void setAcct(String acct) {
this.acct = acct;
}
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
}

View File

@ -0,0 +1,71 @@
/* 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.client.Entities;
import java.util.Date;
/**
* Created by Thomas on 23/04/2017.
*/
public class Notification {
private String id;
private String type;
private Date created_at;
private Account account;
private Status status;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getType() {
return type;
}
public void setType(String type) {
this.type = type;
}
public Date getCreated_at() {
return created_at;
}
public void setCreated_at(Date created_at) {
this.created_at = created_at;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
public Status getStatus() {
return status;
}
public void setStatus(Status status) {
this.status = status;
}
}

View File

@ -0,0 +1,78 @@
/* 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.client.Entities;
/**
* Created by Thomas on 23/04/2017.
* Manage relationship between the authenticated account and another account
*/
public class Relationship {
private String id;
private boolean following;
private boolean followed_by;
private boolean blocking;
private boolean muting;
private boolean requested;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public boolean isFollowing() {
return following;
}
public void setFollowing(boolean following) {
this.following = following;
}
public boolean isFollowed_by() {
return followed_by;
}
public void setFollowed_by(boolean followed_by) {
this.followed_by = followed_by;
}
public boolean isBlocking() {
return blocking;
}
public void setBlocking(boolean blocking) {
this.blocking = blocking;
}
public boolean isMuting() {
return muting;
}
public void setMuting(boolean muting) {
this.muting = muting;
}
public boolean isRequested() {
return requested;
}
public void setRequested(boolean requested) {
this.requested = requested;
}
}

View File

@ -0,0 +1,39 @@
package fr.gouv.etalab.mastodon.client.Entities;
import java.util.List;
/**
* Created by Thomas on 05/05/2017.
* Manage Results for search
*/
public class Results {
private List<Account> accounts;
private List<Status> statuses;
private List<String> hashtags;
public List<Account> getAccounts() {
return accounts;
}
public void setAccounts(List<Account> accounts) {
this.accounts = accounts;
}
public List<Status> getStatuses() {
return statuses;
}
public void setStatuses(List<Status> statuses) {
this.statuses = statuses;
}
public List<String> getHashtags() {
return hashtags;
}
public void setHashtags(List<String> hashtags) {
this.hashtags = hashtags;
}
}

View File

@ -0,0 +1,220 @@
/* 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.client.Entities;
import java.util.Date;
import java.util.List;
/**
* Created by Thomas on 23/04/2017.
*
*/
public class Status {
private String id;
private String uri;
private String url;
private Account account;
private String in_reply_to_id;
private String in_reply_to_account_id;
private Status reblog;
private String content;
private Date created_at;
private int reblogs_count;
private int favourites_count;
private boolean reblogged;
private boolean favourited;
private boolean sensitive;
private String spoiler_text;
private String visibility;
private boolean attachmentShown = false;
public String getId() {
return id;
}
public void setId(String id) {
this.id = id;
}
public String getUri() {
return uri;
}
public void setUri(String uri) {
this.uri = uri;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
public Account getAccount() {
return account;
}
public void setAccount(Account account) {
this.account = account;
}
public String getIn_reply_to_id() {
return in_reply_to_id;
}
public void setIn_reply_to_id(String in_reply_to_id) {
this.in_reply_to_id = in_reply_to_id;
}
public String getIn_reply_to_account_id() {
return in_reply_to_account_id;
}
public void setIn_reply_to_account_id(String in_reply_to_account_id) {
this.in_reply_to_account_id = in_reply_to_account_id;
}
public String getContent() {
return content;
}
public void setContent(String content) {
this.content = content;
}
public Status getReblog() {
return reblog;
}
public void setReblog(Status reblog) {
this.reblog = reblog;
}
public int getReblogs_count() {
return reblogs_count;
}
public void setReblogs_count(int reblogs_count) {
this.reblogs_count = reblogs_count;
}
public Date getCreated_at() {
return created_at;
}
public void setCreated_at(Date created_at) {
this.created_at = created_at;
}
public int getFavourites_count() {
return favourites_count;
}
public void setFavourites_count(int favourites_count) {
this.favourites_count = favourites_count;
}
public boolean isReblogged() {
return reblogged;
}
public void setReblogged(boolean reblogged) {
this.reblogged = reblogged;
}
public boolean isFavourited() {
return favourited;
}
public void setFavourited(boolean favourited) {
this.favourited = favourited;
}
public boolean isSensitive() {
return sensitive;
}
public void setSensitive(boolean sensitive) {
this.sensitive = sensitive;
}
public String getSpoiler_text() {
return spoiler_text;
}
public void setSpoiler_text(String spoiler_text) {
this.spoiler_text = spoiler_text;
}
private List<Attachment> media_attachments;
private List<Mention> mentions;
private List<Tag> tags;
private Application application;
public List<Attachment> getMedia_attachments() {
return media_attachments;
}
public void setMedia_attachments(List<Attachment> media_attachments) {
this.media_attachments = media_attachments;
}
public List<Mention> getMentions() {
return mentions;
}
public void setMentions(List<Mention> mentions) {
this.mentions = mentions;
}
public List<Tag> getTags() {
return tags;
}
public void setTags(List<Tag> tags) {
this.tags = tags;
}
public Application getApplication() {
return application;
}
public void setApplication(Application application) {
this.application = application;
}
public String getVisibility() {
return visibility;
}
public void setVisibility(String visibility) {
this.visibility = visibility;
}
public boolean isAttachmentShown() {
return attachmentShown;
}
public void setAttachmentShown(boolean attachmentShown) {
this.attachmentShown = attachmentShown;
}
}

View File

@ -0,0 +1,42 @@
/* 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.client.Entities;
/**
* Created by Thomas on 23/04/2017.
* Manage Tags
*/
public class Tag {
private String name;
private String url;
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
public String getUrl() {
return url;
}
public void setUrl(String url) {
this.url = url;
}
}

View File

@ -0,0 +1,42 @@
/* 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.client;
/**
* Created by Thomas on 23/04/2017.
*/
public class Error {
private int code;
private String message;
public int getCode() {
return code;
}
public void setCode(int code) {
this.code = code;
}
public String getMessage() {
return message;
}
public void setMessage(String message) {
this.message = message;
}
}

View File

@ -0,0 +1,68 @@
/* 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.client;
import com.loopj.android.http.AsyncHttpClient;
import com.loopj.android.http.AsyncHttpResponseHandler;
import com.loopj.android.http.RequestParams;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import fr.gouv.etalab.mastodon.helper.Helper;
/**
* Created by Thomas on 23/04/2017.
* Client to call urls
*/
public class OauthClient {
private static final String BASE_URL = "https://" + Helper.INSTANCE;
private static AsyncHttpClient client = new AsyncHttpClient();
public void get(String action, HashMap<String, String> paramaters, AsyncHttpResponseHandler responseHandler) {
client.setTimeout(5000);
RequestParams params = hashToRequestParams(paramaters);
client.get(getAbsoluteUrl(action), params, responseHandler);
}
public void post(String action, HashMap<String, String> paramaters, AsyncHttpResponseHandler responseHandler) {
RequestParams params = hashToRequestParams(paramaters);
client.post(getAbsoluteUrl(action), params, responseHandler);
}
private String getAbsoluteUrl(String action) {
return BASE_URL + action;
}
/**
* Convert HashMap<String,String> to RequestParams
* @param params HashMap
* @return RequestParams
*/
private RequestParams hashToRequestParams(HashMap<String,String> params){
RequestParams requestParams = new RequestParams();
Iterator it = params.entrySet().iterator();
while (it.hasNext()) {
Map.Entry pair = (Map.Entry)it.next();
requestParams.add(pair.getKey().toString(), pair.getValue().toString());
it.remove();
}
return requestParams;
}
}

View File

@ -0,0 +1,247 @@
package fr.gouv.etalab.mastodon.drawers;
/* 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.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.design.widget.FloatingActionButton;
import android.text.Html;
import android.text.util.Linkify;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
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.RetrieveAccountsAsyncTask;
import fr.gouv.etalab.mastodon.client.API;
import fr.gouv.etalab.mastodon.client.Entities.Account;
import fr.gouv.etalab.mastodon.helper.Helper;
import fr.gouv.etalab.mastodon.interfaces.OnPostActionInterface;
import mastodon.etalab.gouv.fr.mastodon.R;
import fr.gouv.etalab.mastodon.activities.ShowAccountActivity;
import fr.gouv.etalab.mastodon.asynctasks.PostActionAsyncTask;
/**
* Created by Thomas on 27/04/2017.
* Adapter for accounts
*/
public class AccountsListAdapter extends BaseAdapter implements OnPostActionInterface {
private List<Account> accounts;
private LayoutInflater layoutInflater;
private ImageLoader imageLoader;
private DisplayImageOptions options;
private RetrieveAccountsAsyncTask.Type action;
private Context context;
private AccountsListAdapter accountsListAdapter;
public AccountsListAdapter(Context context, RetrieveAccountsAsyncTask.Type action, List<Account> accounts){
this.context = context;
this.accounts = accounts;
layoutInflater = LayoutInflater.from(context);
imageLoader = ImageLoader.getInstance();
options = new DisplayImageOptions.Builder().displayer(new SimpleBitmapDisplayer()).cacheInMemory(false)
.cacheOnDisk(true).resetViewBeforeLoading(true).build();
this.action = action;
this.accountsListAdapter = this;
}
@Override
public int getCount() {
return accounts.size();
}
@Override
public Object getItem(int position) {
return accounts.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
final Account account = accounts.get(position);
final ViewHolder holder;
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.drawer_account, parent, false);
holder = new ViewHolder();
holder.account_pp = (ImageView) convertView.findViewById(R.id.account_pp);
holder.account_dn = (TextView) convertView.findViewById(R.id.account_dn);
holder.account_ac = (TextView) convertView.findViewById(R.id.account_ac);
holder.account_un = (TextView) convertView.findViewById(R.id.account_un);
holder.account_ds = (TextView) convertView.findViewById(R.id.account_ds);
holder.account_sc = (TextView) convertView.findViewById(R.id.account_sc);
holder.account_fgc = (TextView) convertView.findViewById(R.id.account_fgc);
holder.account_frc = (TextView) convertView.findViewById(R.id.account_frc);
holder.account_action_block = (FloatingActionButton) convertView.findViewById(R.id.account_action_block);
holder.account_action_mute = (FloatingActionButton) convertView.findViewById(R.id.account_action_mute);
holder.account_container = (LinearLayout) convertView.findViewById(R.id.account_container);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
if( action == RetrieveAccountsAsyncTask.Type.BLOCKED)
holder.account_action_block.setVisibility(View.VISIBLE);
else if( action == RetrieveAccountsAsyncTask.Type.MUTED)
holder.account_action_mute.setVisibility(View.VISIBLE);
holder.account_action_mute.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
moreOptionDialog(account);
}
});
holder.account_action_block.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
moreOptionDialog(account);
}
});
holder.account_container.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if( holder.account_ds.getVisibility() == View.VISIBLE)
holder.account_ds.setVisibility(View.GONE);
else
holder.account_ds.setVisibility(View.VISIBLE);
}
});
holder.account_dn.setText(account.getDisplay_name());
holder.account_un.setText(String.format("@%s",account.getUsername()));
holder.account_ac.setText(account.getAcct());
if( account.getDisplay_name().equals(account.getAcct()))
holder.account_ac.setVisibility(View.GONE);
else
holder.account_ac.setVisibility(View.VISIBLE);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
holder.account_ds.setText(Html.fromHtml(account.getNote(), Html.FROM_HTML_MODE_COMPACT));
else
holder.account_ds.setText(Html.fromHtml(account.getNote()));
holder.account_ds.setAutoLinkMask(Linkify.WEB_URLS);
holder.account_sc.setText(String.valueOf(account.getStatuses_count()));
holder.account_fgc.setText(String.valueOf(account.getFollowing_count()));
holder.account_frc.setText(String.valueOf(account.getFollowers_count()));
//Profile picture
imageLoader.displayImage(account.getAvatar(), holder.account_pp, options);
holder.account_pp.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(context, ShowAccountActivity.class);
Bundle b = new Bundle();
b.putString("accountId", account.getId());
intent.putExtras(b);
context.startActivity(intent);
}
});
return convertView;
}
@Override
public void onPostAction(int statusCode, API.StatusAction statusAction, String targetedId) {
Helper.manageMessageStatusCode(context, statusCode, statusAction);
//When unmuting or unblocking an account, it is removed from the list
List<Account> accountsToRemove = new ArrayList<>();
if( statusAction == API.StatusAction.UNMUTE || statusAction == API.StatusAction.UNBLOCK){
for(Account account: accounts){
if( account.getId().equals(targetedId))
accountsToRemove.add(account);
}
accounts.removeAll(accountsToRemove);
accountsListAdapter.notifyDataSetChanged();
}
}
private class ViewHolder {
ImageView account_pp;
TextView account_ac;
TextView account_dn;
TextView account_un;
TextView account_ds;
TextView account_sc;
TextView account_fgc;
TextView account_frc;
LinearLayout account_container;
FloatingActionButton account_action_block;
FloatingActionButton account_action_mute;
}
/**
* More option for acccounts (unmute / unblock)
* @param account Account current account
*/
private void moreOptionDialog(final Account account){
String[] stringArrayConf = context.getResources().getStringArray(R.array.more_action_confirm_account);
final API.StatusAction doAction;
AlertDialog.Builder dialog = new AlertDialog.Builder(context);
if( action == RetrieveAccountsAsyncTask.Type.BLOCKED) {
dialog.setMessage(stringArrayConf[1]);
doAction = API.StatusAction.UNBLOCK;
}else {
dialog.setMessage(stringArrayConf[0]);
doAction = API.StatusAction.UNMUTE;
}
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) {
new PostActionAsyncTask(context, doAction, account.getId(), AccountsListAdapter.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
dialog.dismiss();
}
});
dialog.show();
}
}

View File

@ -0,0 +1,118 @@
package fr.gouv.etalab.mastodon.drawers;
/* 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.content.Context;
import android.content.Intent;
import android.support.v4.content.LocalBroadcastManager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.TextView;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer;
import java.util.List;
import fr.gouv.etalab.mastodon.client.Entities.Account;
import fr.gouv.etalab.mastodon.helper.Helper;
import mastodon.etalab.gouv.fr.mastodon.R;
/**
* Created by Thomas on 05/05/2017.
* Adapter for accounts when searching
*/
public class AccountsSearchAdapter extends BaseAdapter {
private List<Account> accounts;
private LayoutInflater layoutInflater;
private ImageLoader imageLoader;
private DisplayImageOptions options;
private Context context;
public AccountsSearchAdapter(Context context, List<Account> accounts){
this.accounts = accounts;
layoutInflater = LayoutInflater.from(context);
imageLoader = ImageLoader.getInstance();
this.context = context;
options = new DisplayImageOptions.Builder().displayer(new SimpleBitmapDisplayer()).cacheInMemory(false)
.cacheOnDisk(true).resetViewBeforeLoading(true).build();
}
@Override
public int getCount() {
return accounts.size();
}
@Override
public Object getItem(int position) {
return accounts.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
final Account account = accounts.get(position);
final ViewHolder holder;
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.drawer_account_search, parent, false);
holder = new ViewHolder();
holder.account_pp = (ImageView) convertView.findViewById(R.id.account_pp);
holder.account_dn = (TextView) convertView.findViewById(R.id.account_dn);
holder.account_un = (TextView) convertView.findViewById(R.id.account_un);
holder.account_container = (LinearLayout) convertView.findViewById(R.id.account_container);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
holder.account_dn.setText(account.getDisplay_name());
holder.account_un.setText(String.format("@%s",account.getUsername()));
//Profile picture
imageLoader.displayImage(account.getAvatar(), holder.account_pp, options);
holder.account_container.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(Helper.SEARCH_VALIDATE_ACCOUNT);
intent.putExtra("acct", account.getAcct());
LocalBroadcastManager.getInstance(context).sendBroadcast(intent);
}
});
return convertView;
}
private class ViewHolder {
ImageView account_pp;
TextView account_dn;
TextView account_un;
LinearLayout account_container;
}
}

View File

@ -0,0 +1,249 @@
package fr.gouv.etalab.mastodon.drawers;
/* 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.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.net.Uri;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.text.Html;
import android.text.util.Linkify;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.BaseAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.VideoView;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
import java.util.List;
import fr.gouv.etalab.mastodon.activities.ShowAccountActivity;
import fr.gouv.etalab.mastodon.activities.ShowConversationActivity;
import fr.gouv.etalab.mastodon.activities.TootActivity;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveFeedsAsyncTask;
import mastodon.etalab.gouv.fr.mastodon.R;
import fr.gouv.etalab.mastodon.client.Entities.Attachment;
import fr.gouv.etalab.mastodon.client.Entities.Notification;
import fr.gouv.etalab.mastodon.client.Entities.Status;
import fr.gouv.etalab.mastodon.helper.Helper;
/**
* Created by Thomas on 24/04/2017.
* Adapter for Status
*/
public class NotificationsListAdapter extends BaseAdapter {
private Context context;
private List<Notification> notifications;
private LayoutInflater layoutInflater;
private ImageLoader imageLoader;
private DisplayImageOptions options;
public NotificationsListAdapter(Context context, List<Notification> notifications){
this.context = context;
this.notifications = notifications;
layoutInflater = LayoutInflater.from(this.context);
imageLoader = ImageLoader.getInstance();
options = new DisplayImageOptions.Builder().displayer(new SimpleBitmapDisplayer()).cacheInMemory(false)
.cacheOnDisk(true).resetViewBeforeLoading(true).build();
}
@Override
public int getCount() {
return notifications.size();
}
@Override
public Object getItem(int position) {
return notifications.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
final Notification notification = notifications.get(position);
final ViewHolder holder;
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.drawer_notification, parent, false);
holder = new ViewHolder();
holder.notification_status_container = (LinearLayout) convertView.findViewById(R.id.notification_status_container);
holder.status_document_container = (LinearLayout) convertView.findViewById(R.id.status_document_container);
holder.notification_status_content = (TextView) convertView.findViewById(R.id.notification_status_content);
holder.notification_account_username = (TextView) convertView.findViewById(R.id.notification_account_username);
holder.notification_type = (TextView) convertView.findViewById(R.id.notification_type);
holder.notification_account_displayname = (TextView) convertView.findViewById(R.id.notification_account_displayname);
holder.notification_account_profile = (ImageView) convertView.findViewById(R.id.notification_account_profile);
holder.status_favorite_count = (TextView) convertView.findViewById(R.id.status_favorite_count);
holder.status_reblog_count = (TextView) convertView.findViewById(R.id.status_reblog_count);
holder.status_date = (TextView) convertView.findViewById(R.id.status_date);
holder.status_reply = (ImageView) convertView.findViewById(R.id.status_reply);
holder.status_privacy = (ImageView) convertView.findViewById(R.id.status_privacy);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
final float scale = context.getResources().getDisplayMetrics().density;
String type = notification.getType();
String typeString = "";
switch (type){
case "mention":
typeString = String.format("@%s %s", notification.getAccount().getAcct(),context.getString(R.string.notif_mention));
break;
case "reblog":
typeString = String.format("@%s %s", notification.getAccount().getAcct(),context.getString(R.string.notif_reblog));
break;
case "favourite":
typeString = String.format("@%s %s", notification.getAccount().getAcct(),context.getString(R.string.notif_favourite));
break;
case "follow":
typeString = String.format("@%s %s", notification.getAccount().getAcct(),context.getString(R.string.notif_follow));
break;
}
holder.notification_type.setText(typeString);
final Status status = notification.getStatus();
if( status != null){
if( status.getMedia_attachments().size() < 1)
holder.status_document_container.setVisibility(View.GONE);
else
holder.status_document_container.setVisibility(View.VISIBLE);
if( !status.getIn_reply_to_account_id().equals("null") || !status.getIn_reply_to_id().equals("null") ){
Drawable img = ContextCompat.getDrawable(context, R.drawable.ic_reply);
img.setBounds(0,0,(int) (20 * scale + 0.5f),(int) (15 * scale + 0.5f));
holder.notification_account_displayname.setCompoundDrawables( img, null, null, null);
}else if( status.isReblogged()){
Drawable img = ContextCompat.getDrawable(context, R.drawable.ic_retweet);
img.setBounds(0,0,(int) (20 * scale + 0.5f),(int) (15 * scale + 0.5f));
holder.notification_account_displayname.setCompoundDrawables( img, null, null, null);
}else{
holder.notification_account_displayname.setCompoundDrawables( null, null, null, null);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
holder.notification_status_content.setText(Html.fromHtml(status.getContent(), Html.FROM_HTML_MODE_COMPACT));
else
holder.notification_status_content.setText(Html.fromHtml(status.getContent()));
holder.notification_status_content.setAutoLinkMask(Linkify.WEB_URLS);
holder.status_favorite_count.setText(String.valueOf(status.getFavourites_count()));
holder.status_reblog_count.setText(String.valueOf(status.getReblogs_count()));
holder.status_date.setText(Helper.dateDiff(context, status.getCreated_at()));
//Adds attachment -> disabled, to enable them uncomment the line below
//loadAttachments(status, holder);
holder.notification_status_container.setVisibility(View.VISIBLE);
if( !status.getIn_reply_to_account_id().equals("null") || !status.getIn_reply_to_id().equals("null") ) {
holder.notification_status_content.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(context, ShowConversationActivity.class);
Bundle b = new Bundle();
b.putString("statusId", status.getId()); //Your id
intent.putExtras(b); //Put your id to your next Intent
context.startActivity(intent);
}
});
}
switch (status.getVisibility()){
case "public":
holder.status_privacy.setImageResource(R.drawable.ic_action_globe);
break;
case "unlisted":
holder.status_privacy.setImageResource(R.drawable.ic_action_lock_open);
break;
case "private":
holder.status_privacy.setImageResource(R.drawable.ic_action_lock_closed);
break;
case "direct":
holder.status_privacy.setImageResource(R.drawable.ic_local_post_office);
break;
}
}else {
holder.notification_status_container.setVisibility(View.GONE);
}
holder.notification_account_profile.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(context, ShowAccountActivity.class);
Bundle b = new Bundle();
b.putString("accountId", notification.getAccount().getId());
intent.putExtras(b);
context.startActivity(intent);
}
});
holder.status_reply.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(context, TootActivity.class);
Bundle b = new Bundle();
b.putString("inReplyTo", notification.getStatus().getId()); //Your id
intent.putExtras(b); //Put your id to your next Intent
context.startActivity(intent);
}
});
holder.notification_account_displayname.setText(notification.getAccount().getDisplay_name());
holder.notification_account_username.setText( String.format("@%s",notification.getAccount().getUsername()));
//Profile picture
imageLoader.displayImage(notification.getAccount().getAvatar(), holder.notification_account_profile, options);
return convertView;
}
private class ViewHolder {
TextView notification_status_content;
TextView notification_type;
TextView notification_account_username;
TextView notification_account_displayname;
ImageView notification_account_profile;
TextView status_favorite_count;
TextView status_reblog_count;
TextView status_date;
ImageView status_reply;
LinearLayout status_document_container;
LinearLayout notification_status_container;
ImageView status_privacy;
}
}

View File

@ -0,0 +1,718 @@
package fr.gouv.etalab.mastodon.drawers;
/* 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.Manifest;
import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.drawable.Drawable;
import android.media.MediaPlayer;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.support.v4.app.ActivityCompat;
import android.support.v4.content.ContextCompat;
import android.text.Html;
import android.text.util.Linkify;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.webkit.WebView;
import android.widget.ArrayAdapter;
import android.widget.BaseAdapter;
import android.widget.Button;
import android.widget.EditText;
import android.widget.ImageView;
import android.widget.LinearLayout;
import android.widget.RelativeLayout;
import android.widget.TextView;
import android.widget.VideoView;
import com.nostra13.universalimageloader.core.DisplayImageOptions;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.assist.FailReason;
import com.nostra13.universalimageloader.core.display.SimpleBitmapDisplayer;
import com.nostra13.universalimageloader.core.listener.SimpleImageLoadingListener;
import java.util.ArrayList;
import java.util.List;
import fr.gouv.etalab.mastodon.activities.MainActivity;
import fr.gouv.etalab.mastodon.activities.ShowConversationActivity;
import fr.gouv.etalab.mastodon.activities.TootActivity;
import fr.gouv.etalab.mastodon.helper.Helper;
import mastodon.etalab.gouv.fr.mastodon.R;
import fr.gouv.etalab.mastodon.activities.ShowAccountActivity;
import fr.gouv.etalab.mastodon.asynctasks.PostActionAsyncTask;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveFeedsAsyncTask;
import fr.gouv.etalab.mastodon.client.API;
import fr.gouv.etalab.mastodon.client.Entities.Attachment;
import fr.gouv.etalab.mastodon.client.Entities.Status;
import fr.gouv.etalab.mastodon.interfaces.OnPostActionInterface;
import static fr.gouv.etalab.mastodon.helper.Helper.EXTERNAL_STORAGE_REQUEST_CODE;
/**
* Created by Thomas on 24/04/2017.
* Adapter for Status
*/
public class StatusListAdapter extends BaseAdapter implements OnPostActionInterface {
private Context context;
private List<Status> statuses;
private LayoutInflater layoutInflater;
private ImageLoader imageLoader;
private DisplayImageOptions options;
private boolean isOnWifi;
private int behaviorWithAttachments;
private StatusListAdapter statusListAdapter;
private final int REBLOG = 1;
private final int FAVOURITE = 2;
private ViewHolder holder;
private RetrieveFeedsAsyncTask.Type type;
public StatusListAdapter(Context context, RetrieveFeedsAsyncTask.Type type, boolean isOnWifi, int behaviorWithAttachments, List<Status> statuses){
this.context = context;
this.statuses = statuses;
this.isOnWifi = isOnWifi;
this.behaviorWithAttachments = behaviorWithAttachments;
layoutInflater = LayoutInflater.from(this.context);
imageLoader = ImageLoader.getInstance();
options = new DisplayImageOptions.Builder().displayer(new SimpleBitmapDisplayer()).cacheInMemory(false)
.cacheOnDisk(true).resetViewBeforeLoading(true).build();
statusListAdapter = this;
this.type = type;
}
@Override
public int getCount() {
return statuses.size();
}
@Override
public Object getItem(int position) {
return statuses.get(position);
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public View getView(final int position, View convertView, ViewGroup parent) {
final Status status = statuses.get(position);
if (convertView == null) {
convertView = layoutInflater.inflate(R.layout.drawer_status, parent, false);
holder = new ViewHolder();
holder.status_document_container = (LinearLayout) convertView.findViewById(R.id.status_document_container);
holder.status_content = (TextView) convertView.findViewById(R.id.status_content);
holder.status_account_username = (TextView) convertView.findViewById(R.id.status_account_username);
holder.status_account_displayname = (TextView) convertView.findViewById(R.id.status_account_displayname);
holder.status_account_profile = (ImageView) convertView.findViewById(R.id.status_account_profile);
holder.status_favorite_count = (TextView) convertView.findViewById(R.id.status_favorite_count);
holder.status_reblog_count = (TextView) convertView.findViewById(R.id.status_reblog_count);
holder.status_toot_date = (TextView) convertView.findViewById(R.id.status_toot_date);
holder.status_show_more = (Button) convertView.findViewById(R.id.status_show_more);
holder.status_more = (ImageView) convertView.findViewById(R.id.status_more);
holder.status_reblog_user = (TextView) convertView.findViewById(R.id.status_reblog_user);
holder.status_action_container = (LinearLayout) convertView.findViewById(R.id.status_action_container);
holder.status_prev1 = (ImageView) convertView.findViewById(R.id.status_prev1);
holder.status_prev2 = (ImageView) convertView.findViewById(R.id.status_prev2);
holder.status_prev3 = (ImageView) convertView.findViewById(R.id.status_prev3);
holder.status_prev4 = (ImageView) convertView.findViewById(R.id.status_prev4);
holder.status_container2 = (LinearLayout) convertView.findViewById(R.id.status_container2);
holder.status_container3 = (LinearLayout) convertView.findViewById(R.id.status_container3);
holder.status_reply = (ImageView) convertView.findViewById(R.id.status_reply);
holder.status_privacy = (ImageView) convertView.findViewById(R.id.status_privacy);
holder.main_container = (LinearLayout) convertView.findViewById(R.id.main_container);
convertView.setTag(holder);
} else {
holder = (ViewHolder) convertView.getTag();
}
//Hides action bottom bar action when looking to status trough accounts
if( type == RetrieveFeedsAsyncTask.Type.USER){
holder.status_action_container.setVisibility(View.GONE);
}
final float scale = context.getResources().getDisplayMetrics().density;
if( !status.getIn_reply_to_account_id().equals("null") || !status.getIn_reply_to_id().equals("null") ){
Drawable img = ContextCompat.getDrawable(context, R.drawable.ic_reply);
img.setBounds(0,0,(int) (20 * scale + 0.5f),(int) (15 * scale + 0.5f));
holder.status_account_displayname.setCompoundDrawables( img, null, null, null);
}else if( status.getReblog() != null){
Drawable img = ContextCompat.getDrawable(context, R.drawable.ic_retweet_header);
img.setBounds(0,0,(int) (20 * scale + 0.5f),(int) (15 * scale + 0.5f));
holder.status_account_displayname.setCompoundDrawables( img, null, null, null);
}else{
holder.status_account_displayname.setCompoundDrawables( null, null, null, null);
}
//Click on a conversation
if( type != RetrieveFeedsAsyncTask.Type.CONTEXT ){
if( !status.getIn_reply_to_account_id().equals("null") || !status.getIn_reply_to_id().equals("null") ) {
holder.status_content.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(context, ShowConversationActivity.class);
Bundle b = new Bundle();
b.putString("statusId", status.getId()); //Your id
intent.putExtras(b); //Put your id to your next Intent
context.startActivity(intent);
}
});
}else{
holder.status_content.setOnClickListener(null);
}
}else {
if( position == ShowConversationActivity.position){
holder.main_container.setBackgroundResource(R.color.blue_light);
}else {
holder.main_container.setBackgroundResource(R.color.white);
}
}
final String content, displayName, username, ppurl;
if( status.getReblog() != null){
content = status.getReblog().getContent();
displayName = status.getReblog().getAccount().getDisplay_name();
username = status.getReblog().getAccount().getUsername();
holder.status_reblog_user.setText(displayName + " " +String.format("@%s",username));
ppurl = status.getReblog().getAccount().getAvatar();
holder.status_reblog_user.setVisibility(View.VISIBLE);
holder.status_account_displayname.setText(context.getResources().getString(R.string.reblog_by, status.getAccount().getAcct()));
holder.status_account_username.setText( "");
}else {
ppurl = status.getAccount().getAvatar();
content = status.getContent();
displayName = status.getAccount().getDisplay_name();
username = status.getAccount().getUsername();
holder.status_reblog_user.setVisibility(View.GONE);
holder.status_account_displayname.setText(displayName);
holder.status_account_username.setText( String.format("@%s",username));
}
holder.status_reply.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(context, TootActivity.class);
Bundle b = new Bundle();
b.putString("inReplyTo", status.getId()); //Your id
intent.putExtras(b); //Put your id to your next Intent
context.startActivity(intent);
}
});
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
holder.status_content.setText(Html.fromHtml(content, Html.FROM_HTML_MODE_COMPACT));
else
//noinspection deprecation
holder.status_content.setText(Html.fromHtml(content));
holder.status_content.setAutoLinkMask(Linkify.WEB_URLS);
holder.status_favorite_count.setText(String.valueOf(status.getFavourites_count()));
holder.status_reblog_count.setText(String.valueOf(status.getReblogs_count()));
holder.status_toot_date.setText(Helper.dateDiff(context, status.getCreated_at()));
imageLoader.displayImage(ppurl, holder.status_account_profile, options);
if( status.getMedia_attachments().size() < 1) {
holder.status_document_container.setVisibility(View.GONE);
holder.status_show_more.setVisibility(View.GONE);
}else{
if(behaviorWithAttachments == Helper.ATTACHMENT_ALWAYS || ( behaviorWithAttachments == Helper.ATTACHMENT_WIFI && isOnWifi)){
loadAttachments(status);
holder.status_show_more.setVisibility(View.GONE);
status.setAttachmentShown(true);
}else{
if( !status.isAttachmentShown() ) {
holder.status_show_more.setVisibility(View.VISIBLE);
holder.status_document_container.setVisibility(View.GONE);
}else {
loadAttachments(status);
}
}
}
switch (status.getVisibility()){
case "public":
holder.status_privacy.setImageResource(R.drawable.ic_action_globe);
break;
case "unlisted":
holder.status_privacy.setImageResource(R.drawable.ic_action_lock_open);
break;
case "private":
holder.status_privacy.setImageResource(R.drawable.ic_action_lock_closed);
break;
case "direct":
holder.status_privacy.setImageResource(R.drawable.ic_local_post_office);
break;
}
Drawable imgFav, imgReblog;
if( status.isFavourited())
imgFav = ContextCompat.getDrawable(context, R.drawable.ic_fav_yellow);
else
imgFav = ContextCompat.getDrawable(context, R.drawable.ic_fav_black);
if( status.isReblogged())
imgReblog = ContextCompat.getDrawable(context, R.drawable.ic_retweet_yellow);
else
imgReblog = ContextCompat.getDrawable(context, R.drawable.ic_retweet_black);
imgFav.setBounds(0,0,(int) (20 * scale + 0.5f),(int) (20 * scale + 0.5f));
imgReblog.setBounds(0,0,(int) (20 * scale + 0.5f),(int) (15 * scale + 0.5f));
holder.status_favorite_count.setCompoundDrawables(imgFav, null, null, null);
holder.status_reblog_count.setCompoundDrawables(imgReblog, null, null, null);
holder.status_show_more.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
loadAttachments(status);
holder.status_show_more.setVisibility(View.GONE);
status.setAttachmentShown(true);
statusListAdapter.notifyDataSetChanged();
}
});
holder.status_favorite_count.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
boolean confirmation = sharedpreferences.getBoolean(Helper.SET_NOTIF_VALIDATION, true);
if( confirmation )
displayConfirmationDialog(FAVOURITE,status);
else
favouriteAction(status);
}
});
holder.status_reblog_count.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
boolean confirmation = sharedpreferences.getBoolean(Helper.SET_NOTIF_VALIDATION, true);
if( confirmation )
displayConfirmationDialog(REBLOG,status);
else
favouriteAction(status);
}
});
holder.status_more.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
moreOptionDialog(status);
}
});
holder.status_account_profile.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(context, ShowAccountActivity.class);
Bundle b = new Bundle();
b.putString("accountId", status.getAccount().getId());
intent.putExtras(b);
context.startActivity(intent);
}
});
//Profile picture
return convertView;
}
/**
* Favourites/Unfavourites a status
* @param status Status
*/
private void favouriteAction(Status status){
if( status.isFavourited()){
new PostActionAsyncTask(context, API.StatusAction.UNFAVOURITE, status.getId(), StatusListAdapter.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
status.setFavourited(false);
}else{
new PostActionAsyncTask(context, API.StatusAction.FAVOURITE, status.getId(), StatusListAdapter.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
status.setFavourited(true);
}
statusListAdapter.notifyDataSetChanged();
}
/**
* Reblog/Unreblog a status
* @param status Status
*/
private void reblogAction(Status status){
if( status.isReblogged()){
new PostActionAsyncTask(context, API.StatusAction.UNREBLOG, status.getId(), StatusListAdapter.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
status.setReblogged(false);
}else{
new PostActionAsyncTask(context, API.StatusAction.REBLOG, status.getId(), StatusListAdapter.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
status.setReblogged(true);
}
statusListAdapter.notifyDataSetChanged();
}
private void loadAttachments(final Status status){
List<Attachment> attachments = status.getMedia_attachments();
if( attachments != null && attachments.size() > 0){
int i = 0;
if( attachments.size() == 1){
holder.status_container2.setVisibility(View.GONE);
}else if(attachments.size() == 2){
holder.status_container2.setVisibility(View.VISIBLE);
holder.status_container3.setVisibility(View.GONE);
}else if( attachments.size() == 3){
holder.status_container2.setVisibility(View.VISIBLE);
holder.status_container3.setVisibility(View.VISIBLE);
holder.status_prev4.setVisibility(View.GONE);
}else {
holder.status_prev4.setVisibility(View.VISIBLE);
}
for(final Attachment attachment: attachments){
ImageView imageView;
if( i == 0)
imageView = holder.status_prev1;
else if( i == 1)
imageView = holder.status_prev2;
else if(i == 2)
imageView = holder.status_prev3;
else
imageView = holder.status_prev4;
String url = attachment.getPreview_url();
if( url == null || url.trim().equals(""))
url = attachment.getUrl();
imageLoader.displayImage(url, imageView, options);
imageView.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
showPicture(attachment);
}
});
i++;
}
holder.status_document_container.setVisibility(View.VISIBLE);
}else{
holder.status_document_container.setVisibility(View.GONE);
}
holder.status_show_more.setVisibility(View.GONE);
}
private void showPicture(final Attachment attachment) {
final AlertDialog.Builder alertadd = new AlertDialog.Builder(context);
LayoutInflater factory = LayoutInflater.from(context);
final View view = factory.inflate(R.layout.show_attachment, null);
alertadd.setView(view);
final RelativeLayout loader = (RelativeLayout) view.findViewById(R.id.loader);
switch (attachment.getType()){
case "image": {
String url = attachment.getRemote_url();
if(url == null || url.trim().equals(""))
url = attachment.getUrl();
final ImageView imageView = (ImageView) view.findViewById(R.id.dialog_imageview);
imageLoader.displayImage(url, imageView, options, new SimpleImageLoadingListener(){
@Override
public void onLoadingComplete(String imageUri, View view, Bitmap loadedImage) {
super.onLoadingComplete(imageUri, view, loadedImage);
loader.setVisibility(View.GONE);
imageView.setVisibility(View.VISIBLE);
}
@Override
public void onLoadingFailed(java.lang.String imageUri, android.view.View view, FailReason failReason){
imageLoader.displayImage(attachment.getPreview_url(), imageView, options);
loader.setVisibility(View.GONE);
}
});
break;
}
case "gifv":
case "video": {
if( attachment.getRemote_url().contains(".gif") ){
view.findViewById(R.id.dialog_webview_container).setVisibility(View.VISIBLE);
WebView webView = (WebView) view.findViewById(R.id.dialog_webview);
webView.getSettings().setJavaScriptEnabled(false);
webView.clearCache(false);
webView.setScrollbarFadingEnabled(true);
webView.getSettings().setBuiltInZoomControls(false);
webView.getSettings().setSupportZoom(false);
webView.getSettings().setUseWideViewPort(false);
webView.setVerticalScrollBarEnabled(false);
webView.setHorizontalScrollBarEnabled(false);
webView.setInitialScale(0);
String url = attachment.getRemote_url();
if(url == null || url.trim().equals(""))
url = attachment.getUrl();
webView.loadUrl(url);
loader.setVisibility(View.GONE);
}else {
String url = attachment.getRemote_url();
if(url == null || url.trim().equals(""))
url = attachment.getUrl();
Uri uri = Uri.parse(url);
VideoView videoView = (VideoView) view.findViewById(R.id.dialog_videoview);
videoView.setVisibility(View.VISIBLE);
videoView.setVideoURI(uri);
videoView.start();
videoView.setOnPreparedListener(new MediaPlayer.OnPreparedListener() {
@Override
public void onPrepared(MediaPlayer mp) {
loader.setVisibility(View.GONE);
}
});
}
break;
}
}
alertadd.setPositiveButton(R.string.download, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dlg, int sumthin) {
if(Build.VERSION.SDK_INT >= 23 ){
if (ContextCompat.checkSelfPermission(context, Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED ) {
ActivityCompat.requestPermissions((MainActivity)context, new String[]{Manifest.permission.WRITE_EXTERNAL_STORAGE}, EXTERNAL_STORAGE_REQUEST_CODE);
} else {
Helper.manageDownloads(context, attachment.getRemote_url());
}
}else{
Helper.manageDownloads(context, attachment.getRemote_url());
}
dlg.dismiss();
}
});
alertadd.setNeutralButton(R.string.close, new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dlg, int sumthin) {
dlg.dismiss();
}
});
alertadd.show();
}
@Override
public void onPostAction(int statusCode,API.StatusAction statusAction, String targetedId) {
Helper.manageMessageStatusCode(context, statusCode, statusAction);
//When muting or blocking an account, its status are removed from the list
List<Status> statusesToRemove = new ArrayList<>();
if( statusAction == API.StatusAction.MUTE || statusAction == API.StatusAction.BLOCK){
for(Status status: statuses){
if( status.getAccount().getId().equals(targetedId))
statusesToRemove.add(status);
}
statuses.removeAll(statusesToRemove);
statusListAdapter.notifyDataSetChanged();
}else if( statusAction == API.StatusAction.UNSTATUS ){
for(Status status: statuses){
if( status.getId().equals(targetedId))
statusesToRemove.add(status);
}
statuses.removeAll(statusesToRemove);
statusListAdapter.notifyDataSetChanged();
}
}
private class ViewHolder {
TextView status_content;
TextView status_account_username;
TextView status_account_displayname;
ImageView status_account_profile;
TextView status_favorite_count;
TextView status_reblog_count;
TextView status_toot_date;
TextView status_reblog_user;
Button status_show_more;
ImageView status_more;
LinearLayout status_action_container;
LinearLayout status_document_container;
ImageView status_prev1;
ImageView status_prev2;
ImageView status_prev3;
ImageView status_prev4;
ImageView status_reply;
ImageView status_privacy;
LinearLayout status_container2;
LinearLayout status_container3;
LinearLayout main_container;
}
/**
* Display a validation message
* @param action int
* @param status Status
*/
private void displayConfirmationDialog(final int action, final Status status){
String title = null;
if( action == FAVOURITE){
if( status.isFavourited())
title = context.getString(R.string.favourite_remove);
else
title = context.getString(R.string.favourite_add);
}else if( action == REBLOG ){
if( status.isReblogged())
title = context.getString(R.string.reblog_remove);
else
title = context.getString(R.string.reblog_add);
}
AlertDialog.Builder builder = new AlertDialog.Builder(context);
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
builder.setMessage(Html.fromHtml(status.getContent(), Html.FROM_HTML_MODE_COMPACT));
else
//noinspection deprecation
builder.setMessage(Html.fromHtml(status.getContent()));
builder.setIcon(android.R.drawable.ic_dialog_alert)
.setTitle(title)
.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
if( action == REBLOG)
reblogAction(status);
else if( action == FAVOURITE)
favouriteAction(status);
dialog.dismiss();
}
})
.setNegativeButton(R.string.no, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
})
.show();
}
/**
* More option for status (report / remove status / Mute / Block)
* @param status Status current status
*/
private void moreOptionDialog(final Status status){
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
String userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null);
final boolean isOwner = status.getAccount().getId().equals(userId);
AlertDialog.Builder builderSingle = new AlertDialog.Builder(context);
builderSingle.setTitle(R.string.make_a_choice);
final String[] stringArray, stringArrayConf;
final API.StatusAction[] doAction;
if( isOwner) {
stringArray = context.getResources().getStringArray(R.array.more_action_owner);
stringArrayConf = context.getResources().getStringArray(R.array.more_action_owner_confirm);
doAction = new API.StatusAction[]{API.StatusAction.UNSTATUS};
}else {
stringArray = context.getResources().getStringArray(R.array.more_action);
stringArrayConf = context.getResources().getStringArray(R.array.more_action_confirm);
doAction = new API.StatusAction[]{API.StatusAction.MUTE,API.StatusAction.BLOCK,API.StatusAction.REPORT};
}
final ArrayAdapter<String> arrayAdapter = new ArrayAdapter<>(context, android.R.layout.simple_list_item_1, stringArray);
builderSingle.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
dialog.dismiss();
}
});
builderSingle.setAdapter(arrayAdapter, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog, int which) {
AlertDialog.Builder builderInner = new AlertDialog.Builder(context);
builderInner.setTitle(stringArrayConf[which]);
if( isOwner) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
builderInner.setMessage(Html.fromHtml(status.getContent(), Html.FROM_HTML_MODE_COMPACT));
else
//noinspection deprecation
builderInner.setMessage(Html.fromHtml(status.getContent()));
}else {
if( which < 2 ){
builderInner.setMessage(status.getAccount().getAcct());
}else {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N)
builderInner.setMessage(Html.fromHtml(status.getContent(), Html.FROM_HTML_MODE_COMPACT));
else
//noinspection deprecation
builderInner.setMessage(Html.fromHtml(status.getContent()));
}
}
//Text for report
EditText input = null;
final int position = which;
if( doAction[which] == API.StatusAction.REPORT){
input = new EditText(context);
LinearLayout.LayoutParams lp = new LinearLayout.LayoutParams(
LinearLayout.LayoutParams.MATCH_PARENT,
LinearLayout.LayoutParams.WRAP_CONTENT);
input.setLayoutParams(lp);
builderInner.setView(input);
}
builderInner.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,int which) {
dialog.dismiss();
}
});
final EditText finalInput = input;
builderInner.setPositiveButton(R.string.yes, new DialogInterface.OnClickListener() {
@Override
public void onClick(DialogInterface dialog,int which) {
API.StatusAction statusAction = doAction[position];
if(statusAction == API.StatusAction.REPORT || statusAction == API.StatusAction.CREATESTATUS){
String comment = null;
if( finalInput != null && finalInput.getText() != null)
comment = finalInput.getText().toString();
new PostActionAsyncTask(context, statusAction, status.getId(), status, comment, StatusListAdapter.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}else{
String targetedId;
if( doAction[position] == API.StatusAction.FAVOURITE ||
doAction[position] == API.StatusAction.UNFAVOURITE ||
doAction[position] == API.StatusAction.REBLOG ||
doAction[position] == API.StatusAction.UNREBLOG ||
doAction[position] == API.StatusAction.UNSTATUS
)
targetedId = status.getId();
else
targetedId = status.getAccount().getId();
new PostActionAsyncTask(context, statusAction, targetedId, StatusListAdapter.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
dialog.dismiss();
}
});
builderInner.show();
}
});
builderSingle.show();
}
}

View File

@ -0,0 +1,188 @@
package fr.gouv.etalab.mastodon.fragments;
/* 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.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import fr.gouv.etalab.mastodon.client.Entities.Account;
import fr.gouv.etalab.mastodon.helper.Helper;
import mastodon.etalab.gouv.fr.mastodon.R;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveAccountsAsyncTask;
import fr.gouv.etalab.mastodon.drawers.AccountsListAdapter;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveAccountsInterface;
/**
* Created by Thomas on 27/04/2017.
* Fragment to display content related to accounts
*/
public class DisplayAccountsFragment extends Fragment implements OnRetrieveAccountsInterface {
private TextView noAction;
private boolean flag_loading;
private Context context;
private AsyncTask<Void, Void, Void> asyncTask;
private AccountsListAdapter accountsListAdapter;
private String max_id;
private List<Account> accounts;
private RetrieveAccountsAsyncTask.Type type;
private RelativeLayout mainLoader, nextElementLoader, textviewNoAction;
private boolean firstLoad;
private SwipeRefreshLayout swipeRefreshLayout;
private int accountPerPage;
private String targetedId;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_accounts, container, false);
context = getContext();
Bundle bundle = this.getArguments();
if (bundle != null) {
type = (RetrieveAccountsAsyncTask.Type) bundle.get("type");
targetedId = bundle.getString("targetedId", null);
}
max_id = null;
firstLoad = true;
flag_loading = true;
accounts = new ArrayList<>();
swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipeContainer);
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
accountPerPage = sharedpreferences.getInt(Helper.SET_ACCOUNTS_PER_PAGE, 40);
ListView lv_accounts = (ListView) rootView.findViewById(R.id.lv_accounts);
mainLoader = (RelativeLayout) rootView.findViewById(R.id.loader);
nextElementLoader = (RelativeLayout) rootView.findViewById(R.id.loading_next_accounts);
textviewNoAction = (RelativeLayout) rootView.findViewById(R.id.no_action);
mainLoader.setVisibility(View.VISIBLE);
nextElementLoader.setVisibility(View.GONE);
accountsListAdapter = new AccountsListAdapter(context, type, this.accounts);
lv_accounts.setAdapter(accountsListAdapter);
lv_accounts.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if(firstVisibleItem + visibleItemCount == totalItemCount ) {
if(!flag_loading ) {
flag_loading = true;
if( type != RetrieveAccountsAsyncTask.Type.FOLLOWERS && type != RetrieveAccountsAsyncTask.Type.FOLLOWING)
asyncTask = new RetrieveAccountsAsyncTask(context, type, max_id, DisplayAccountsFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
asyncTask = new RetrieveAccountsAsyncTask(context, type, targetedId, max_id, DisplayAccountsFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
nextElementLoader.setVisibility(View.VISIBLE);
}
} else {
nextElementLoader.setVisibility(View.GONE);
}
}
});
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
max_id = null;
accounts = new ArrayList<>();
firstLoad = true;
flag_loading = true;
if( type != RetrieveAccountsAsyncTask.Type.FOLLOWERS && type != RetrieveAccountsAsyncTask.Type.FOLLOWING)
asyncTask = new RetrieveAccountsAsyncTask(context, type, max_id, DisplayAccountsFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
asyncTask = new RetrieveAccountsAsyncTask(context, type, targetedId, max_id, DisplayAccountsFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
});
swipeRefreshLayout.setColorSchemeResources(R.color.colorAccent,
R.color.colorPrimary,
R.color.colorPrimaryDark);
if( type != RetrieveAccountsAsyncTask.Type.FOLLOWERS && type != RetrieveAccountsAsyncTask.Type.FOLLOWING)
asyncTask = new RetrieveAccountsAsyncTask(context, type, max_id, DisplayAccountsFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
asyncTask = new RetrieveAccountsAsyncTask(context, type, targetedId, max_id, DisplayAccountsFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
return rootView;
}
@Override
public void onCreate(Bundle saveInstance)
{
super.onCreate(saveInstance);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
this.context = context;
}
public void onStop() {
super.onStop();
if(asyncTask != null && asyncTask.getStatus() == AsyncTask.Status.RUNNING)
asyncTask.cancel(true);
}
@Override
public void onRetrieveAccounts(List<Account> accounts) {
if( firstLoad && (accounts == null || accounts.size() == 0))
textviewNoAction.setVisibility(View.VISIBLE);
else
textviewNoAction.setVisibility(View.GONE);
if( accounts != null && accounts.size() > 1)
max_id =accounts.get(accounts.size()-1).getId();
else
max_id = null;
mainLoader.setVisibility(View.GONE);
nextElementLoader.setVisibility(View.GONE);
if( accounts != null) {
for(Account tmpAccount: accounts){
this.accounts.add(tmpAccount);
}
accountsListAdapter.notifyDataSetChanged();
}
swipeRefreshLayout.setRefreshing(false);
firstLoad = false;
if( accounts != null && accounts.size() < accountPerPage )
flag_loading = true;
else
flag_loading = false;
}
}

View File

@ -0,0 +1,170 @@
package fr.gouv.etalab.mastodon.fragments;
/* 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.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.RelativeLayout;
import java.util.ArrayList;
import java.util.List;
import fr.gouv.etalab.mastodon.drawers.NotificationsListAdapter;
import fr.gouv.etalab.mastodon.helper.Helper;
import mastodon.etalab.gouv.fr.mastodon.R;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveNotificationsAsyncTask;
import fr.gouv.etalab.mastodon.client.Entities.Notification;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveNotificationsInterface;
/**
* Created by Thomas on 28/04/2017.
* Fragment to display notifications related to accounts
*/
public class DisplayNotificationsFragment extends Fragment implements OnRetrieveNotificationsInterface {
private boolean flag_loading;
private Context context;
private AsyncTask<Void, Void, Void> asyncTask;
private NotificationsListAdapter notificationsListAdapter;
private String max_id = null;
private List<Notification> notifications;
private RelativeLayout mainLoader, nextElementLoader, textviewNoAction;
private boolean firstLoad;
private SwipeRefreshLayout swipeRefreshLayout;
private int notificationPerPage;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_notifications, container, false);
context = getContext();
firstLoad = true;
flag_loading = true;
notifications = new ArrayList<>();
swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipeContainer);
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
notificationPerPage = sharedpreferences.getInt(Helper.SET_NOTIFICATIONS_PER_PAGE, 40);
ListView lv_notifications = (ListView) rootView.findViewById(R.id.lv_notifications);
mainLoader = (RelativeLayout) rootView.findViewById(R.id.loader);
nextElementLoader = (RelativeLayout) rootView.findViewById(R.id.loading_next_notifications);
textviewNoAction = (RelativeLayout) rootView.findViewById(R.id.no_action);
mainLoader.setVisibility(View.VISIBLE);
nextElementLoader.setVisibility(View.GONE);
notificationsListAdapter = new NotificationsListAdapter(context, this.notifications);
lv_notifications.setAdapter(notificationsListAdapter);
lv_notifications.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if(firstVisibleItem + visibleItemCount == totalItemCount ) {
if(!flag_loading ) {
flag_loading = true;
asyncTask = new RetrieveNotificationsAsyncTask(context, max_id, null,DisplayNotificationsFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
nextElementLoader.setVisibility(View.VISIBLE);
}
} else {
nextElementLoader.setVisibility(View.GONE);
}
}
});
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
max_id = null;
notifications = new ArrayList<>();
firstLoad = true;
flag_loading = true;
asyncTask = new RetrieveNotificationsAsyncTask(context, max_id, null,DisplayNotificationsFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
});
swipeRefreshLayout.setColorSchemeResources(R.color.colorAccent,
R.color.colorPrimary,
R.color.colorPrimaryDark);
asyncTask = new RetrieveNotificationsAsyncTask(context, max_id, null, DisplayNotificationsFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
return rootView;
}
@Override
public void onCreate(Bundle saveInstance)
{
super.onCreate(saveInstance);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
this.context = context;
}
public void onStop() {
super.onStop();
if(asyncTask != null && asyncTask.getStatus() == AsyncTask.Status.RUNNING)
asyncTask.cancel(true);
}
@Override
public void onRetrieveNotifications(List<Notification> notifications, String acct) {
if( firstLoad && (notifications == null || notifications.size() == 0))
textviewNoAction.setVisibility(View.VISIBLE);
else
textviewNoAction.setVisibility(View.GONE);
if( notifications != null && notifications.size() > 1)
max_id =notifications.get(notifications.size()-1).getId();
else
max_id = null;
mainLoader.setVisibility(View.GONE);
nextElementLoader.setVisibility(View.GONE);
if( notifications != null) {
for(Notification tmpNotification: notifications){
this.notifications.add(tmpNotification);
}
notificationsListAdapter.notifyDataSetChanged();
}
swipeRefreshLayout.setRefreshing(false);
firstLoad = false;
if( notifications != null && notifications.size() < notificationPerPage )
flag_loading = true;
else
flag_loading = false;
}
}

View File

@ -0,0 +1,201 @@
package fr.gouv.etalab.mastodon.fragments;
/* 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.content.Context;
import android.content.SharedPreferences;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v4.widget.SwipeRefreshLayout;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.AbsListView;
import android.widget.ListView;
import android.widget.RelativeLayout;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import fr.gouv.etalab.mastodon.drawers.StatusListAdapter;
import fr.gouv.etalab.mastodon.helper.Helper;
import mastodon.etalab.gouv.fr.mastodon.R;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveFeedsAsyncTask;
import fr.gouv.etalab.mastodon.client.Entities.Status;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveFeedsInterface;
/**
* Created by Thomas on 24/04/2017.
* Fragment to display content related to status
*/
public class DisplayStatusFragment extends Fragment implements OnRetrieveFeedsInterface {
private TextView noAction;
private boolean flag_loading;
private Context context;
private AsyncTask<Void, Void, Void> asyncTask;
private StatusListAdapter statusListAdapter;
private String max_id;
private List<Status> statuses;
private static RetrieveFeedsAsyncTask.Type type;
private RelativeLayout mainLoader, nextElementLoader, textviewNoAction;
private boolean firstLoad;
private SwipeRefreshLayout swipeRefreshLayout;
private int tootPerPage;
private String targetedId;
private String tag;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_status, container, false);
context = getContext();
Bundle bundle = this.getArguments();
if (bundle != null) {
type = (RetrieveFeedsAsyncTask.Type) bundle.get("type");
targetedId = bundle.getString("targetedId", null);
tag = bundle.getString("tag", null);
}
max_id = null;
flag_loading = true;
firstLoad = true;
statuses = new ArrayList<>();
boolean isOnWifi = Helper.isOnWIFI(context);
swipeRefreshLayout = (SwipeRefreshLayout) rootView.findViewById(R.id.swipeContainer);
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
tootPerPage = sharedpreferences.getInt(Helper.SET_TOOTS_PER_PAGE, 40);
int behaviorWithAttachments = sharedpreferences.getInt(Helper.SET_ATTACHMENT_ACTION, Helper.ATTACHMENT_ALWAYS);
ListView lv_status = (ListView) rootView.findViewById(R.id.lv_status);
mainLoader = (RelativeLayout) rootView.findViewById(R.id.loader);
nextElementLoader = (RelativeLayout) rootView.findViewById(R.id.loading_next_status);
textviewNoAction = (RelativeLayout) rootView.findViewById(R.id.no_action);
mainLoader.setVisibility(View.VISIBLE);
nextElementLoader.setVisibility(View.GONE);
statusListAdapter = new StatusListAdapter(context, type, isOnWifi, behaviorWithAttachments, this.statuses);
lv_status.setAdapter(statusListAdapter);
lv_status.setOnScrollListener(new AbsListView.OnScrollListener() {
@Override
public void onScrollStateChanged(AbsListView view, int scrollState) {
}
public void onScroll(AbsListView view, int firstVisibleItem, int visibleItemCount, int totalItemCount) {
if(firstVisibleItem + visibleItemCount == totalItemCount ) {
if(!flag_loading ) {
flag_loading = true;
if( type == RetrieveFeedsAsyncTask.Type.USER)
asyncTask = new RetrieveFeedsAsyncTask(context, type, targetedId, max_id, DisplayStatusFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
if( type == RetrieveFeedsAsyncTask.Type.TAG)
asyncTask = new RetrieveFeedsAsyncTask(context, type, tag, targetedId, max_id, DisplayStatusFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
asyncTask = new RetrieveFeedsAsyncTask(context, type, max_id, DisplayStatusFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
nextElementLoader.setVisibility(View.VISIBLE);
}
} else {
nextElementLoader.setVisibility(View.GONE);
}
}
});
swipeRefreshLayout.setOnRefreshListener(new SwipeRefreshLayout.OnRefreshListener() {
@Override
public void onRefresh() {
max_id = null;
statuses = new ArrayList<>();
firstLoad = true;
flag_loading = true;
if( type == RetrieveFeedsAsyncTask.Type.USER)
asyncTask = new RetrieveFeedsAsyncTask(context, type, targetedId, max_id, DisplayStatusFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
if( type == RetrieveFeedsAsyncTask.Type.TAG)
asyncTask = new RetrieveFeedsAsyncTask(context, type, tag, targetedId, max_id, DisplayStatusFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
asyncTask = new RetrieveFeedsAsyncTask(context, type, max_id, DisplayStatusFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
});
swipeRefreshLayout.setColorSchemeResources(R.color.colorAccent,
R.color.colorPrimary,
R.color.colorPrimaryDark);
if( type == RetrieveFeedsAsyncTask.Type.USER)
asyncTask = new RetrieveFeedsAsyncTask(context, type, targetedId, max_id, DisplayStatusFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
if( type == RetrieveFeedsAsyncTask.Type.TAG)
asyncTask = new RetrieveFeedsAsyncTask(context, type, tag, targetedId, max_id, DisplayStatusFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
else
asyncTask = new RetrieveFeedsAsyncTask(context, type, max_id, DisplayStatusFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
return rootView;
}
@Override
public void onCreate(Bundle saveInstance)
{
super.onCreate(saveInstance);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
this.context = context;
}
public void onStop() {
super.onStop();
if(asyncTask != null && asyncTask.getStatus() == AsyncTask.Status.RUNNING)
asyncTask.cancel(true);
}
@Override
public void onRetrieveFeeds(List<Status> statuses) {
if( firstLoad && (statuses == null || statuses.size() == 0))
textviewNoAction.setVisibility(View.VISIBLE);
else
textviewNoAction.setVisibility(View.GONE);
if( statuses != null && statuses.size() > 1)
max_id =statuses.get(statuses.size()-1).getId();
else
max_id = null;
mainLoader.setVisibility(View.GONE);
nextElementLoader.setVisibility(View.GONE);
if( statuses != null) {
for(Status tmpStatus: statuses){
this.statuses.add(tmpStatus);
}
statusListAdapter.notifyDataSetChanged();
}
swipeRefreshLayout.setRefreshing(false);
firstLoad = false;
if( statuses != null && statuses.size() < tootPerPage )
flag_loading = true;
else
flag_loading = false;
}
}

View File

@ -0,0 +1,153 @@
package fr.gouv.etalab.mastodon.fragments;
/* 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.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.support.v7.widget.SwitchCompat;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.CheckBox;
import android.widget.CompoundButton;
import fr.gouv.etalab.mastodon.helper.Helper;
import mastodon.etalab.gouv.fr.mastodon.R;
/**
* Created by Thomas on 29/04/2017.
* Fragment for settings, yes I didn't use PreferenceFragment :)
*/
public class SettingsNotificationsFragment extends Fragment {
private Context context;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_settings_notifications, container, false);
context = getContext();
final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true);
boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true);
boolean notif_ask = sharedpreferences.getBoolean(Helper.SET_NOTIF_ASK, true);
boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true);
boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true);
boolean notif_validation = sharedpreferences.getBoolean(Helper.SET_NOTIF_VALIDATION, true);
boolean notif_wifi = sharedpreferences.getBoolean(Helper.SET_WIFI_ONLY, false);
final CheckBox set_notif_follow = (CheckBox) rootView.findViewById(R.id.set_notif_follow);
final CheckBox set_notif_follow_add = (CheckBox) rootView.findViewById(R.id.set_notif_follow_add);
final CheckBox set_notif_follow_ask = (CheckBox) rootView.findViewById(R.id.set_notif_follow_ask);
final CheckBox set_notif_follow_mention = (CheckBox) rootView.findViewById(R.id.set_notif_follow_mention);
final CheckBox set_notif_follow_share = (CheckBox) rootView.findViewById(R.id.set_notif_follow_share);
final CheckBox set_share_validation = (CheckBox) rootView.findViewById(R.id.set_share_validation);
final SwitchCompat switchCompatWIFI = (SwitchCompat) rootView.findViewById(R.id.set_wifi_only);
set_notif_follow.setChecked(notif_follow);
set_notif_follow_add.setChecked(notif_add);
set_notif_follow_ask.setChecked(notif_ask);
set_notif_follow_mention.setChecked(notif_mention);
set_notif_follow_share.setChecked(notif_share);
set_share_validation.setChecked(notif_validation);
switchCompatWIFI.setChecked(notif_wifi);
set_notif_follow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putBoolean(Helper.SET_NOTIF_FOLLOW, set_notif_follow.isChecked());
editor.apply();
}
});
set_notif_follow_add.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putBoolean(Helper.SET_NOTIF_ADD, set_notif_follow_add.isChecked());
editor.apply();
}
});
set_notif_follow_ask.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putBoolean(Helper.SET_NOTIF_ASK, set_notif_follow_ask.isChecked());
editor.apply();
}
});
set_notif_follow_mention.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putBoolean(Helper.SET_NOTIF_MENTION, set_notif_follow_mention.isChecked());
editor.apply();
}
});
set_notif_follow_share.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putBoolean(Helper.SET_NOTIF_SHARE, set_notif_follow_share.isChecked());
editor.apply();
}
});
set_share_validation.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putBoolean(Helper.SET_NOTIF_VALIDATION, set_share_validation.isChecked());
editor.apply();
}
});
switchCompatWIFI.setOnCheckedChangeListener(new CompoundButton.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(CompoundButton buttonView, boolean isChecked) {
// Save the state here
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putBoolean(Helper.SET_WIFI_ONLY, isChecked);
editor.apply();
}
});
return rootView;
}
@Override
public void onCreate(Bundle saveInstance) {
super.onCreate(saveInstance);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
this.context = context;
}
}

View File

@ -0,0 +1,172 @@
package fr.gouv.etalab.mastodon.fragments;
/* 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.content.Context;
import android.content.SharedPreferences;
import android.os.Bundle;
import android.support.v4.app.Fragment;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.RadioGroup;
import android.widget.SeekBar;
import android.widget.TextView;
import fr.gouv.etalab.mastodon.helper.Helper;
import mastodon.etalab.gouv.fr.mastodon.R;
/**
* Created by Thomas on 25/04/2017.
* Fragment for settings, yes I didn't use PreferenceFragment :)
*/
public class SettingsOptimizationFragment extends Fragment {
private Context context;
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) {
View rootView = inflater.inflate(R.layout.fragment_settings_optimization, container, false);
context = getContext();
final SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
//Status per page
SeekBar statusSeekBar = (SeekBar) rootView.findViewById(R.id.set_toots_per_page);
final TextView set_toots_page_value = (TextView) rootView.findViewById(R.id.set_toots_page_value);
statusSeekBar.setMax(30);
int tootPerPage = sharedpreferences.getInt(Helper.SET_TOOTS_PER_PAGE, 40);
statusSeekBar.setProgress(tootPerPage-10);
set_toots_page_value.setText(String.valueOf(tootPerPage));
statusSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int value = 10 + progress;
set_toots_page_value.setText(String.valueOf(value));
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putInt(Helper.SET_TOOTS_PER_PAGE, value);
editor.apply();
}
});
//Accounts per page
SeekBar accountsSeekBar = (SeekBar) rootView.findViewById(R.id.set_accounts_per_page);
final TextView set_accounts_page_value = (TextView) rootView.findViewById(R.id.set_accounts_page_value);
accountsSeekBar.setMax(30);
int accountsPerPage = sharedpreferences.getInt(Helper.SET_ACCOUNTS_PER_PAGE, 40);
accountsSeekBar.setProgress(accountsPerPage-10);
set_accounts_page_value.setText(String.valueOf(accountsPerPage));
accountsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int value = 10 + progress;
set_accounts_page_value.setText(String.valueOf(value));
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putInt(Helper.SET_ACCOUNTS_PER_PAGE, value);
editor.apply();
}
});
//Notifications per page
SeekBar notificationsSeekBar = (SeekBar) rootView.findViewById(R.id.set_notifications_per_page);
final TextView set_notifications_page_value = (TextView) rootView.findViewById(R.id.set_notifications_page_value);
notificationsSeekBar.setMax(30);
int notificationsPerPage = sharedpreferences.getInt(Helper.SET_NOTIFICATIONS_PER_PAGE, 40);
notificationsSeekBar.setProgress(notificationsPerPage-10);
set_notifications_page_value.setText(String.valueOf(notificationsPerPage));
notificationsSeekBar.setOnSeekBarChangeListener(new SeekBar.OnSeekBarChangeListener() {
@Override
public void onStopTrackingTouch(SeekBar seekBar) {}
@Override
public void onStartTrackingTouch(SeekBar seekBar) {}
@Override
public void onProgressChanged(SeekBar seekBar, int progress, boolean fromUser) {
int value = 10 + progress;
set_notifications_page_value.setText(String.valueOf(value));
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putInt(Helper.SET_NOTIFICATIONS_PER_PAGE, value);
editor.apply();
}
});
//Manage download of attachments
RadioGroup radioGroup = (RadioGroup) rootView.findViewById(R.id.set_attachment_group);
int attachmentAction = sharedpreferences.getInt(Helper.SET_ATTACHMENT_ACTION, Helper.ATTACHMENT_ALWAYS);
switch (attachmentAction){
case Helper.ATTACHMENT_ALWAYS:
radioGroup.check(R.id.set_attachment_always);
break;
case Helper.ATTACHMENT_WIFI:
radioGroup.check(R.id.set_attachment_wifi);
break;
case Helper.ATTACHMENT_ASK:
radioGroup.check(R.id.set_attachment_ask);
break;
}
radioGroup.setOnCheckedChangeListener(new RadioGroup.OnCheckedChangeListener() {
@Override
public void onCheckedChanged(RadioGroup group, int checkedId) {
switch(checkedId) {
case R.id.set_attachment_always:
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putInt(Helper.SET_ATTACHMENT_ACTION, Helper.ATTACHMENT_ALWAYS);
editor.apply();
break;
case R.id.set_attachment_wifi:
editor = sharedpreferences.edit();
editor.putInt(Helper.SET_ATTACHMENT_ACTION, Helper.ATTACHMENT_WIFI);
editor.apply();
break;
case R.id.set_attachment_ask:
editor = sharedpreferences.edit();
editor.putInt(Helper.SET_ATTACHMENT_ACTION, Helper.ATTACHMENT_ASK);
editor.apply();
break;
}
}
});
return rootView;
}
@Override
public void onCreate(Bundle saveInstance) {
super.onCreate(saveInstance);
}
@Override
public void onAttach(Context context) {
super.onAttach(context);
this.context = context;
}
}

View File

@ -0,0 +1,99 @@
package fr.gouv.etalab.mastodon.fragments;
/* 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.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.ViewPager;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import mastodon.etalab.gouv.fr.mastodon.R;
/**
* Created by Thomas on 29/04/2017.
* Tablayout selection for settings
*/
public class TabLayoutSettingsFragment extends Fragment {
@Override
public View onCreateView(LayoutInflater inflater, ViewGroup container,
Bundle savedInstanceState) {
View inflatedView = inflater.inflate(R.layout.tablayout_settings, container, false);
TabLayout tabLayout = (TabLayout) inflatedView.findViewById(R.id.tabLayout);
tabLayout.addTab(tabLayout.newTab().setText(getString(R.string.notifications)));
tabLayout.addTab(tabLayout.newTab().setText(getString(R.string.optimization)));
final ViewPager viewPager = (ViewPager) inflatedView.findViewById(R.id.viewpager);
viewPager.setAdapter(new PagerAdapter
(getChildFragmentManager(), tabLayout.getTabCount()));
viewPager.addOnPageChangeListener(new TabLayout.TabLayoutOnPageChangeListener(tabLayout));
tabLayout.addOnTabSelectedListener(new TabLayout.OnTabSelectedListener() {
@Override
public void onTabSelected(TabLayout.Tab tab) {
viewPager.setCurrentItem(tab.getPosition());
}
@Override
public void onTabUnselected(TabLayout.Tab tab) {
}
@Override
public void onTabReselected(TabLayout.Tab tab) {
}
});
return inflatedView;
}
/**
* Page Adapter for settings
*/
private class PagerAdapter extends FragmentStatePagerAdapter {
int mNumOfTabs;
private PagerAdapter(FragmentManager fm, int NumOfTabs) {
super(fm);
this.mNumOfTabs = NumOfTabs;
}
@Override
public Fragment getItem(int position) {
switch (position) {
case 0:
return new SettingsNotificationsFragment();
case 1:
return new SettingsOptimizationFragment();
default:
return new SettingsNotificationsFragment();
}
}
@Override
public int getCount() {
return mNumOfTabs;
}
}
}

View File

@ -0,0 +1,359 @@
/* 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.helper;
import android.app.AlertDialog;
import android.app.DownloadManager;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
import android.net.Uri;
import android.os.Build;
import android.os.Environment;
import android.view.WindowManager;
import android.widget.Toast;
import java.io.File;
import java.net.InetAddress;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Locale;
import java.util.TimeZone;
import mastodon.etalab.gouv.fr.mastodon.R;
import fr.gouv.etalab.mastodon.client.API;
import static android.content.Context.DOWNLOAD_SERVICE;
/**
* Created by Thomas on 23/04/2017.
* - Constants are defined here.
* - Reusable methods are implemented in this section
*/
public class Helper {
public static final String TAG = "mastodon_etalab";
//Connection with API
public static final String OAUTH_SCHEME = "oauth2redirect";
public static final String OAUTH_REDIRECT_HOST = "fr.gouv.etalab.mastodon";
public static final String INSTANCE = "mastodon.etalab.gouv.fr";
public static final String OAUTH_SCOPES = "read write follow";
public static final String PREF_KEY_OAUTH_TOKEN = "oauth_token";
public static final String PREF_KEY_ID = "userID";
public static final String REDIRECT_CONTENT = "/redirect_mastodon_api";
public static final int EXTERNAL_STORAGE_REQUEST_CODE = 84;
//Some definitions
public static final String CLIENT_NAME = "client_name";
public static final String APP_PREFS = "app_prefs";
public static final String ID = "id";
public static final String CLIENT_ID = "client_id";
public static final String CLIENT_SECRET = "client_secret";
public static final String REDIRECT_URI = "redirect_uri";
public static final String REDIRECT_URIS = "redirect_uris";
public static final String RESPONSE_TYPE = "response_type";
public static final String SCOPE = "scope";
public static final String SCOPES = "scopes";
public static final String WEBSITE = "website";
public static final String LAST_NOTIFICATION_MAX_ID = "last_notification_max_id";
//Notifications
public static final String NOTIFICATION_TYPE = "notification_type";
public static final int NOTIFICATION_INTENT = 1;
//Settings
public static final String SET_TOOTS_PER_PAGE = "set_toots_per_page";
public static final String SET_ACCOUNTS_PER_PAGE = "set_accounts_per_page";
public static final String SET_NOTIFICATIONS_PER_PAGE = "set_notifications_per_page";
public static final String SET_ATTACHMENT_ACTION = "set_attachment_action";
public static final int ATTACHMENT_ALWAYS = 1;
public static final int ATTACHMENT_WIFI = 2;
public static final int ATTACHMENT_ASK = 3;
public static final String SET_NOTIF_FOLLOW = "set_notif_follow";
public static final String SET_NOTIF_ADD = "set_notif_follow_add";
public static final String SET_NOTIF_ASK = "set_notif_follow_ask";
public static final String SET_NOTIF_MENTION = "set_notif_follow_mention";
public static final String SET_NOTIF_SHARE = "set_notif_follow_share";
public static final String SET_NOTIF_VALIDATION = "set_share_validation";
public static final String SET_WIFI_ONLY = "set_wifi_only";
public static final String SET_NOTIF_SILENT = "set_notif_silent";
//End points
public static final String EP_AUTHORIZE = "/oauth/authorize";
//Refresh job
public static final int MINUTES_BETWEEN_NOTIFICATIONS_REFRESH = 15;
//Intent
public static final String INTENT_ACTION = "intent_action";
public static final int INTENT_NOTIFICATION = 1;
//Receiver
public static final String SEARCH_VALIDATE_ACCOUNT = "search_validate_account";
/***
* Check if the user is connected to Internet
* @return boolean
*/
public static boolean isConnectingToInternet(Context context) {
ConnectivityManager cm = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo ni = cm.getActiveNetworkInfo();
if ( ni != null && ni.isConnected()) {
try {
//Google is used for the ping
InetAddress ipAddr = InetAddress.getByName("google.com");
return !ipAddr.toString().equals("");
} catch (Exception e) {
return false;
}
} else {
return false;
}
}
/**
* Returns boolean depending if the user is authenticated
* @param context Context
* @return boolean
*/
public static boolean isLoggedIn(Context context) {
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
String prefKeyOauthTokenT = sharedpreferences.getString(PREF_KEY_OAUTH_TOKEN, null);
return ( prefKeyOauthTokenT != null);
}
/**
* Log out the authenticated user by removing its token
* @param context Context
*/
public static void logout(Context context) {
SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.PREF_KEY_OAUTH_TOKEN, null);
editor.putString(Helper.CLIENT_ID, null);
editor.putString(Helper.CLIENT_SECRET, null);
editor.putString(Helper.PREF_KEY_ID, null);
editor.putString(Helper.ID, null);
editor.apply();
}
/**
* Convert String date from Mastodon
* @param context Context
* @param date String
* @return Date
*/
public static Date mstStringToDate(Context context, String date){
Locale userLocale;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
userLocale = context.getResources().getConfiguration().getLocales().get(0);
} else {
//noinspection deprecation
userLocale = context.getResources().getConfiguration().locale;
}
final String STRING_DATE_FORMAT = "yyyy-MM-dd'T'HH:mm:ss.SSS'Z'";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(STRING_DATE_FORMAT, userLocale);
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("gmt"));
simpleDateFormat.setLenient(true);
try {
return simpleDateFormat.parse(date);
} catch (ParseException e) {
return null;
}
}
/**
* Convert a date in String -> format yyyy-MM-dd HH:mm:ss
* @param context Context
* @param date Date
* @return String
*/
public static String dateToString(Context context, Date date) {
Locale userLocale;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
userLocale = context.getResources().getConfiguration().getLocales().get(0);
} else {
//noinspection deprecation
userLocale = context.getResources().getConfiguration().locale;
}
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",userLocale);
return dateFormat.format(date);
}
/**
* Convert String date from db to Date Object
* @param stringDate date to convert
* @return Date
*/
public static Date stringToDate(Context context, String stringDate) {
if( stringDate == null)
return null;
Locale userLocale;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
userLocale = context.getResources().getConfiguration().getLocales().get(0);
} else {
//noinspection deprecation
userLocale = context.getResources().getConfiguration().locale;
}
SimpleDateFormat dateFormat = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss",userLocale);
Date date = null;
try {
date = dateFormat.parse(stringDate);
} catch (java.text.ParseException ignored) {
}
return date;
}
/**
* Check if WIFI is opened
* @param context Context
* @return boolean
*/
public static boolean isOnWIFI(Context context) {
ConnectivityManager connManager = (ConnectivityManager) context.getSystemService(Context.CONNECTIVITY_SERVICE);
NetworkInfo activeNetwork = connManager.getActiveNetworkInfo();
return (activeNetwork != null && activeNetwork.getType() == ConnectivityManager.TYPE_WIFI);
}
/***
* Returns a String depending of the date
* @param context Context
* @param dateToot Date
* @return String
*/
public static String dateDiff(Context context, Date dateToot){
Date now = new Date();
long diff = now.getTime() - dateToot.getTime();
long seconds = diff / 1000;
long minutes = seconds / 60;
long hours = minutes / 60;
long days = hours / 24;
long months = days / 30;
long years = days / 365;
if( years > 0)
return context.getResources().getQuantityString(R.plurals.date_year, (int)years, (int)years);
else if( months > 0)
return context.getResources().getQuantityString(R.plurals.date_month, (int)months, (int)months);
else if( days > 2)
return context.getString(R.string.date_day,days);
else if(days == 2 )
return context.getString(R.string.date_day_before_yesterday);
else if(days == 1 )
return context.getString(R.string.date_yesterday);
else if(hours > 0)
return context.getResources().getQuantityString(R.plurals.date_hours, (int)hours, (int)hours);
else if(minutes > 0)
return context.getResources().getQuantityString(R.plurals.date_minutes, (int)minutes, (int)minutes);
else
return context.getResources().getQuantityString(R.plurals.date_seconds, (int)seconds, (int)seconds);
}
/***
* Toast message depending of the status code and the initial action
* @param context Context
* @param statusCode int the status code
* @param statusAction API.StatusAction the initial action
*/
public static void manageMessageStatusCode(Context context, int statusCode,API.StatusAction statusAction){
String message = "";
if( statusCode == 200){
if( statusAction == API.StatusAction.BLOCK){
message = context.getString(R.string.toast_block);
}else if(statusAction == API.StatusAction.UNBLOCK){
message = context.getString(R.string.toast_unblock);
}else if(statusAction == API.StatusAction.REBLOG){
message = context.getString(R.string.toast_reblog);
}else if(statusAction == API.StatusAction.UNREBLOG){
message = context.getString(R.string.toast_unreblog);
}else if(statusAction == API.StatusAction.MUTE){
message = context.getString(R.string.toast_mute);
}else if(statusAction == API.StatusAction.UNMUTE){
message = context.getString(R.string.toast_unmute);
}else if(statusAction == API.StatusAction.FOLLOW){
message = context.getString(R.string.toast_follow);
}else if(statusAction == API.StatusAction.UNFOLLOW){
message = context.getString(R.string.toast_unfollow);
}else if(statusAction == API.StatusAction.FAVOURITE){
message = context.getString(R.string.toast_favourite);
}else if(statusAction == API.StatusAction.UNFAVOURITE){
message = context.getString(R.string.toast_unfavourite);
}else if(statusAction == API.StatusAction.REPORT){
message = context.getString(R.string.toast_report);
}else if(statusAction == API.StatusAction.UNSTATUS){
message = context.getString(R.string.toast_unstatus);
}
}else {
message = context.getString(R.string.toast_error);
}
if( !message.trim().equals(""))
Toast.makeText(context, message, Toast.LENGTH_LONG).show();
}
public static void manageDownloads(final Context context, final String url){
final AlertDialog.Builder builder = new AlertDialog.Builder(context);
final DownloadManager.Request request = new DownloadManager.Request(Uri.parse(url));
Uri uri = Uri.parse(url);
File f = new File("" + uri);
final String fileName = f.getName();
builder.setMessage(context.getResources().getString(R.string.download_file, fileName));
builder.setCancelable(false)
.setPositiveButton(context.getString(R.string.yes), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
request.allowScanningByMediaScanner();
request.setDestinationInExternalPublicDir(Environment.DIRECTORY_DOWNLOADS,fileName);
request.setNotificationVisibility(DownloadManager.Request.VISIBILITY_VISIBLE_NOTIFY_COMPLETED);
DownloadManager dm = (DownloadManager) context.getSystemService(DOWNLOAD_SERVICE);
dm.enqueue(request);
dialog.dismiss();
}
})
.setNegativeButton(context.getString(R.string.cancel), new DialogInterface.OnClickListener() {
public void onClick(DialogInterface dialog, int id) {
dialog.cancel();
}
});
AlertDialog alert = builder.create();
if( alert.getWindow() != null )
alert.getWindow().setSoftInputMode(WindowManager.LayoutParams.SOFT_INPUT_STATE_VISIBLE);
alert.show();
}
}

View File

@ -0,0 +1,26 @@
/* 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.interfaces;
import fr.gouv.etalab.mastodon.client.API;
/**
* Created by Thomas on 29/04/2017.
* Interface when post actions has been done with a status
*/
public interface OnPostActionInterface {
void onPostAction(int statusCode, API.StatusAction statusAction, String userId);
}

View File

@ -0,0 +1,25 @@
/* 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.interfaces;
import fr.gouv.etalab.mastodon.client.Entities.Account;
/**
* Created by Thomas on 01/05/2017.
* Interface when one account have been retrieved
*/
public interface OnRetrieveAccountInterface {
void onRetrieveAccount(Account account);
}

View File

@ -0,0 +1,28 @@
/* 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.interfaces;
import java.util.List;
import fr.gouv.etalab.mastodon.client.Entities.Account;
/**
* Created by Thomas on 27/04/2017.
* Interface when accounts have been retrieved
*/
public interface OnRetrieveAccountsInterface {
void onRetrieveAccounts(List<Account> accounts);
}

View File

@ -0,0 +1,25 @@
/* 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.interfaces;
import fr.gouv.etalab.mastodon.client.Entities.Attachment;
/**
* Created by Thomas on 01/05/2017.
* Interface when an attachment has been retrieved
*/
public interface OnRetrieveAttachmentInterface {
void onRetrieveAttachment(Attachment attachment);
}

View File

@ -0,0 +1,26 @@
/* 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.interfaces;
import fr.gouv.etalab.mastodon.client.Entities.Context;
/**
* Created by Thomas on 04/05/2017.
* Interface when a context for a status has been retrieved
*/
public interface OnRetrieveContextInterface {
void onRetrieveFeeds(Context context);
}

View File

@ -0,0 +1,28 @@
/* 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.interfaces;
import java.util.List;
import fr.gouv.etalab.mastodon.client.Entities.Status;
/**
* Created by Thomas on 01/05/2017.
* Interface when status have been retrieved for an account
*/
public interface OnRetrieveFeedsAccountInterface {
void onRetrieveFeedsAccount(List<Status> statuses);
}

View File

@ -0,0 +1,28 @@
/* 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.interfaces;
import java.util.List;
import fr.gouv.etalab.mastodon.client.Entities.Status;
/**
* Created by Thomas on 24/04/2017.
* Interface when status have been retrieved
*/
public interface OnRetrieveFeedsInterface {
void onRetrieveFeeds(List<Status> statuses);
}

View File

@ -0,0 +1,28 @@
/* 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.interfaces;
import java.util.List;
import fr.gouv.etalab.mastodon.client.Entities.Notification;
/**
* Created by Thomas on 28/04/2017.
* Interface when notifications have been retrieved
*/
public interface OnRetrieveNotificationsInterface {
void onRetrieveNotifications(List<Notification> notifications, String acct);
}

View File

@ -0,0 +1,26 @@
/* 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.interfaces;
import fr.gouv.etalab.mastodon.client.Entities.Relationship;
/**
* Created by Thomas on 01/05/2017.
* Interface when relationship has been retrieved for an account
*/
public interface OnRetrieveRelationshipInterface {
void onRetrieveRelationship(Relationship relationship);
}

View File

@ -0,0 +1,25 @@
/* 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.interfaces;
import fr.gouv.etalab.mastodon.client.Entities.Results;
/**
* Created by Thomas on 05/05/2017.
* Interface for search
*/
public interface OnRetrieveSearchInterface {
void onRetrieveSearch(Results results);
}

View File

@ -0,0 +1,24 @@
/* 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.interfaces;
/**
* Created by Thomas on 03/05/2017.
* Interface when account is updated
*/
public interface OnUpdateAccountInfoInterface {
void onUpdateAccountInfo(boolean error);
}

View File

@ -0,0 +1,39 @@
package fr.gouv.etalab.mastodon.jobs;
/* 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 com.evernote.android.job.Job;
import com.evernote.android.job.JobCreator;
import com.evernote.android.job.JobManager;
/**
* Created by Thomas on 29/04/2017.
* Notification job
*/
public class ApplicationJob implements JobCreator {
@Override
public Job create(String tag) {
switch (tag) {
case NotificationsSyncJob.NOTIFICATION_REFRESH:
return new NotificationsSyncJob();
default:
return null;
}
}
public static void cancelAllJob(String TAG){
JobManager.instance().cancelAllForTag(TAG);
}
}

View File

@ -0,0 +1,251 @@
package fr.gouv.etalab.mastodon.jobs;
/* 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.PendingIntent;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.media.RingtoneManager;
import android.os.AsyncTask;
import android.support.annotation.NonNull;
import android.support.v4.app.NotificationCompat;
import android.support.v4.app.NotificationManagerCompat;
import com.evernote.android.job.Job;
import com.evernote.android.job.JobManager;
import com.evernote.android.job.JobRequest;
import com.nostra13.universalimageloader.cache.disc.impl.UnlimitedDiskCache;
import com.nostra13.universalimageloader.core.ImageLoader;
import com.nostra13.universalimageloader.core.ImageLoaderConfiguration;
import java.io.File;
import java.util.List;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import fr.gouv.etalab.mastodon.activities.MainActivity;
import fr.gouv.etalab.mastodon.helper.Helper;
import mastodon.etalab.gouv.fr.mastodon.R;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveNotificationsAsyncTask;
import fr.gouv.etalab.mastodon.client.Entities.Account;
import fr.gouv.etalab.mastodon.client.Entities.Notification;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveNotificationsInterface;
import fr.gouv.etalab.mastodon.sqlite.AccountDAO;
import fr.gouv.etalab.mastodon.sqlite.Sqlite;
import static fr.gouv.etalab.mastodon.helper.Helper.INTENT_ACTION;
import static fr.gouv.etalab.mastodon.helper.Helper.INTENT_NOTIFICATION;
/**
* Created by Thomas on 29/04/2017.
* Notifications refresh job
*/
public class NotificationsSyncJob extends Job implements OnRetrieveNotificationsInterface{
public static final String NOTIFICATION_REFRESH = "job_notification";
private int jobId;
private int notificationId;
@NonNull
@Override
protected Result onRunJob(Params params) {
//Code refresh here
callAsynchronousTask();
return Result.SUCCESS;
}
public static int schedule(Context context, boolean updateCurrent){
Set<JobRequest> jobRequests = JobManager.instance().getAllJobRequestsForTag(NOTIFICATION_REFRESH);
if (!jobRequests.isEmpty() && !updateCurrent) {
return jobRequests.iterator().next().getJobId();
}
return new JobRequest.Builder(NotificationsSyncJob.NOTIFICATION_REFRESH)
.setPeriodic(TimeUnit.MINUTES.toMillis(Helper.MINUTES_BETWEEN_NOTIFICATIONS_REFRESH), TimeUnit.MINUTES.toMillis(5))
.setPersisted(true)
.setUpdateCurrent(updateCurrent)
.setRequiredNetworkType(JobRequest.NetworkType.CONNECTED)
.setRequirementsEnforced(false)
.build()
.schedule();
}
/**
* Task in background starts here.
*/
private void callAsynchronousTask() {
SQLiteDatabase db = Sqlite.getInstance(getContext(), Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
//If an Internet connection and user agrees with notification refresh
final SharedPreferences sharedpreferences = getContext().getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
//If WIFI only and on WIFI OR user defined any connections to use the service.
if(!sharedpreferences.getBoolean(Helper.SET_WIFI_ONLY, false) || Helper.isOnWIFI(getContext())) {
List<Account> accounts = new AccountDAO(getContext(),db).getAllAccount();
//It means there is no user in DB.
if( accounts == null )
return;
//Retrieve users in db that owner has.
for (Account account: accounts) {
String max_id = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getAcct(), null);
notificationId = (int) Math.round(Double.parseDouble(account.getId())/1000);
new RetrieveNotificationsAsyncTask(getContext(), max_id, account.getAcct(), NotificationsSyncJob.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
}
}
@Override
public void onRetrieveNotifications(List<Notification> notifications, String acct) {
if( notifications == null || notifications.size() == 0)
return;
Bitmap icon_notification = null;
final SharedPreferences sharedpreferences = getContext().getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
boolean notif_follow = sharedpreferences.getBoolean(Helper.SET_NOTIF_FOLLOW, true);
boolean notif_add = sharedpreferences.getBoolean(Helper.SET_NOTIF_ADD, true);
boolean notif_ask = sharedpreferences.getBoolean(Helper.SET_NOTIF_ASK, true);
boolean notif_mention = sharedpreferences.getBoolean(Helper.SET_NOTIF_MENTION, true);
boolean notif_share = sharedpreferences.getBoolean(Helper.SET_NOTIF_SHARE, true);
String max_id = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + acct, null);
//No previous notifications in cache, so no notification will be sent
if( max_id != null ){
int newFollows = 0;
int newAdds = 0;
int newAsks = 0;
int newMentions = 0;
int newShare = 0;
String notificationUrl = null;
String title = null;
String message = null;
for(Notification notification: notifications){
//The notification associated to max_id is discarded as it is supposed to have already been sent
if( notification.getId().equals(max_id))
continue;
switch (notification.getType()){
case "mention":
if(notif_mention){
newMentions++;
if( notificationUrl == null){
notificationUrl = notification.getAccount().getAvatar();
title = String.format("@%s %s", notification.getAccount().getAcct(),getContext().getString(R.string.notif_mention));
}
}
break;
case "reblog":
if(notif_share){
newShare++;
if( notificationUrl == null){
notificationUrl = notification.getAccount().getAvatar();
title = String.format("@%s %s", notification.getAccount().getAcct(),getContext().getString(R.string.notif_reblog));
}
}
break;
case "favourite":
if(notif_add){
newAdds++;
if( notificationUrl == null){
notificationUrl = notification.getAccount().getAvatar();
title = String.format("@%s %s", notification.getAccount().getAcct(),getContext().getString(R.string.notif_favourite));
}
}
break;
case "follow":
if(notif_follow){
newFollows++;
if( notificationUrl == null){
notificationUrl = notification.getAccount().getAvatar();
title = String.format("@%s %s", notification.getAccount().getAcct(),getContext().getString(R.string.notif_follow));
}
}
break;
default:
}
if( notificationUrl != null && icon_notification == null){
try {
ImageLoader imageLoaderNoty = ImageLoader.getInstance();
File cacheDir = new File(getContext().getCacheDir(), getContext().getString(R.string.app_name));
ImageLoaderConfiguration config = new ImageLoaderConfiguration.Builder(getContext())
.threadPoolSize(5)
.threadPriority(Thread.MIN_PRIORITY + 3)
.denyCacheImageMultipleSizesInMemory()
.diskCache(new UnlimitedDiskCache(cacheDir))
.build();
imageLoaderNoty.init(config);
icon_notification = imageLoaderNoty.loadImageSync(notificationUrl);
}catch (Exception e){
icon_notification = BitmapFactory.decodeResource(getContext().getResources(),
R.drawable.mastodon_logo);
}
}
}
int allNotifCount = newFollows + newAdds + newAsks + newMentions + newShare;
if( allNotifCount > 0){
//Some others notification
int other = allNotifCount -1;
if(other > 0 )
message = getContext().getResources().getQuantityString(R.plurals.other_notifications, other, other);
else
message = "";
notify_user(icon_notification,title,message);
}
}
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + acct, notifications.get(0).getId());
editor.apply();
}
/**
* Sends notification with intent
* @param icon Bitmap profile picture
* @param title String title of the notification
* @param message String message for the notification
*/
private void notify_user(Bitmap icon, String title, String message ) {
final SharedPreferences sharedpreferences = getContext().getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
// prepare intent which is triggered if the user click on the notification
NotificationManagerCompat notificationManager = NotificationManagerCompat.from(getContext());
final Intent intent = new Intent(getContext(), MainActivity.class);
intent.putExtra(Helper.NOTIFICATION_TYPE, Helper.NOTIFICATION_INTENT);
intent.addFlags(Intent.FLAG_ACTIVITY_SINGLE_TOP | Intent.FLAG_ACTIVITY_NEW_TASK );
intent.putExtra(INTENT_ACTION, INTENT_NOTIFICATION);
PendingIntent pIntent = PendingIntent.getActivity(getContext(), notificationId, intent, PendingIntent.FLAG_ONE_SHOT);
RingtoneManager.getDefaultUri(RingtoneManager.TYPE_NOTIFICATION);
// build notification
NotificationCompat.Builder notificationBuilder = new NotificationCompat.Builder(getContext())
.setSmallIcon(R.drawable.notification_icon)
.setTicker(message)
.setWhen(System.currentTimeMillis())
.setAutoCancel(true)
.setContentIntent(pIntent)
.setContentText(message);
if( !sharedpreferences.getBoolean(Helper.SET_NOTIF_SILENT,false) ) {
notificationBuilder.setDefaults(-1);
}
notificationBuilder.setContentTitle(title);
notificationBuilder.setLargeIcon(icon);
notificationManager.notify(notificationId, notificationBuilder.build());
}
}

View File

@ -0,0 +1,261 @@
package fr.gouv.etalab.mastodon.sqlite;
/* 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.content.ContentValues;
import android.content.Context;
import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase;
import java.util.ArrayList;
import java.util.List;
import fr.gouv.etalab.mastodon.client.Entities.Account;
import fr.gouv.etalab.mastodon.helper.Helper;
/**
* Created by Thomas on 24/04/2017.
* Manage Account in DB
*/
public class AccountDAO {
private SQLiteDatabase db;
public Context context;
public AccountDAO(Context context, SQLiteDatabase db) {
//Creation of the DB with tables
this.context = context;
this.db = db;
}
/**
* Insert an Account in database
* @param account Account
* @return boolean
*/
public boolean insertAccount(Account account)
{
ContentValues values = new ContentValues();
values.put(Sqlite.COL_USER_ID, account.getId());
values.put(Sqlite.COL_USERNAME, account.getUsername());
values.put(Sqlite.COL_ACCT, account.getAcct());
values.put(Sqlite.COL_DISPLAYED_NAME, account.getDisplay_name());
values.put(Sqlite.COL_LOCKED,account.isLocked());
values.put(Sqlite.COL_FOLLOWERS_COUNT,account.getFollowers_count());
values.put(Sqlite.COL_FOLLOWING_COUNT,account.getFollowing_count());
values.put(Sqlite.COL_STATUSES_COUNT,account.getStatuses_count());
values.put(Sqlite.COL_NOTE,account.getNote());
values.put(Sqlite.COL_URL,account.getUrl());
values.put(Sqlite.COL_AVATAR,account.getAvatar());
values.put(Sqlite.COL_AVATAR_STATIC,account.getAvatar_static());
values.put(Sqlite.COL_HEADER,account.getHeader());
values.put(Sqlite.COL_HEADER_STATIC,account.getHeader_static());
values.put(Sqlite.COL_CREATED_AT, Helper.dateToString(context, account.getCreated_at()));
values.put(Sqlite.COL_INSTANCE, account.getInstance());
if( account.getToken() != null)
values.put(Sqlite.COL_OAUTHTOKEN, account.getToken());
//Inserts account
try{
db.insert(Sqlite.TABLE_USER_ACCOUNT, null, values);
}catch (Exception e) {
return false;
}
return true;
}
/**
* Update an Account in database
* @param account Account
* @return boolean
*/
public int updateAccount(Account account)
{
ContentValues values = new ContentValues();
values.put(Sqlite.COL_ACCT, account.getAcct());
values.put(Sqlite.COL_DISPLAYED_NAME, account.getDisplay_name());
values.put(Sqlite.COL_LOCKED,account.isLocked());
values.put(Sqlite.COL_FOLLOWERS_COUNT,account.getFollowers_count());
values.put(Sqlite.COL_FOLLOWING_COUNT,account.getFollowing_count());
values.put(Sqlite.COL_STATUSES_COUNT,account.getStatuses_count());
values.put(Sqlite.COL_NOTE,account.getNote());
values.put(Sqlite.COL_URL,account.getUrl());
values.put(Sqlite.COL_AVATAR,account.getAvatar());
values.put(Sqlite.COL_AVATAR_STATIC,account.getAvatar_static());
values.put(Sqlite.COL_HEADER,account.getHeader());
values.put(Sqlite.COL_HEADER_STATIC,account.getHeader_static());
values.put(Sqlite.COL_CREATED_AT, Helper.dateToString(context, account.getCreated_at()));
values.put(Sqlite.COL_INSTANCE, account.getInstance());
if( account.getToken() != null)
values.put(Sqlite.COL_OAUTHTOKEN, account.getToken());
return db.update(Sqlite.TABLE_USER_ACCOUNT,
values, Sqlite.COL_USER_ID + " = ? AND " + Sqlite.COL_USERNAME + " =?",
new String[]{account.getId(), account.getUsername()});
}
public int removeUser(Account account){
return db.delete(Sqlite.TABLE_USER_ACCOUNT, Sqlite.COL_USER_ID + " = '" +account.getId() +
"' AND " + Sqlite.COL_USERNAME + " = '" + account.getUsername()+ "'", null);
}
/**
* Returns an Account by id
* @param accountId String
* @return Account
*/
public Account getAccountByID(String accountId){
try {
Cursor c = db.query(Sqlite.TABLE_USER_ACCOUNT, null, Sqlite.COL_USER_ID + " = '" + accountId + "'", null, null, null, null, "1");
return cursorToUser(c);
} catch (Exception e) {
return null;
}
}
/**
* Returns all Account in db
* @return Account List<Account>
*/
public List<Account> getAllAccount(){
try {
Cursor c = db.query(Sqlite.TABLE_USER_ACCOUNT, null, null, null, null, null, null, null);
return cursorToListUser(c);
} catch (Exception e) {
return null;
}
}
/**
* Returns an Account by token
* @param token String
* @return Account
*/
public Account getAccountByToken(String token){
try {
Cursor c = db.query(Sqlite.TABLE_USER_ACCOUNT, null, Sqlite.COL_OAUTHTOKEN + " = \"" + token + "\"", null, null, null, null, "1");
return cursorToUser(c);
} catch (Exception e) {
return null;
}
}
/**
* Test if the current user is already stored in data base
* @param account Account
* @return boolean
*/
public boolean userExist(Account account)
{
Cursor mCount= db.rawQuery("select count(*) from " + Sqlite.TABLE_USER_ACCOUNT
+ " where " + Sqlite.COL_USER_ID + " = '" + account.getId() + "' AND " + Sqlite.COL_USERNAME + " = '" + account.getUsername()+ "'", null);
mCount.moveToFirst();
int count = mCount.getInt(0);
mCount.close();
return (count > 0);
}
/***
* Method to hydrate an Account from database
* @param c Cursor
* @return Account
*/
private Account cursorToUser(Cursor c){
//No element found
if (c.getCount() == 0)
return null;
//Take the first element
c.moveToFirst();
//New user
Account account = new Account();
account.setId(c.getString(c.getColumnIndex(Sqlite.COL_USER_ID)));
account.setUsername(c.getString(c.getColumnIndex(Sqlite.COL_USERNAME)));
account.setAcct(c.getString(c.getColumnIndex(Sqlite.COL_ACCT)));
account.setDisplay_name(c.getString(c.getColumnIndex(Sqlite.COL_DISPLAYED_NAME)));
account.setLocked(c.getInt(c.getColumnIndex(Sqlite.COL_LOCKED)) == 1);
account.setFollowers_count(c.getInt(c.getColumnIndex(Sqlite.COL_FOLLOWERS_COUNT)));
account.setFollowing_count(c.getInt(c.getColumnIndex(Sqlite.COL_FOLLOWING_COUNT)));
account.setStatuses_count(c.getInt(c.getColumnIndex(Sqlite.COL_STATUSES_COUNT)));
account.setNote(c.getString(c.getColumnIndex(Sqlite.COL_NOTE)));
account.setUrl(c.getString(c.getColumnIndex(Sqlite.COL_URL)));
account.setAvatar(c.getString(c.getColumnIndex(Sqlite.COL_AVATAR)));
account.setAvatar_static(c.getString(c.getColumnIndex(Sqlite.COL_AVATAR_STATIC)));
account.setHeader(c.getString(c.getColumnIndex(Sqlite.COL_HEADER)));
account.setHeader_static(c.getString(c.getColumnIndex(Sqlite.COL_HEADER_STATIC)));
account.setCreated_at(Helper.stringToDate(context, c.getString(c.getColumnIndex(Sqlite.COL_CREATED_AT))));
account.setInstance(c.getString(c.getColumnIndex(Sqlite.COL_INSTANCE)));
account.setToken(c.getString(c.getColumnIndex(Sqlite.COL_OAUTHTOKEN)));
//Close the cursor
c.close();
//User is returned
return account;
}
/***
* Method to hydrate an Accounts from database
* @param c Cursor
* @return List<Account>
*/
private List<Account> cursorToListUser(Cursor c){
//No element found
if (c.getCount() == 0)
return null;
List<Account> accounts = new ArrayList<>();
while (c.moveToNext() ) {
//New user
Account account = new Account();
account.setId(c.getString(c.getColumnIndex(Sqlite.COL_USER_ID)));
account.setUsername(c.getString(c.getColumnIndex(Sqlite.COL_USERNAME)));
account.setAcct(c.getString(c.getColumnIndex(Sqlite.COL_ACCT)));
account.setDisplay_name(c.getString(c.getColumnIndex(Sqlite.COL_DISPLAYED_NAME)));
account.setLocked(c.getInt(c.getColumnIndex(Sqlite.COL_LOCKED)) == 1);
account.setFollowers_count(c.getInt(c.getColumnIndex(Sqlite.COL_FOLLOWERS_COUNT)));
account.setFollowing_count(c.getInt(c.getColumnIndex(Sqlite.COL_FOLLOWING_COUNT)));
account.setStatuses_count(c.getInt(c.getColumnIndex(Sqlite.COL_STATUSES_COUNT)));
account.setNote(c.getString(c.getColumnIndex(Sqlite.COL_NOTE)));
account.setUrl(c.getString(c.getColumnIndex(Sqlite.COL_URL)));
account.setAvatar(c.getString(c.getColumnIndex(Sqlite.COL_AVATAR)));
account.setAvatar_static(c.getString(c.getColumnIndex(Sqlite.COL_AVATAR_STATIC)));
account.setHeader(c.getString(c.getColumnIndex(Sqlite.COL_HEADER)));
account.setHeader_static(c.getString(c.getColumnIndex(Sqlite.COL_HEADER_STATIC)));
account.setCreated_at(Helper.stringToDate(context, c.getString(c.getColumnIndex(Sqlite.COL_CREATED_AT))));
account.setInstance(c.getString(c.getColumnIndex(Sqlite.COL_INSTANCE)));
account.setToken(c.getString(c.getColumnIndex(Sqlite.COL_OAUTHTOKEN)));
accounts.add(account);
}
//Close the cursor
c.close();
//Users list is returned
return accounts;
}
}

View File

@ -0,0 +1,112 @@
/* 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.sqlite;
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.database.sqlite.SQLiteOpenHelper;
/**
* Created by Thomas on 23/04/2017.
* Manage the DataBase
*/
@SuppressWarnings("WeakerAccess")
public class Sqlite extends SQLiteOpenHelper {
public static final int DB_VERSION = 1;
public static final String DB_NAME = "mastodon_etalab_db";
public static SQLiteDatabase db;
private static Sqlite sInstance;
/***
* List of tables to manage users and data
*/
//Table of owned accounts
static final String TABLE_USER_ACCOUNT = "USER_ACCOUNT";
public static final String COL_USER_ID = "USER_ID";
public static final String COL_USERNAME = "USERNAME";
public static final String COL_ACCT = "ACCT";
public static final String COL_DISPLAYED_NAME = "DISPLAYED_NAME";
public static final String COL_LOCKED = "LOCKED";
public static final String COL_CREATED_AT = "CREATED_AT";
public static final String COL_FOLLOWERS_COUNT = "FOLLOWERS_COUNT";
public static final String COL_FOLLOWING_COUNT = "FOLLOWING_COUNT";
public static final String COL_STATUSES_COUNT = "STATUSES_COUNT";
public static final String COL_NOTE = "NOTE";
public static final String COL_URL = "URL";
public static final String COL_AVATAR = "AVATAR";
public static final String COL_AVATAR_STATIC = "AVATAR_STATIC";
public static final String COL_HEADER = "HEADER";
public static final String COL_HEADER_STATIC = "HEADER_STATIC";
public static final String COL_INSTANCE = "INSTANCE";
public static final String COL_OAUTHTOKEN = "OAUTH_TOKEN";
private static final String CREATE_TABLE_USER_ACCOUNT = "CREATE TABLE " + TABLE_USER_ACCOUNT + " ("
+ COL_USER_ID + " TEXT PRIMARY KEY, " + COL_USERNAME + " TEXT NOT NULL, " + COL_ACCT + " TEXT NOT NULL, "
+ COL_DISPLAYED_NAME + " TEXT NOT NULL, " + COL_LOCKED + " INTEGER NOT NULL, "
+ COL_FOLLOWERS_COUNT + " INTEGER NOT NULL, " + COL_FOLLOWING_COUNT + " INTEGER NOT NULL, " + COL_STATUSES_COUNT + " INTEGER NOT NULL, "
+ COL_NOTE + " TEXT NOT NULL, "+ COL_URL + " TEXT NOT NULL, "
+ COL_AVATAR + " TEXT NOT NULL, "+ COL_AVATAR_STATIC + " TEXT NOT NULL, "
+ COL_HEADER + " TEXT NOT NULL, "+ COL_HEADER_STATIC + " TEXT NOT NULL, "
+ COL_INSTANCE + " TEXT NOT NULL, " + COL_OAUTHTOKEN + " TEXT NOT NULL, " + COL_CREATED_AT + " TEXT NOT NULL)";
public Sqlite(Context context, String name, SQLiteDatabase.CursorFactory factory, int version) {
super(context, name, factory, version);
}
public static synchronized Sqlite getInstance(Context context, String name, SQLiteDatabase.CursorFactory factory, int version)
{
if (sInstance == null) {
sInstance = new Sqlite(context, name, factory, version);
}
return sInstance;
}
@Override
public void onCreate(SQLiteDatabase db) {
db.execSQL(CREATE_TABLE_USER_ACCOUNT);
}
@Override
public void onUpgrade(SQLiteDatabase db, int oldVersion, int newVersion) {
switch (oldVersion) {
default:
break;
}
}
public SQLiteDatabase open(){
//opened with write access
db = getWritableDatabase();
return db;
}
public void close(){
//Close the db
if( db != null && db.isOpen() ) {
db.close();
}
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 720 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 795 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 469 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 613 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 638 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 445 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 926 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 486 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 317 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 306 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 536 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 680 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 563 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 829 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 806 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 201 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 265 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 937 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 1.6 KiB

Some files were not shown because too many files have changed in this diff Show More