commit c4ea4943d6602f39496787c971036b014b236ee4 Author: tom79 Date: Fri May 5 16:36:04 2017 +0200 initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..39fb081a4 --- /dev/null +++ b/.gitignore @@ -0,0 +1,9 @@ +*.iml +.gradle +/local.properties +/.idea/workspace.xml +/.idea/libraries +.DS_Store +/build +/captures +.externalNativeBuild diff --git a/.idea/compiler.xml b/.idea/compiler.xml new file mode 100644 index 000000000..96cc43efa --- /dev/null +++ b/.idea/compiler.xml @@ -0,0 +1,22 @@ + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/.idea/copyright/profiles_settings.xml b/.idea/copyright/profiles_settings.xml new file mode 100644 index 000000000..e7bedf337 --- /dev/null +++ b/.idea/copyright/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/gradle.xml b/.idea/gradle.xml new file mode 100644 index 000000000..7ac24c777 --- /dev/null +++ b/.idea/gradle.xml @@ -0,0 +1,18 @@ + + + + + + \ No newline at end of file diff --git a/.idea/markdown-navigator/profiles_settings.xml b/.idea/markdown-navigator/profiles_settings.xml new file mode 100644 index 000000000..57927c5a7 --- /dev/null +++ b/.idea/markdown-navigator/profiles_settings.xml @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/.idea/misc.xml b/.idea/misc.xml new file mode 100644 index 000000000..871829d8b --- /dev/null +++ b/.idea/misc.xml @@ -0,0 +1,129 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + 1.8 + + + + + + + + \ No newline at end of file diff --git a/.idea/modules.xml b/.idea/modules.xml new file mode 100644 index 000000000..36eb0b784 --- /dev/null +++ b/.idea/modules.xml @@ -0,0 +1,9 @@ + + + + + + + + + \ No newline at end of file diff --git a/.idea/runConfigurations.xml b/.idea/runConfigurations.xml new file mode 100644 index 000000000..7f68460d8 --- /dev/null +++ b/.idea/runConfigurations.xml @@ -0,0 +1,12 @@ + + + + + + \ No newline at end of file diff --git a/.idea/vcs.xml b/.idea/vcs.xml new file mode 100644 index 000000000..830674470 --- /dev/null +++ b/.idea/vcs.xml @@ -0,0 +1,7 @@ + + + + + + + \ No newline at end of file diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 000000000..796b96d1c --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 000000000..b58bf7e45 --- /dev/null +++ b/app/build.gradle @@ -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' +} diff --git a/app/mastodon-etalab-v1.0.1.apk b/app/mastodon-etalab-v1.0.1.apk new file mode 100644 index 000000000..c2669d37b Binary files /dev/null and b/app/mastodon-etalab-v1.0.1.apk differ diff --git a/app/mastodon-etalab-v1.0.2.apk b/app/mastodon-etalab-v1.0.2.apk new file mode 100644 index 000000000..65de92bc9 Binary files /dev/null and b/app/mastodon-etalab-v1.0.2.apk differ diff --git a/app/mastodon-etalab-v1.0.apk b/app/mastodon-etalab-v1.0.apk new file mode 100644 index 000000000..ed4e555fc Binary files /dev/null and b/app/mastodon-etalab-v1.0.apk differ diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 000000000..37588b146 --- /dev/null +++ b/app/proguard-rules.pro @@ -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 diff --git a/app/src/androidTest/java/mastodon/etalab/gouv/fr/mastodon/ExampleInstrumentedTest.java b/app/src/androidTest/java/mastodon/etalab/gouv/fr/mastodon/ExampleInstrumentedTest.java new file mode 100644 index 000000000..cd1981be9 --- /dev/null +++ b/app/src/androidTest/java/mastodon/etalab/gouv/fr/mastodon/ExampleInstrumentedTest.java @@ -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 Testing documentation + */ +@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()); + } +} diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 000000000..8cc184ac3 --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,88 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/ic_launcher-web.png b/app/src/main/ic_launcher-web.png new file mode 100644 index 000000000..4670ce24a Binary files /dev/null and b/app/src/main/ic_launcher-web.png differ diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/AboutActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/AboutActivity.java new file mode 100644 index 000000000..bde2f96dd --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/AboutActivity.java @@ -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 . */ +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); + } + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/LoginActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/LoginActivity.java new file mode 100644 index 000000000..77e82b560 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/LoginActivity.java @@ -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 . */ +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 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(); + } + }); + + } + + + + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainActivity.java new file mode 100644 index 000000000..c7bce7db1 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainActivity.java @@ -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 . */ +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 tagTile = new HashMap<>(); + private HashMap 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(); + } + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainApplication.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainApplication.java new file mode 100644 index 000000000..3ed2bfe00 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainApplication.java @@ -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 . */ +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); + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/ShowAccountActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/ShowAccountActivity.java new file mode 100644 index 000000000..bd3998e29 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/ShowAccountActivity.java @@ -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 . */ +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 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 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; + } + } + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/ShowConversationActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/ShowConversationActivity.java new file mode 100644 index 000000000..3f4da5012 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/ShowConversationActivity.java @@ -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 . */ +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 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 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); + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/SplashActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/SplashActivity.java new file mode 100644 index 000000000..2baefef2b --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/SplashActivity.java @@ -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 . */ +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(); + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java new file mode 100644 index 000000000..f5593b39e --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/TootActivity.java @@ -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 . */ +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 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 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 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 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 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); + } + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/activities/WebviewActivity.java b/app/src/main/java/fr/gouv/etalab/mastodon/activities/WebviewActivity.java new file mode 100644 index 000000000..6f5afcb85 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/WebviewActivity.java @@ -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 . */ + +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 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(); + } + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/PostActionAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/PostActionAsyncTask.java new file mode 100644 index 000000000..60ffc4e54 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/PostActionAsyncTask.java @@ -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 . */ +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 { + + 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); + } + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveAccountAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveAccountAsyncTask.java new file mode 100644 index 000000000..5f258c5da --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveAccountAsyncTask.java @@ -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 . */ +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 { + + 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); + } + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveAccountsAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveAccountsAsyncTask.java new file mode 100644 index 000000000..da3405904 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveAccountsAsyncTask.java @@ -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 . */ +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 { + + private Context context; + private Type action; + private List 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); + } + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveContextAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveContextAsyncTask.java new file mode 100644 index 000000000..158d7efa8 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveContextAsyncTask.java @@ -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 . */ +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 { + + 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); + } + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveFeedsAccountAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveFeedsAccountAsyncTask.java new file mode 100644 index 000000000..de938d533 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveFeedsAccountAsyncTask.java @@ -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 . */ +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 { + + private Context context; + private String accountId; + private List 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); + } + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveFeedsAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveFeedsAsyncTask.java new file mode 100644 index 000000000..0e6565bc9 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveFeedsAsyncTask.java @@ -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 . */ +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 { + + private Context context; + private Type action; + private List 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); + } + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveNotificationsAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveNotificationsAsyncTask.java new file mode 100644 index 000000000..4a8e1d688 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveNotificationsAsyncTask.java @@ -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 . */ +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 { + + private Context context; + private List 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); + } + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveRelationshipAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveRelationshipAsyncTask.java new file mode 100644 index 000000000..0be118325 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveRelationshipAsyncTask.java @@ -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 . */ +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 { + + 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); + } + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveSearchAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveSearchAsyncTask.java new file mode 100644 index 000000000..ed57d586c --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveSearchAsyncTask.java @@ -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 . */ +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 { + + 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); + } + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/UpdateAccountInfoAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/UpdateAccountInfoAsyncTask.java new file mode 100644 index 000000000..a39442654 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/UpdateAccountInfoAsyncTask.java @@ -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 . */ +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 { + + 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); + } + + } + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/UploadActionAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/UploadActionAsyncTask.java new file mode 100644 index 000000000..d9c5025ad --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/UploadActionAsyncTask.java @@ -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 . */ +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 { + + 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); + } + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java new file mode 100644 index 000000000..c49e7e1a9 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java @@ -0,0 +1,1192 @@ +package fr.gouv.etalab.mastodon.client; +/* 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 . */ + +import android.content.Context; +import android.content.SharedPreferences; +import android.util.Log; + +import com.loopj.android.http.AsyncHttpResponseHandler; +import com.loopj.android.http.JsonHttpResponseHandler; +import com.loopj.android.http.RequestParams; +import com.loopj.android.http.SyncHttpClient; + +import org.json.JSONArray; +import org.json.JSONException; +import org.json.JSONObject; + +import java.io.InputStream; +import java.lang.*; +import java.util.ArrayList; +import java.util.List; + +import cz.msebera.android.httpclient.Header; +import fr.gouv.etalab.mastodon.client.Entities.Results; +import fr.gouv.etalab.mastodon.helper.Helper; +import fr.gouv.etalab.mastodon.client.Entities.Account; +import fr.gouv.etalab.mastodon.client.Entities.Application; +import fr.gouv.etalab.mastodon.client.Entities.Attachment; +import fr.gouv.etalab.mastodon.client.Entities.Notification; +import fr.gouv.etalab.mastodon.client.Entities.Relationship; +import fr.gouv.etalab.mastodon.client.Entities.Status; + + +/** + * Created by Thomas on 23/04/2017. + * Manage Calls to the REST API + */ + +public class API { + + + private static final String BASE_URL = "https://" + Helper.INSTANCE + "/api/v1"; + + private SyncHttpClient client = new SyncHttpClient(); + + private Account account; + private Context context; + private Relationship relationship; + private Results results; + private fr.gouv.etalab.mastodon.client.Entities.Context statusContext; + private Attachment attachment; + private List accounts; + private List statuses; + private List notifications; + private int tootPerPage, accountPerPage, notificationPerPage; + private int actionCode; + private String userId; + + public enum StatusAction{ + FAVOURITE, + UNFAVOURITE, + REBLOG, + UNREBLOG, + MUTE, + UNMUTE, + BLOCK, + UNBLOCK, + FOLLOW, + UNFOLLOW, + CREATESTATUS, + UNSTATUS, + REPORT + } + + public API(Context context) { + this.context = context; + SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + tootPerPage = sharedpreferences.getInt(Helper.SET_TOOTS_PER_PAGE, 40); + accountPerPage = sharedpreferences.getInt(Helper.SET_ACCOUNTS_PER_PAGE, 40); + notificationPerPage = sharedpreferences.getInt(Helper.SET_NOTIFICATIONS_PER_PAGE, 40); + userId = sharedpreferences.getString(Helper.PREF_KEY_ID, null); + } + + /*** + * Verifiy credential of the authenticated user *synchronously* + * @return Account + */ + public Account verifyCredentials() { + + account = new Account(); + get("/accounts/verify_credentials", null, new JsonHttpResponseHandler() { + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + account = parseAccountResponse(response); + } + @Override + public void onSuccess(int statusCode, Header[] headers, JSONArray response) { + try { + account = parseAccountResponse(response.getJSONObject(0)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + @Override + public void onFailure(int statusCode, Header[] headers, Throwable error, JSONObject response){ + + } + }); + return account; + } + + /** + * Returns an account + * @param accountId String account fetched + * @return Account entity + */ + public Account getAccount(String accountId) { + + account = new Account(); + get(String.format("/accounts/%s",accountId), null, new JsonHttpResponseHandler() { + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + account = parseAccountResponse(response); + } + @Override + public void onSuccess(int statusCode, Header[] headers, JSONArray response) { + try { + account = parseAccountResponse(response.getJSONObject(0)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + @Override + public void onFailure(int statusCode, Header[] headers, Throwable error, JSONObject response){ + + } + }); + return account; + } + + + /** + * Returns a relationship between the authenticated account and an account + * @param accountId String account fetched + * @return Relationship entity + */ + public Relationship getRelationship(String accountId) { + + relationship = new Relationship(); + RequestParams params = new RequestParams(); + params.put("id",accountId); + get("/accounts/relationships", params, new JsonHttpResponseHandler() { + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + relationship = parseRelationshipResponse(response); + } + @Override + public void onSuccess(int statusCode, Header[] headers, JSONArray response) { + try { + relationship = parseRelationshipResponse(response.getJSONObject(0)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + @Override + public void onFailure(int statusCode, Header[] headers, Throwable error, JSONObject response){ + + } + }); + return relationship; + } + + /** + * Retrieves status for the account *synchronously* + * + * @param accountId String Id of the account + * @return List + */ + public List getStatus(String accountId) { + return getStatus(accountId, false, false, null, null, tootPerPage); + } + + /** + * Retrieves status for the account *synchronously* + * + * @param accountId String Id of the account + * @param max_id String id max + * @return List + */ + public List getStatus(String accountId, String max_id) { + return getStatus(accountId, false, false, max_id, null, tootPerPage); + } + + /** + * Retrieves status for the account *synchronously* + * + * @param accountId String Id of the account + * @param onlyMedia boolean only with media + * @param exclude_replies boolean excludes replies + * @param max_id String id max + * @param since_id String since the id + * @param limit int limit - max value 40 + * @return List + */ + private List getStatus(String accountId, boolean onlyMedia, + boolean exclude_replies, String max_id, String since_id, int limit) { + + RequestParams params = new RequestParams(); + if( exclude_replies) + params.put("exclude_replies", Boolean.toString(true)); + if (max_id != null) + params.put("max_id", max_id); + if (since_id != null) + params.put("since_id", since_id); + if (0 < limit || limit > 40) + limit = 40; + if( onlyMedia) + params.put("only_media", Boolean.toString(true)); + params.put("limit", String.valueOf(limit)); + statuses = new ArrayList<>(); + get(String.format("/accounts/%s/statuses", accountId), params, new JsonHttpResponseHandler() { + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + Status status = parseStatuses(response); + statuses.add(status); + } + @Override + public void onSuccess(int statusCode, Header[] headers, JSONArray response) { + statuses = parseStatuses(response); + + } + @Override + public void onFailure(int statusCode, Header[] headers, Throwable error, JSONObject response){ + } + }); + return statuses; + } + + + /** + * Retrieves one status *synchronously* + * + * @param statusId String Id of the status + * @return List + */ + public List getStatusbyId(String statusId) { + statuses = new ArrayList<>(); + get(String.format("/statuses/%s", statusId), null, new JsonHttpResponseHandler() { + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + Status status = parseStatuses(response); + statuses.add(status); + } + @Override + public void onSuccess(int statusCode, Header[] headers, JSONArray response) { + statuses = parseStatuses(response); + } + @Override + public void onFailure(int statusCode, Header[] headers, Throwable error, JSONObject response){ + + } + }); + return statuses; + } + + /** + * Retrieves the context of status with replies *synchronously* + * + * @param statusId Id of the status + * @return List + */ + public fr.gouv.etalab.mastodon.client.Entities.Context getStatusContext(String statusId) { + statusContext = new fr.gouv.etalab.mastodon.client.Entities.Context(); + get(String.format("/statuses/%s/context", statusId), null, new JsonHttpResponseHandler() { + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + statusContext = parseContext(response); + } + @Override + public void onFailure(int statusCode, Header[] headers, Throwable error, JSONObject response){ + + } + }); + return statusContext; + } + + /** + * Retrieves home timeline for the account *synchronously* + * @return List + */ + public List getHomeTimeline() { + return getHomeTimeline(null, null, tootPerPage); + } + + /** + * Retrieves home timeline for the account *synchronously* + * @param max_id String id max + * @return List + */ + public List getHomeTimeline( String max_id) { + return getHomeTimeline(max_id, null, tootPerPage); + } + + /** + * Retrieves home timeline for the account *synchronously* + * @param max_id String id max + * @param since_id String since the id + * @param limit int limit - max value 40 + * @return List + */ + private List getHomeTimeline(String max_id, String since_id, int limit) { + + RequestParams params = new RequestParams(); + if (max_id != null) + params.put("max_id", max_id); + if (since_id != null) + params.put("since_id", since_id); + if (0 > limit || limit > 40) + limit = 40; + params.put("limit",String.valueOf(limit)); + statuses = new ArrayList<>(); + + get(String.format("/accounts/%s/statuses",userId), params, new JsonHttpResponseHandler() { + + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + Status status = parseStatuses(response); + statuses.add(status); + } + @Override + public void onSuccess(int statusCode, Header[] headers, JSONArray response) { + statuses = parseStatuses(response); + } + @Override + public void onFailure(int statusCode, Header[] headers, Throwable error, JSONObject response){ + + } + }); + return statuses; + } + + /** + * Retrieves public timeline for the account *synchronously* + * @param local boolean only local timeline + * @return List + */ + public List getPublicTimeline(boolean local){ + return getPublicTimeline(local, null, null, tootPerPage); + } + + /** + * Retrieves public timeline for the account *synchronously* + * @param local boolean only local timeline + * @param max_id String id max + * @return List + */ + public List getPublicTimeline(boolean local, String max_id){ + return getPublicTimeline(local, max_id, null, tootPerPage); + } + /** + * Retrieves public timeline for the account *synchronously* + * @param local boolean only local timeline + * @param max_id String id max + * @param since_id String since the id + * @param limit int limit - max value 40 + * @return List + */ + private List getPublicTimeline(boolean local, String max_id, String since_id, int limit){ + + RequestParams params = new RequestParams(); + if( local) + params.put("local", Boolean.toString(true)); + if( max_id != null ) + params.put("max_id", max_id); + if( since_id != null ) + params.put("since_id", since_id); + if( 0 > limit || limit > 40) + limit = 40; + params.put("limit",String.valueOf(limit)); + statuses = new ArrayList<>(); + get("/timelines/public", params, new JsonHttpResponseHandler() { + + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + Status status = parseStatuses(response); + statuses.add(status); + } + @Override + public void onSuccess(int statusCode, Header[] headers, JSONArray response) { + statuses = parseStatuses(response); + } + @Override + public void onFailure(int statusCode, Header[] headers, Throwable error, JSONObject response){ + + } + }); + return statuses; + } + + /** + * Retrieves public tag timeline *synchronously* + * @param tag String + * @param local boolean only local timeline + * @param max_id String id max + * @return List + */ + public List getPublicTimelineTag(String tag, boolean local, String max_id){ + return getPublicTimelineTag(tag, local, max_id, null, tootPerPage); + } + /** + * Retrieves public tag timeline *synchronously* + * @param tag String + * @param local boolean only local timeline + * @param max_id String id max + * @param since_id String since the id + * @param limit int limit - max value 40 + * @return List + */ + private List getPublicTimelineTag(String tag, boolean local, String max_id, String since_id, int limit){ + + RequestParams params = new RequestParams(); + if( local) + params.put("local", Boolean.toString(true)); + if( max_id != null ) + params.put("max_id", max_id); + if( since_id != null ) + params.put("since_id", since_id); + if( 0 > limit || limit > 40) + limit = 40; + params.put("limit",String.valueOf(limit)); + statuses = new ArrayList<>(); + get(String.format("/timelines/tag/%s",tag), params, new JsonHttpResponseHandler() { + + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + Status status = parseStatuses(response); + statuses.add(status); + } + @Override + public void onSuccess(int statusCode, Header[] headers, JSONArray response) { + statuses = parseStatuses(response); + } + @Override + public void onFailure(int statusCode, Header[] headers, Throwable error, JSONObject response){ + + } + }); + return statuses; + } + + + /** + * Retrieves muted users by the authenticated account *synchronously* + * @param max_id String id max + * @return List + */ + public List getMuted(String max_id){ + return getAccounts("/mutes", max_id, null, accountPerPage); + } + + /** + * Retrieves blocked users by the authenticated account *synchronously* + * @param max_id String id max + * @return List + */ + public List getBlocks(String max_id){ + return getAccounts("/blocks", max_id, null, accountPerPage); + } + + + /** + * Retrieves following for the account specified by targetedId *synchronously* + * @param targetedId String targetedId + * @param max_id String id max + * @return List + */ + public List getFollowing(String targetedId, String max_id){ + return getAccounts(String.format("/accounts/%s/following",targetedId),max_id, null, accountPerPage); + } + + /** + * Retrieves followers for the account specified by targetedId *synchronously* + * @param targetedId String targetedId + * @param max_id String id max + * @return List + */ + public List getFollowers(String targetedId, String max_id){ + return getAccounts(String.format("/accounts/%s/followers",targetedId),max_id, null, accountPerPage); + } + + /** + * Retrieves blocked users by the authenticated account *synchronously* + * @param max_id String id max + * @param since_id String since the id + * @param limit int limit - max value 40 + * @return List + */ + private List getAccounts(String action, String max_id, String since_id, int limit){ + + RequestParams params = new RequestParams(); + if( max_id != null ) + params.put("max_id", max_id); + if( since_id != null ) + params.put("since_id", since_id); + if( 0 > limit || limit > 40) + limit = 40; + params.put("limit",String.valueOf(limit)); + accounts = new ArrayList<>(); + get(action, params, new JsonHttpResponseHandler() { + + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + Account account = parseAccountResponse(response); + accounts.add(account); + } + @Override + public void onSuccess(int statusCode, Header[] headers, JSONArray response) { + accounts = parseAccountResponse(response); + } + @Override + public void onFailure(int statusCode, Header[] headers, Throwable error, JSONObject response){ + + } + }); + return accounts; + } + + + /** + * Retrieves favourited status for the authenticated account *synchronously* + * @param max_id String id max + * @return List + */ + public List getFavourites(String max_id){ + return getFavourites(max_id, null, tootPerPage); + } + /** + * Retrieves favourited status for the authenticated account *synchronously* + * @param max_id String id max + * @param since_id String since the id + * @param limit int limit - max value 40 + * @return List + */ + private List getFavourites(String max_id, String since_id, int limit){ + + RequestParams params = new RequestParams(); + if( max_id != null ) + params.put("max_id", max_id); + if( since_id != null ) + params.put("since_id", since_id); + if( 0 > limit || limit > 40) + limit = 40; + params.put("limit",String.valueOf(limit)); + statuses = new ArrayList<>(); + get("/favourites", params, new JsonHttpResponseHandler() { + + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + Status status = parseStatuses(response); + statuses.add(status); + } + @Override + public void onSuccess(int statusCode, Header[] headers, JSONArray response) { + statuses = parseStatuses(response); + } + @Override + public void onFailure(int statusCode, Header[] headers, Throwable error, JSONObject response){ + + } + }); + return statuses; + } + + + /** + * Makes the post action for a status + * @param statusAction Enum + * @param targetedId String id of the targeted Id *can be this of a status or an account* + * @return in status code - Should be equal to 200 when action is done + */ + public int postAction(StatusAction statusAction, String targetedId){ + return postAction(statusAction, targetedId, null, null); + } + + /** + * Makes the post action + * @param status Status object related to the status + * @param comment String comment for the report + * @return in status code - Should be equal to 200 when action is done + */ + public int reportAction(Status status, String comment){ + return postAction(API.StatusAction.REPORT, null, status, comment); + } + + public int statusAction(Status status){ + return postAction(StatusAction.CREATESTATUS, null, status, null); + } + + /** + * Makes the post action + * @param statusAction Enum + * @param targetedId String id of the targeted Id *can be this of a status or an account* + * @param status Status object related to the status + * @param comment String comment for the report + * @return in status code - Should be equal to 200 when action is done + */ + private int postAction(StatusAction statusAction, String targetedId, Status status, String comment ){ + + String action; + RequestParams params = null; + switch (statusAction){ + case FAVOURITE: + action = String.format("/statuses/%s/favourite", targetedId); + break; + case UNFAVOURITE: + action = String.format("/statuses/%s/unfavourite", targetedId); + break; + case REBLOG: + action = String.format("/statuses/%s/reblog", targetedId); + break; + case UNREBLOG: + action = String.format("/statuses/%s/unreblog", targetedId); + break; + case FOLLOW: + action = String.format("/accounts/%s/follow", targetedId); + break; + case UNFOLLOW: + action = String.format("/accounts/%s/unfollow", targetedId); + break; + case BLOCK: + action = String.format("/accounts/%s/block", targetedId); + break; + case UNBLOCK: + action = String.format("/accounts/%s/unblock", targetedId); + break; + case MUTE: + action = String.format("/accounts/%s/mute", targetedId); + break; + case UNMUTE: + action = String.format("/accounts/%s/unmute", targetedId); + break; + case UNSTATUS: + action = String.format("/statuses/%s", targetedId); + break; + case REPORT: + action = "/reports"; + params = new RequestParams(); + params.put("account_id", status.getAccount().getId()); + params.put("comment", comment); + params.put("status_ids[]", status.getId()); + break; + case CREATESTATUS: + + params = new RequestParams(); + action = "/statuses"; + params.put("status", status.getContent()); + if( status.getIn_reply_to_id() != null) + params.put("in_reply_to_id", status.getIn_reply_to_id()); + if( status.getMedia_attachments() != null && status.getMedia_attachments().size() > 0 ) { + int i = 0; + for(Attachment attachment: status.getMedia_attachments()) { + params.add("media_ids[]", attachment.getId()); + i++; + } + } + if( status.isSensitive()) + params.put("sensitive", Boolean.toString(status.isSensitive())); + if( status.getSpoiler_text() != null) + params.put("spoiler_text", status.getSpoiler_text()); + params.put("visibility", status.getVisibility()); + break; + default: + return -1; + } + if(statusAction != StatusAction.UNSTATUS ) { + post(action, params, new JsonHttpResponseHandler() { + + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + actionCode = statusCode; + } + + @Override + public void onSuccess(int statusCode, Header[] headers, JSONArray response) { + actionCode = statusCode; + } + + @Override + public void onFailure(int statusCode, Header[] headers, Throwable error, JSONObject response) { + actionCode = statusCode; + } + }); + }else{ + delete(action, params, new JsonHttpResponseHandler() { + + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + actionCode = statusCode; + } + + @Override + public void onSuccess(int statusCode, Header[] headers, JSONArray response) { + actionCode = statusCode; + } + + @Override + public void onFailure(int statusCode, Header[] headers, Throwable error, JSONObject response) { + actionCode = statusCode; + } + }); + } + + return actionCode; + } + + + /** + * Retrieves notifications for the authenticated account since an id*synchronously* + * @param since_id String since max + * @return List + */ + public List getNotificationsSince(String since_id){ + return getNotifications(null, since_id, notificationPerPage); + } + + /** + * Retrieves notifications for the authenticated account *synchronously* + * @param max_id String id max + * @return List + */ + public List getNotifications(String max_id){ + return getNotifications(max_id, null, notificationPerPage); + } + /** + * Retrieves notifications for the authenticated account *synchronously* + * @param max_id String id max + * @param since_id String since the id + * @param limit int limit - max value 40 + * @return List + */ + private List getNotifications(String max_id, String since_id, int limit){ + + RequestParams params = new RequestParams(); + if( max_id != null ) + params.put("max_id", max_id); + if( since_id != null ) + params.put("since_id", since_id); + if( 0 > limit || limit > 40) + limit = 40; + params.put("limit",String.valueOf(limit)); + notifications = new ArrayList<>(); + get("/notifications", params, new JsonHttpResponseHandler() { + + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + Notification notification = parseNotificationResponse(response); + notifications.add(notification); + } + @Override + public void onSuccess(int statusCode, Header[] headers, JSONArray response) { + notifications = parseNotificationResponse(response); + } + @Override + public void onFailure(int statusCode, Header[] headers, Throwable error, JSONObject response){ + + } + }); + return notifications; + } + + public Attachment uploadMedia(InputStream inputStream){ + + RequestParams params = new RequestParams(); + params.put("file", inputStream); + + post("/media", params, new JsonHttpResponseHandler() { + + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + attachment = parseAttachmentResponse(response); + } + @Override + public void onSuccess(int statusCode, Header[] headers, JSONArray response) { + try { + attachment = parseAttachmentResponse(response.getJSONObject(0)); + } catch (JSONException e) { + e.printStackTrace(); + } + } + @Override + public void onFailure(int statusCode, Header[] headers, Throwable error, JSONObject response){ + } + }); + return attachment; + } + + + /** + * Retrieves Accounts when searching (ie: via @...) *synchronously* + * + * @param query String search + * @return List + */ + public Results search(String query) { + + RequestParams params = new RequestParams(); + params.put("q", query); + //params.put("resolve","false"); + params.put("limit","4"); + get("/search", params, new JsonHttpResponseHandler() { + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + results = parseResultsResponse(response); + } + @Override + public void onFailure(int statusCode, Header[] headers, Throwable error, JSONObject response){ + + } + }); + return results; + } + + + /** + * Parse json response an unique account + * @param resobj JSONObject + * @return Account + */ + private Results parseResultsResponse(JSONObject resobj){ + + Results results = new Results(); + try { + results.setAccounts(parseAccountResponse(resobj.getJSONArray("accounts"))); + results.setStatuses(parseStatuses(resobj.getJSONArray("statuses"))); + } catch (JSONException e) { + e.printStackTrace(); + } + return results; + } + /** + * Parse json response for several status + * @param jsonArray JSONArray + * @return List + */ + private List parseStatuses(JSONArray jsonArray){ + + List statuses = new ArrayList<>(); + try { + int i = 0; + while (i < jsonArray.length() ){ + Status status = new Status(); + JSONObject resobj = jsonArray.getJSONObject(i); + status.setId(resobj.get("id").toString()); + status.setCreated_at(Helper.mstStringToDate(context, resobj.get("created_at").toString())); + status.setIn_reply_to_id(resobj.get("in_reply_to_id").toString()); + status.setIn_reply_to_account_id(resobj.get("in_reply_to_account_id").toString()); + status.setSensitive(Boolean.getBoolean(resobj.get("sensitive").toString())); + status.setSpoiler_text(resobj.get("spoiler_text").toString()); + status.setVisibility(resobj.get("visibility").toString()); + + //TODO: replace by the value + status.setApplication(new Application()); + + JSONArray arrayAttachement = resobj.getJSONArray("media_attachments"); + List attachments = new ArrayList<>(); + if( arrayAttachement != null){ + for(int j = 0 ; j < arrayAttachement.length() ; j++){ + JSONObject attObj = arrayAttachement.getJSONObject(j); + Attachment attachment = new Attachment(); + attachment.setId(attObj.get("id").toString()); + attachment.setPreview_url(attObj.get("preview_url").toString()); + attachment.setRemote_url(attObj.get("remote_url").toString()); + attachment.setType(attObj.get("type").toString()); + attachment.setText_url(attObj.get("text_url").toString()); + attachment.setUrl(attObj.get("url").toString()); + attachments.add(attachment); + } + } + status.setMedia_attachments(attachments); + status.setAccount(parseAccountResponse(resobj.getJSONObject("account"))); + status.setContent(resobj.get("content").toString()); + status.setFavourites_count(Integer.valueOf(resobj.get("favourites_count").toString())); + status.setReblogs_count(Integer.valueOf(resobj.get("reblogs_count").toString())); + status.setReblogged(Boolean.valueOf(resobj.get("reblogged").toString())); + status.setFavourited(Boolean.valueOf(resobj.get("favourited").toString())); + try{ + status.setReblog(parseStatuses(resobj.getJSONObject("reblog"))); + }catch (Exception ignored){} + i++; + + statuses.add(status); + } + + } catch (JSONException e) { + e.printStackTrace(); + } + return statuses; + } + + /** + * Parse json response for unique status + * @param resobj JSONObject + * @return Status + */ + @SuppressWarnings("InfiniteRecursion") + private Status parseStatuses(JSONObject resobj){ + Status status = new Status(); + try { + status.setId(resobj.get("id").toString()); + status.setCreated_at(Helper.mstStringToDate(context, resobj.get("created_at").toString())); + status.setIn_reply_to_id(resobj.get("in_reply_to_id").toString()); + status.setIn_reply_to_account_id(resobj.get("in_reply_to_account_id").toString()); + status.setSensitive(Boolean.getBoolean(resobj.get("sensitive").toString())); + status.setSpoiler_text(resobj.get("spoiler_text").toString()); + status.setVisibility(resobj.get("visibility").toString()); + + //TODO: replace by the value + status.setApplication(new Application()); + + JSONArray arrayAttachement = resobj.getJSONArray("media_attachments"); + List attachments = new ArrayList<>(); + if( arrayAttachement != null){ + for(int j = 0 ; j < arrayAttachement.length() ; j++){ + JSONObject attObj = arrayAttachement.getJSONObject(j); + Attachment attachment = new Attachment(); + attachment.setId(attObj.get("id").toString()); + attachment.setPreview_url(attObj.get("preview_url").toString()); + attachment.setRemote_url(attObj.get("remote_url").toString()); + attachment.setType(attObj.get("type").toString()); + attachment.setText_url(attObj.get("text_url").toString()); + attachment.setUrl(attObj.get("url").toString()); + attachments.add(attachment); + } + } + status.setMedia_attachments(attachments); + status.setAccount(parseAccountResponse(resobj.getJSONObject("account"))); + status.setContent(resobj.get("content").toString()); + status.setFavourites_count(Integer.valueOf(resobj.get("favourites_count").toString())); + status.setReblogs_count(Integer.valueOf(resobj.get("reblogs_count").toString())); + status.setReblogged(Boolean.valueOf(resobj.get("reblogged").toString())); + status.setFavourited(Boolean.valueOf(resobj.get("favourited").toString())); + try{ + status.setReblog(parseStatuses(resobj.getJSONObject("reblog"))); + }catch (Exception ignored){} + } catch (JSONException e) { + e.printStackTrace(); + } + return status; + } + + + /** + * Parse json response an unique account + * @param resobj JSONObject + * @return Account + */ + private Account parseAccountResponse(JSONObject resobj){ + + Account account = new Account(); + try { + account.setId(resobj.get("id").toString()); + account.setUsername(resobj.get("username").toString()); + account.setAcct(resobj.get("acct").toString()); + account.setDisplay_name(resobj.get("display_name").toString()); + account.setLocked(Boolean.parseBoolean(resobj.get("locked").toString())); + account.setCreated_at(Helper.mstStringToDate(context, resobj.get("created_at").toString())); + account.setFollowers_count(Integer.valueOf(resobj.get("followers_count").toString())); + account.setFollowing_count(Integer.valueOf(resobj.get("following_count").toString())); + account.setStatuses_count(Integer.valueOf(resobj.get("statuses_count").toString())); + account.setNote(resobj.get("note").toString()); + account.setUrl(resobj.get("url").toString()); + account.setAvatar(resobj.get("avatar").toString()); + account.setAvatar_static(resobj.get("avatar_static").toString()); + account.setHeader(resobj.get("header").toString()); + account.setHeader_static(resobj.get("header_static").toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + return account; + } + + /** + * Parse json response for list of accounts + * @param jsonArray JSONArray + * @return List + */ + private List parseAccountResponse(JSONArray jsonArray){ + + List accounts = new ArrayList<>(); + try { + int i = 0; + while (i < jsonArray.length() ) { + Account account = new Account(); + JSONObject resobj = jsonArray.getJSONObject(i); + account.setId(resobj.get("id").toString()); + account.setUsername(resobj.get("username").toString()); + account.setAcct(resobj.get("acct").toString()); + account.setDisplay_name(resobj.get("display_name").toString()); + account.setLocked(Boolean.parseBoolean(resobj.get("locked").toString())); + account.setCreated_at(Helper.mstStringToDate(context, resobj.get("created_at").toString())); + account.setFollowers_count(Integer.valueOf(resobj.get("followers_count").toString())); + account.setFollowing_count(Integer.valueOf(resobj.get("following_count").toString())); + account.setStatuses_count(Integer.valueOf(resobj.get("statuses_count").toString())); + account.setNote(resobj.get("note").toString()); + account.setUrl(resobj.get("url").toString()); + account.setAvatar(resobj.get("avatar").toString()); + account.setAvatar_static(resobj.get("avatar_static").toString()); + account.setHeader(resobj.get("header").toString()); + account.setHeader_static(resobj.get("header_static").toString()); + accounts.add(account); + i++; + } + } catch (JSONException e) { + e.printStackTrace(); + } + return accounts; + } + + /** + * Parse json response for the context + * @param jsonObject JSONObject + * @return fr.gouv.etalab.mastodon.client.Entities.Context + */ + private fr.gouv.etalab.mastodon.client.Entities.Context parseContext(JSONObject jsonObject){ + + fr.gouv.etalab.mastodon.client.Entities.Context context = new fr.gouv.etalab.mastodon.client.Entities.Context(); + try { + context.setAncestors(parseStatuses(jsonObject.getJSONArray("ancestors"))); + context.setDescendants(parseStatuses(jsonObject.getJSONArray("descendants"))); + } catch (JSONException e) { + e.printStackTrace(); + } + return context; + } + + /** + * Parse json response an unique attachment + * @param resobj JSONObject + * @return Relationship + */ + private Attachment parseAttachmentResponse(JSONObject resobj){ + + Attachment attachment = new Attachment(); + try { + attachment.setId(resobj.get("id").toString()); + attachment.setType(resobj.get("type").toString()); + attachment.setUrl(resobj.get("url").toString()); + try{ + attachment.setRemote_url(resobj.get("remote_url").toString()); + }catch (JSONException ignore){} + try{ + attachment.setPreview_url(resobj.get("preview_url").toString()); + }catch (JSONException ignore){} + try{ + attachment.setText_url(resobj.get("text_url").toString()); + }catch (JSONException ignore){} + + } catch (JSONException e) { + e.printStackTrace(); + } + return attachment; + } + + /** + * Parse json response an unique relationship + * @param resobj JSONObject + * @return Relationship + */ + private Relationship parseRelationshipResponse(JSONObject resobj){ + + Relationship relationship = new Relationship(); + try { + relationship.setId(resobj.get("id").toString()); + relationship.setFollowing(Boolean.valueOf(resobj.get("following").toString())); + relationship.setFollowed_by(Boolean.valueOf(resobj.get("followed_by").toString())); + relationship.setBlocking(Boolean.valueOf(resobj.get("blocking").toString())); + relationship.setMuting(Boolean.valueOf(resobj.get("muting").toString())); + relationship.setRequested(Boolean.valueOf(resobj.get("requested").toString())); + } catch (JSONException e) { + e.printStackTrace(); + } + return relationship; + } + + /** + * Parse json response an unique notification + * @param resobj JSONObject + * @return Account + */ + private Notification parseNotificationResponse(JSONObject resobj){ + + Notification notification = new Notification(); + try { + notification.setId(resobj.get("id").toString()); + notification.setType(resobj.get("type").toString()); + notification.setCreated_at(Helper.mstStringToDate(context, resobj.get("created_at").toString())); + notification.setAccount(parseAccountResponse(resobj.getJSONObject("account"))); + try{ + notification.setStatus(parseStatuses(resobj.getJSONObject("status"))); + }catch (Exception ignored){} + notification.setCreated_at(Helper.mstStringToDate(context, resobj.get("created_at").toString())); + } catch (JSONException e) { + e.printStackTrace(); + } + return notification; + } + + /** + * Parse json response for list of notifications + * @param jsonArray JSONArray + * @return List + */ + private List parseNotificationResponse(JSONArray jsonArray){ + + List notifications = new ArrayList<>(); + try { + int i = 0; + while (i < jsonArray.length() ) { + Notification notification = new Notification(); + JSONObject resobj = jsonArray.getJSONObject(i); + notification.setId(resobj.get("id").toString()); + notification.setType(resobj.get("type").toString()); + notification.setCreated_at(Helper.mstStringToDate(context, resobj.get("created_at").toString())); + notification.setAccount(parseAccountResponse(resobj.getJSONObject("account"))); + try{ + notification.setStatus(parseStatuses(resobj.getJSONObject("status"))); + }catch (Exception ignored){} + notification.setCreated_at(Helper.mstStringToDate(context, resobj.get("created_at").toString())); + notifications.add(notification); + i++; + } + } catch (JSONException e) { + e.printStackTrace(); + } + return notifications; + } + + + + + /** + * Set the error message + * @param statusCode int code + * @param error Throwable error + */ + private void setError(int statusCode, Throwable error){ + fr.gouv.etalab.mastodon.client.Entities.Error errorResponse = new fr.gouv.etalab.mastodon.client.Entities.Error(); + errorResponse.setError(statusCode + " - " + error.getMessage()); + } + + + private void get(String action, RequestParams params, AsyncHttpResponseHandler responseHandler) { + client.setTimeout(5000); + SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + String prefKeyOauthTokenT = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null); + client.addHeader("Authorization", "Bearer "+prefKeyOauthTokenT); + client.get(getAbsoluteUrl(action), params, responseHandler); + } + + private void post(String action, RequestParams params, AsyncHttpResponseHandler responseHandler) { + SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + String prefKeyOauthTokenT = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null); + client.addHeader("Authorization", "Bearer "+prefKeyOauthTokenT); + client.post(getAbsoluteUrl(action), params, responseHandler); + } + + private void delete(String action, RequestParams params, AsyncHttpResponseHandler responseHandler){ + SharedPreferences sharedpreferences = context.getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE); + String prefKeyOauthTokenT = sharedpreferences.getString(Helper.PREF_KEY_OAUTH_TOKEN, null); + client.addHeader("Authorization", "Bearer "+prefKeyOauthTokenT); + client.delete(getAbsoluteUrl(action), params, responseHandler); + } + + private String getAbsoluteUrl(String action) { + return BASE_URL + action; + } + + + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Account.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Account.java new file mode 100644 index 000000000..43aecff89 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Account.java @@ -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 . */ +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; + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Application.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Application.java new file mode 100644 index 000000000..c0317f181 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Application.java @@ -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 . */ +package fr.gouv.etalab.mastodon.client.Entities; + +/** + * Created by Thomas on 23/04/2017. + */ + +public class Application { + + public String name; + public String website; +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Attachment.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Attachment.java new file mode 100644 index 000000000..ee9eb771b --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Attachment.java @@ -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 . */ +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; + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Card.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Card.java new file mode 100644 index 000000000..5d36256a4 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Card.java @@ -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 . */ +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; + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Context.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Context.java new file mode 100644 index 000000000..e9b805148 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Context.java @@ -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 . */ +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 ancestors; + private List descendants; + + public List getAncestors() { + return ancestors; + } + + public void setAncestors(List ancestors) { + this.ancestors = ancestors; + } + + public List getDescendants() { + return descendants; + } + + public void setDescendants(List descendants) { + this.descendants = descendants; + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Error.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Error.java new file mode 100644 index 000000000..3df7d8b5a --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Error.java @@ -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 . */ +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; + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Mention.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Mention.java new file mode 100644 index 000000000..7d5b7f878 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Mention.java @@ -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 . */ +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; + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Notification.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Notification.java new file mode 100644 index 000000000..376e18adb --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Notification.java @@ -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 . */ +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; + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Relationship.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Relationship.java new file mode 100644 index 000000000..2501630f8 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Relationship.java @@ -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 . */ +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; + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Results.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Results.java new file mode 100644 index 000000000..497c879f2 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Results.java @@ -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 accounts; + private List statuses; + private List hashtags; + + public List getAccounts() { + return accounts; + } + + public void setAccounts(List accounts) { + this.accounts = accounts; + } + + public List getStatuses() { + return statuses; + } + + public void setStatuses(List statuses) { + this.statuses = statuses; + } + + public List getHashtags() { + return hashtags; + } + + public void setHashtags(List hashtags) { + this.hashtags = hashtags; + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Status.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Status.java new file mode 100644 index 000000000..055e26ce3 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Status.java @@ -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 . */ +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 media_attachments; + private List mentions; + private List tags; + private Application application; + public List getMedia_attachments() { + return media_attachments; + } + + public void setMedia_attachments(List media_attachments) { + this.media_attachments = media_attachments; + } + + public List getMentions() { + return mentions; + } + + public void setMentions(List mentions) { + this.mentions = mentions; + } + + public List getTags() { + return tags; + } + + public void setTags(List 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; + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Tag.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Tag.java new file mode 100644 index 000000000..e20ad28a6 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Tag.java @@ -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 . */ +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; + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Error.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Error.java new file mode 100644 index 000000000..5f576aa93 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Error.java @@ -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 . */ +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; + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/OauthClient.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/OauthClient.java new file mode 100644 index 000000000..fceddcb44 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/OauthClient.java @@ -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 . */ +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 paramaters, AsyncHttpResponseHandler responseHandler) { + client.setTimeout(5000); + RequestParams params = hashToRequestParams(paramaters); + client.get(getAbsoluteUrl(action), params, responseHandler); + } + + public void post(String action, HashMap paramaters, AsyncHttpResponseHandler responseHandler) { + RequestParams params = hashToRequestParams(paramaters); + client.post(getAbsoluteUrl(action), params, responseHandler); + } + + private String getAbsoluteUrl(String action) { + return BASE_URL + action; + } + + /** + * Convert HashMap to RequestParams + * @param params HashMap + * @return RequestParams + */ + private RequestParams hashToRequestParams(HashMap 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; + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/drawers/AccountsListAdapter.java b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/AccountsListAdapter.java new file mode 100644 index 000000000..08790ca1b --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/AccountsListAdapter.java @@ -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 . */ + +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 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 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 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(); + } + +} \ No newline at end of file diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/drawers/AccountsSearchAdapter.java b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/AccountsSearchAdapter.java new file mode 100644 index 000000000..ec47fd5d5 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/AccountsSearchAdapter.java @@ -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 . */ + + +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 accounts; + private LayoutInflater layoutInflater; + private ImageLoader imageLoader; + private DisplayImageOptions options; + private Context context; + + public AccountsSearchAdapter(Context context, List 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; + } + + +} \ No newline at end of file diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/drawers/NotificationsListAdapter.java b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/NotificationsListAdapter.java new file mode 100644 index 000000000..476492543 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/NotificationsListAdapter.java @@ -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 . */ + +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 notifications; + private LayoutInflater layoutInflater; + private ImageLoader imageLoader; + private DisplayImageOptions options; + + public NotificationsListAdapter(Context context, List 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; + } + +} \ No newline at end of file diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/drawers/StatusListAdapter.java b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/StatusListAdapter.java new file mode 100644 index 000000000..5183ea27a --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/drawers/StatusListAdapter.java @@ -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 . */ + +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 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 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 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 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 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(); + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayAccountsFragment.java b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayAccountsFragment.java new file mode 100644 index 000000000..2a0d3a0e4 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayAccountsFragment.java @@ -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 . */ +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 asyncTask; + private AccountsListAdapter accountsListAdapter; + private String max_id; + private List 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 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; + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayNotificationsFragment.java b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayNotificationsFragment.java new file mode 100644 index 000000000..c194cead3 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayNotificationsFragment.java @@ -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 . */ +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 asyncTask; + private NotificationsListAdapter notificationsListAdapter; + private String max_id = null; + private List 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 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; + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayStatusFragment.java b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayStatusFragment.java new file mode 100644 index 000000000..5f277dfe9 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/DisplayStatusFragment.java @@ -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 . */ +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 asyncTask; + private StatusListAdapter statusListAdapter; + private String max_id; + private List 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 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; + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/fragments/SettingsNotificationsFragment.java b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/SettingsNotificationsFragment.java new file mode 100644 index 000000000..9d44598cf --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/SettingsNotificationsFragment.java @@ -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 . */ +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; + } + + + + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/fragments/SettingsOptimizationFragment.java b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/SettingsOptimizationFragment.java new file mode 100644 index 000000000..d9946c1d0 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/SettingsOptimizationFragment.java @@ -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 . */ +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; + } + + + + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/fragments/TabLayoutSettingsFragment.java b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/TabLayoutSettingsFragment.java new file mode 100644 index 000000000..758076190 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/TabLayoutSettingsFragment.java @@ -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 . */ +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; + } + } +} \ No newline at end of file diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java b/app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java new file mode 100644 index 000000000..eba216f74 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/helper/Helper.java @@ -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 . */ + + +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(); + } + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnPostActionInterface.java b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnPostActionInterface.java new file mode 100644 index 000000000..02b45aae7 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnPostActionInterface.java @@ -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 . */ +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); +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveAccountInterface.java b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveAccountInterface.java new file mode 100644 index 000000000..bbcaede36 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveAccountInterface.java @@ -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 . */ +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); +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveAccountsInterface.java b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveAccountsInterface.java new file mode 100644 index 000000000..c36fbd2cc --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveAccountsInterface.java @@ -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 . */ +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 accounts); +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveAttachmentInterface.java b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveAttachmentInterface.java new file mode 100644 index 000000000..f5c79f271 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveAttachmentInterface.java @@ -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 . */ +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); +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveContextInterface.java b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveContextInterface.java new file mode 100644 index 000000000..d51d8ee3a --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveContextInterface.java @@ -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 . */ +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); +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveFeedsAccountInterface.java b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveFeedsAccountInterface.java new file mode 100644 index 000000000..edf557b51 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveFeedsAccountInterface.java @@ -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 . */ +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 statuses); +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveFeedsInterface.java b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveFeedsInterface.java new file mode 100644 index 000000000..63f00fde1 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveFeedsInterface.java @@ -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 . */ +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 statuses); +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveNotificationsInterface.java b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveNotificationsInterface.java new file mode 100644 index 000000000..27c8aec54 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveNotificationsInterface.java @@ -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 . */ +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 notifications, String acct); +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveRelationshipInterface.java b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveRelationshipInterface.java new file mode 100644 index 000000000..c4a5b4611 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveRelationshipInterface.java @@ -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 . */ +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); +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveSearchInterface.java b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveSearchInterface.java new file mode 100644 index 000000000..e69aa0300 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveSearchInterface.java @@ -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 . */ +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); +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnUpdateAccountInfoInterface.java b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnUpdateAccountInfoInterface.java new file mode 100644 index 000000000..cfef1e2d9 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnUpdateAccountInfoInterface.java @@ -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 . */ +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); +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/jobs/ApplicationJob.java b/app/src/main/java/fr/gouv/etalab/mastodon/jobs/ApplicationJob.java new file mode 100644 index 000000000..84c301cc3 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/jobs/ApplicationJob.java @@ -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 . */ +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); + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/jobs/NotificationsSyncJob.java b/app/src/main/java/fr/gouv/etalab/mastodon/jobs/NotificationsSyncJob.java new file mode 100644 index 000000000..35e2df73d --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/jobs/NotificationsSyncJob.java @@ -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 . */ +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 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 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 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()); + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/AccountDAO.java b/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/AccountDAO.java new file mode 100644 index 000000000..3a75885c4 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/AccountDAO.java @@ -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 . */ + +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 + */ + public List 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 + */ + private List cursorToListUser(Cursor c){ + //No element found + if (c.getCount() == 0) + return null; + List 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; + } + + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/Sqlite.java b/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/Sqlite.java new file mode 100644 index 000000000..912cf036f --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/sqlite/Sqlite.java @@ -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 . */ + +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(); + } + } +} diff --git a/app/src/main/res/drawable-hdpi-v11/notification_icon.png b/app/src/main/res/drawable-hdpi-v11/notification_icon.png new file mode 100644 index 000000000..07ad40657 Binary files /dev/null and b/app/src/main/res/drawable-hdpi-v11/notification_icon.png differ diff --git a/app/src/main/res/drawable-hdpi-v9/notification_icon.png b/app/src/main/res/drawable-hdpi-v9/notification_icon.png new file mode 100644 index 000000000..af20f7240 Binary files /dev/null and b/app/src/main/res/drawable-hdpi-v9/notification_icon.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_camera.png b/app/src/main/res/drawable-hdpi/ic_action_camera.png new file mode 100644 index 000000000..c1a088fd1 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_camera.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_clock.png b/app/src/main/res/drawable-hdpi/ic_action_clock.png new file mode 100644 index 000000000..917b4aad3 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_clock.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_edit.png b/app/src/main/res/drawable-hdpi/ic_action_edit.png new file mode 100644 index 000000000..ab016122d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_edit.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_gear.png b/app/src/main/res/drawable-hdpi/ic_action_gear.png new file mode 100644 index 000000000..0e0e2a6c5 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_gear.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_globe.png b/app/src/main/res/drawable-hdpi/ic_action_globe.png new file mode 100644 index 000000000..f76e15a6a Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_globe.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_home.png b/app/src/main/res/drawable-hdpi/ic_action_home.png new file mode 100644 index 000000000..ba1e885b6 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_home.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_lock_closed.png b/app/src/main/res/drawable-hdpi/ic_action_lock_closed.png new file mode 100644 index 000000000..18433244b Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_lock_closed.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_lock_open.png b/app/src/main/res/drawable-hdpi/ic_action_lock_open.png new file mode 100644 index 000000000..c9ab87db8 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_lock_open.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_more.png b/app/src/main/res/drawable-hdpi/ic_action_more.png new file mode 100644 index 000000000..034d5a25e Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_more.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_search.png b/app/src/main/res/drawable-hdpi/ic_action_search.png new file mode 100644 index 000000000..45c099ab8 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_search.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_star.png b/app/src/main/res/drawable-hdpi/ic_action_star.png new file mode 100644 index 000000000..c6c9cdf00 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_star.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_users.png b/app/src/main/res/drawable-hdpi/ic_action_users.png new file mode 100644 index 000000000..9ec683995 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_users.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_action_volume_mute.png b/app/src/main/res/drawable-hdpi/ic_action_volume_mute.png new file mode 100644 index 000000000..2becf00d9 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_action_volume_mute.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_block.png b/app/src/main/res/drawable-hdpi/ic_block.png new file mode 100644 index 000000000..5ac5c01e7 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_block.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_block_white.png b/app/src/main/res/drawable-hdpi/ic_block_white.png new file mode 100644 index 000000000..a6f7f7a40 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_block_white.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_close.png b/app/src/main/res/drawable-hdpi/ic_close.png new file mode 100644 index 000000000..0d83919ee Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_close.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_email.png b/app/src/main/res/drawable-hdpi/ic_email.png new file mode 100644 index 000000000..e2782b58c Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_email.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_fav_black.png b/app/src/main/res/drawable-hdpi/ic_fav_black.png new file mode 100644 index 000000000..7ef0927ff Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_fav_black.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_fav_yellow.png b/app/src/main/res/drawable-hdpi/ic_fav_yellow.png new file mode 100644 index 000000000..51d28e043 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_fav_yellow.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_favorite_menu.png b/app/src/main/res/drawable-hdpi/ic_favorite_menu.png new file mode 100644 index 000000000..a099f0d2d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_favorite_menu.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_local_post_office.png b/app/src/main/res/drawable-hdpi/ic_local_post_office.png new file mode 100644 index 000000000..d86e1c615 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_local_post_office.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_message.png b/app/src/main/res/drawable-hdpi/ic_message.png new file mode 100644 index 000000000..4dc32aa28 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_message.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_mute_white.png b/app/src/main/res/drawable-hdpi/ic_mute_white.png new file mode 100644 index 000000000..23316319f Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_mute_white.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_notifications.png b/app/src/main/res/drawable-hdpi/ic_notifications.png new file mode 100644 index 000000000..b1a6e580d Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_notifications.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_photo.png b/app/src/main/res/drawable-hdpi/ic_photo.png new file mode 100644 index 000000000..f166d7e98 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_photo.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_reply.png b/app/src/main/res/drawable-hdpi/ic_reply.png new file mode 100644 index 000000000..561b0c9cc Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_reply.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_reply_header.png b/app/src/main/res/drawable-hdpi/ic_reply_header.png new file mode 100644 index 000000000..561b0c9cc Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_reply_header.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_retweet.png b/app/src/main/res/drawable-hdpi/ic_retweet.png new file mode 100644 index 000000000..459e116c3 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_retweet.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_retweet_black.png b/app/src/main/res/drawable-hdpi/ic_retweet_black.png new file mode 100644 index 000000000..c67b58682 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_retweet_black.png differ diff --git a/app/src/main/res/drawable-hdpi/ic_retweet_yellow.png b/app/src/main/res/drawable-hdpi/ic_retweet_yellow.png new file mode 100644 index 000000000..992eb6bc3 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_retweet_yellow.png differ diff --git a/app/src/main/res/drawable-hdpi/mastodon_icon.png b/app/src/main/res/drawable-hdpi/mastodon_icon.png new file mode 100644 index 000000000..c16328243 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/mastodon_icon.png differ diff --git a/app/src/main/res/drawable-hdpi/notification_icon.png b/app/src/main/res/drawable-hdpi/notification_icon.png new file mode 100644 index 000000000..4737c6d71 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/notification_icon.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_action_camera.png b/app/src/main/res/drawable-ldpi/ic_action_camera.png new file mode 100644 index 000000000..fda64f299 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_action_camera.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_action_clock.png b/app/src/main/res/drawable-ldpi/ic_action_clock.png new file mode 100644 index 000000000..ca0d37d02 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_action_clock.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_action_edit.png b/app/src/main/res/drawable-ldpi/ic_action_edit.png new file mode 100644 index 000000000..03773e180 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_action_edit.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_action_gear.png b/app/src/main/res/drawable-ldpi/ic_action_gear.png new file mode 100644 index 000000000..d027ca9ee Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_action_gear.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_action_globe.png b/app/src/main/res/drawable-ldpi/ic_action_globe.png new file mode 100644 index 000000000..bcf031640 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_action_globe.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_action_home.png b/app/src/main/res/drawable-ldpi/ic_action_home.png new file mode 100644 index 000000000..1f9d69deb Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_action_home.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_action_lock_closed.png b/app/src/main/res/drawable-ldpi/ic_action_lock_closed.png new file mode 100644 index 000000000..2b99186f9 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_action_lock_closed.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_action_lock_open.png b/app/src/main/res/drawable-ldpi/ic_action_lock_open.png new file mode 100644 index 000000000..b8cf2bdb4 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_action_lock_open.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_action_more.png b/app/src/main/res/drawable-ldpi/ic_action_more.png new file mode 100644 index 000000000..cb1ab377d Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_action_more.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_action_search.png b/app/src/main/res/drawable-ldpi/ic_action_search.png new file mode 100644 index 000000000..7ca935afb Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_action_search.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_action_star.png b/app/src/main/res/drawable-ldpi/ic_action_star.png new file mode 100644 index 000000000..5867a65a5 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_action_star.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_action_users.png b/app/src/main/res/drawable-ldpi/ic_action_users.png new file mode 100644 index 000000000..edb6f5c48 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_action_users.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_action_volume_mute.png b/app/src/main/res/drawable-ldpi/ic_action_volume_mute.png new file mode 100644 index 000000000..f74fb60b2 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_action_volume_mute.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_block.png b/app/src/main/res/drawable-ldpi/ic_block.png new file mode 100644 index 000000000..1787e854a Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_block.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_block_white.png b/app/src/main/res/drawable-ldpi/ic_block_white.png new file mode 100644 index 000000000..e4c15307c Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_block_white.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_close.png b/app/src/main/res/drawable-ldpi/ic_close.png new file mode 100644 index 000000000..834a5afcc Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_close.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_email.png b/app/src/main/res/drawable-ldpi/ic_email.png new file mode 100644 index 000000000..eced406f0 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_email.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_fav_black.png b/app/src/main/res/drawable-ldpi/ic_fav_black.png new file mode 100644 index 000000000..811d1fd43 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_fav_black.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_fav_yellow.png b/app/src/main/res/drawable-ldpi/ic_fav_yellow.png new file mode 100644 index 000000000..e1fd9a302 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_fav_yellow.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_favorite_menu.png b/app/src/main/res/drawable-ldpi/ic_favorite_menu.png new file mode 100644 index 000000000..17af1355c Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_favorite_menu.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_local_post_office.png b/app/src/main/res/drawable-ldpi/ic_local_post_office.png new file mode 100644 index 000000000..b66d598a7 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_local_post_office.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_message.png b/app/src/main/res/drawable-ldpi/ic_message.png new file mode 100644 index 000000000..ddedaae51 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_message.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_mute_white.png b/app/src/main/res/drawable-ldpi/ic_mute_white.png new file mode 100644 index 000000000..e5547093c Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_mute_white.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_notifications.png b/app/src/main/res/drawable-ldpi/ic_notifications.png new file mode 100644 index 000000000..3b3d3dc64 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_notifications.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_photo.png b/app/src/main/res/drawable-ldpi/ic_photo.png new file mode 100644 index 000000000..3299cf683 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_photo.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_reply.png b/app/src/main/res/drawable-ldpi/ic_reply.png new file mode 100644 index 000000000..3d21ea453 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_reply.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_retweet.png b/app/src/main/res/drawable-ldpi/ic_retweet.png new file mode 100644 index 000000000..6e6f9cd82 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_retweet.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_retweet_black.png b/app/src/main/res/drawable-ldpi/ic_retweet_black.png new file mode 100644 index 000000000..302c9f89b Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_retweet_black.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_retweet_header.png b/app/src/main/res/drawable-ldpi/ic_retweet_header.png new file mode 100644 index 000000000..6e6f9cd82 Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_retweet_header.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_retweet_yellow.png b/app/src/main/res/drawable-ldpi/ic_retweet_yellow.png new file mode 100644 index 000000000..7df0b529a Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_retweet_yellow.png differ diff --git a/app/src/main/res/drawable-ldpi/mastodon_icon.png b/app/src/main/res/drawable-ldpi/mastodon_icon.png new file mode 100644 index 000000000..add131e9b Binary files /dev/null and b/app/src/main/res/drawable-ldpi/mastodon_icon.png differ diff --git a/app/src/main/res/drawable-mdpi-v11/notification_icon.png b/app/src/main/res/drawable-mdpi-v11/notification_icon.png new file mode 100644 index 000000000..59d8a8af9 Binary files /dev/null and b/app/src/main/res/drawable-mdpi-v11/notification_icon.png differ diff --git a/app/src/main/res/drawable-mdpi-v9/notification_icon.png b/app/src/main/res/drawable-mdpi-v9/notification_icon.png new file mode 100644 index 000000000..221ecd751 Binary files /dev/null and b/app/src/main/res/drawable-mdpi-v9/notification_icon.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_camera.png b/app/src/main/res/drawable-mdpi/ic_action_camera.png new file mode 100644 index 000000000..b2fc9f736 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_camera.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_clock.png b/app/src/main/res/drawable-mdpi/ic_action_clock.png new file mode 100644 index 000000000..dda182055 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_clock.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_edit.png b/app/src/main/res/drawable-mdpi/ic_action_edit.png new file mode 100644 index 000000000..f5a99eac1 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_edit.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_gear.png b/app/src/main/res/drawable-mdpi/ic_action_gear.png new file mode 100644 index 000000000..75bf346ac Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_gear.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_globe.png b/app/src/main/res/drawable-mdpi/ic_action_globe.png new file mode 100644 index 000000000..6ddcc8102 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_globe.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_home.png b/app/src/main/res/drawable-mdpi/ic_action_home.png new file mode 100644 index 000000000..12915c6c5 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_home.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_lock_closed.png b/app/src/main/res/drawable-mdpi/ic_action_lock_closed.png new file mode 100644 index 000000000..53dce80e4 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_lock_closed.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_lock_open.png b/app/src/main/res/drawable-mdpi/ic_action_lock_open.png new file mode 100644 index 000000000..7612de5d8 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_lock_open.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_more.png b/app/src/main/res/drawable-mdpi/ic_action_more.png new file mode 100644 index 000000000..bce163630 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_more.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_search.png b/app/src/main/res/drawable-mdpi/ic_action_search.png new file mode 100644 index 000000000..174ccaabd Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_search.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_star.png b/app/src/main/res/drawable-mdpi/ic_action_star.png new file mode 100644 index 000000000..68b710c72 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_star.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_users.png b/app/src/main/res/drawable-mdpi/ic_action_users.png new file mode 100644 index 000000000..4005044e2 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_users.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_action_volume_mute.png b/app/src/main/res/drawable-mdpi/ic_action_volume_mute.png new file mode 100644 index 000000000..4d38610bf Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_action_volume_mute.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_block.png b/app/src/main/res/drawable-mdpi/ic_block.png new file mode 100644 index 000000000..1f7f6e529 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_block.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_block_white.png b/app/src/main/res/drawable-mdpi/ic_block_white.png new file mode 100644 index 000000000..a8a9e0971 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_block_white.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_close.png b/app/src/main/res/drawable-mdpi/ic_close.png new file mode 100644 index 000000000..385abeaa9 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_close.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_email.png b/app/src/main/res/drawable-mdpi/ic_email.png new file mode 100644 index 000000000..33d399914 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_email.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_fav_black.png b/app/src/main/res/drawable-mdpi/ic_fav_black.png new file mode 100644 index 000000000..fd880f328 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_fav_black.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_fav_yellow.png b/app/src/main/res/drawable-mdpi/ic_fav_yellow.png new file mode 100644 index 000000000..9dc1c3744 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_fav_yellow.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_favorite_menu.png b/app/src/main/res/drawable-mdpi/ic_favorite_menu.png new file mode 100644 index 000000000..cffe7ed71 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_favorite_menu.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_local_post_office.png b/app/src/main/res/drawable-mdpi/ic_local_post_office.png new file mode 100644 index 000000000..f1979b2cc Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_local_post_office.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_message.png b/app/src/main/res/drawable-mdpi/ic_message.png new file mode 100644 index 000000000..30bdd6987 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_message.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_mute_white.png b/app/src/main/res/drawable-mdpi/ic_mute_white.png new file mode 100644 index 000000000..0ea44b996 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_mute_white.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_notifications.png b/app/src/main/res/drawable-mdpi/ic_notifications.png new file mode 100644 index 000000000..81d09aef7 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_notifications.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_photo.png b/app/src/main/res/drawable-mdpi/ic_photo.png new file mode 100644 index 000000000..b46040614 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_photo.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_reply.png b/app/src/main/res/drawable-mdpi/ic_reply.png new file mode 100644 index 000000000..984daa558 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_reply.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_retweet.png b/app/src/main/res/drawable-mdpi/ic_retweet.png new file mode 100644 index 000000000..466af6bca Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_retweet.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_retweet_black.png b/app/src/main/res/drawable-mdpi/ic_retweet_black.png new file mode 100644 index 000000000..bbd38254a Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_retweet_black.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_retweet_header.png b/app/src/main/res/drawable-mdpi/ic_retweet_header.png new file mode 100644 index 000000000..466af6bca Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_retweet_header.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_retweet_yellow.png b/app/src/main/res/drawable-mdpi/ic_retweet_yellow.png new file mode 100644 index 000000000..506ab8c70 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_retweet_yellow.png differ diff --git a/app/src/main/res/drawable-mdpi/mastodon_icon.png b/app/src/main/res/drawable-mdpi/mastodon_icon.png new file mode 100644 index 000000000..bf39d98a1 Binary files /dev/null and b/app/src/main/res/drawable-mdpi/mastodon_icon.png differ diff --git a/app/src/main/res/drawable-mdpi/notification_icon.png b/app/src/main/res/drawable-mdpi/notification_icon.png new file mode 100644 index 000000000..e8b774a9e Binary files /dev/null and b/app/src/main/res/drawable-mdpi/notification_icon.png differ diff --git a/app/src/main/res/drawable-v21/ic_menu_camera.xml b/app/src/main/res/drawable-v21/ic_menu_camera.xml new file mode 100644 index 000000000..0d9ea104b --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_menu_camera.xml @@ -0,0 +1,12 @@ + + + + diff --git a/app/src/main/res/drawable-v21/ic_menu_gallery.xml b/app/src/main/res/drawable-v21/ic_menu_gallery.xml new file mode 100644 index 000000000..f6872c409 --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_menu_gallery.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable-v21/ic_menu_manage.xml b/app/src/main/res/drawable-v21/ic_menu_manage.xml new file mode 100644 index 000000000..c1be60b36 --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_menu_manage.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/drawable-v21/ic_menu_send.xml b/app/src/main/res/drawable-v21/ic_menu_send.xml new file mode 100644 index 000000000..00c668c60 --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_menu_send.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable-v21/ic_menu_share.xml b/app/src/main/res/drawable-v21/ic_menu_share.xml new file mode 100644 index 000000000..a28fb9e28 --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_menu_share.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable-v21/ic_menu_slideshow.xml b/app/src/main/res/drawable-v21/ic_menu_slideshow.xml new file mode 100644 index 000000000..209aa6430 --- /dev/null +++ b/app/src/main/res/drawable-v21/ic_menu_slideshow.xml @@ -0,0 +1,9 @@ + + + diff --git a/app/src/main/res/drawable-xhdpi-v11/notification_icon.png b/app/src/main/res/drawable-xhdpi-v11/notification_icon.png new file mode 100644 index 000000000..1d2eb3988 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi-v11/notification_icon.png differ diff --git a/app/src/main/res/drawable-xhdpi-v9/notification_icon.png b/app/src/main/res/drawable-xhdpi-v9/notification_icon.png new file mode 100644 index 000000000..a8f08407c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi-v9/notification_icon.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_camera.png b/app/src/main/res/drawable-xhdpi/ic_action_camera.png new file mode 100644 index 000000000..ad7f81dfb Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_camera.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_clock.png b/app/src/main/res/drawable-xhdpi/ic_action_clock.png new file mode 100644 index 000000000..25e61f527 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_clock.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_edit.png b/app/src/main/res/drawable-xhdpi/ic_action_edit.png new file mode 100644 index 000000000..700dc01dc Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_edit.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_gear.png b/app/src/main/res/drawable-xhdpi/ic_action_gear.png new file mode 100644 index 000000000..a2f4e8e5a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_gear.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_globe.png b/app/src/main/res/drawable-xhdpi/ic_action_globe.png new file mode 100644 index 000000000..4f263b822 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_globe.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_home.png b/app/src/main/res/drawable-xhdpi/ic_action_home.png new file mode 100644 index 000000000..4417d8fcd Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_home.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_lock_closed.png b/app/src/main/res/drawable-xhdpi/ic_action_lock_closed.png new file mode 100644 index 000000000..8af08d175 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_lock_closed.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_lock_open.png b/app/src/main/res/drawable-xhdpi/ic_action_lock_open.png new file mode 100644 index 000000000..b0120a6fe Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_lock_open.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_more.png b/app/src/main/res/drawable-xhdpi/ic_action_more.png new file mode 100644 index 000000000..7c690cc33 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_more.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_search.png b/app/src/main/res/drawable-xhdpi/ic_action_search.png new file mode 100644 index 000000000..079c9a383 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_search.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_star.png b/app/src/main/res/drawable-xhdpi/ic_action_star.png new file mode 100644 index 000000000..d098e03b0 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_star.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_users.png b/app/src/main/res/drawable-xhdpi/ic_action_users.png new file mode 100644 index 000000000..8330693ba Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_users.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_action_volume_mute.png b/app/src/main/res/drawable-xhdpi/ic_action_volume_mute.png new file mode 100644 index 000000000..b1af9144b Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_action_volume_mute.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_block.png b/app/src/main/res/drawable-xhdpi/ic_block.png new file mode 100644 index 000000000..b21bd0d44 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_block.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_block_white.png b/app/src/main/res/drawable-xhdpi/ic_block_white.png new file mode 100644 index 000000000..6bd89eac3 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_block_white.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_close.png b/app/src/main/res/drawable-xhdpi/ic_close.png new file mode 100644 index 000000000..1b79d0be0 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_close.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_email.png b/app/src/main/res/drawable-xhdpi/ic_email.png new file mode 100644 index 000000000..d86e1c615 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_email.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_fav_black.png b/app/src/main/res/drawable-xhdpi/ic_fav_black.png new file mode 100644 index 000000000..f3b3ec7c1 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_fav_black.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_fav_yellow.png b/app/src/main/res/drawable-xhdpi/ic_fav_yellow.png new file mode 100644 index 000000000..a34801821 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_fav_yellow.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_favorite_menu.png b/app/src/main/res/drawable-xhdpi/ic_favorite_menu.png new file mode 100644 index 000000000..d76866575 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_favorite_menu.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_local_post_office.png b/app/src/main/res/drawable-xhdpi/ic_local_post_office.png new file mode 100644 index 000000000..99bc82074 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_local_post_office.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_message.png b/app/src/main/res/drawable-xhdpi/ic_message.png new file mode 100644 index 000000000..f48f7c909 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_message.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_mute_white.png b/app/src/main/res/drawable-xhdpi/ic_mute_white.png new file mode 100644 index 000000000..533955c73 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_mute_white.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_notifications.png b/app/src/main/res/drawable-xhdpi/ic_notifications.png new file mode 100644 index 000000000..0c22d4926 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_notifications.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_photo.png b/app/src/main/res/drawable-xhdpi/ic_photo.png new file mode 100644 index 000000000..f54db0453 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_photo.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_reply.png b/app/src/main/res/drawable-xhdpi/ic_reply.png new file mode 100644 index 000000000..09ef8e30c Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_reply.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_retweet.png b/app/src/main/res/drawable-xhdpi/ic_retweet.png new file mode 100644 index 000000000..c51f7d015 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_retweet.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_retweet_black.png b/app/src/main/res/drawable-xhdpi/ic_retweet_black.png new file mode 100644 index 000000000..5d90452ea Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_retweet_black.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_retweet_header.png b/app/src/main/res/drawable-xhdpi/ic_retweet_header.png new file mode 100644 index 000000000..c51f7d015 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_retweet_header.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_retweet_yellow.png b/app/src/main/res/drawable-xhdpi/ic_retweet_yellow.png new file mode 100644 index 000000000..8a6bfd543 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_retweet_yellow.png differ diff --git a/app/src/main/res/drawable-xhdpi/mastodon_icon.png b/app/src/main/res/drawable-xhdpi/mastodon_icon.png new file mode 100644 index 000000000..d6652d11a Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/mastodon_icon.png differ diff --git a/app/src/main/res/drawable-xhdpi/notification_icon.png b/app/src/main/res/drawable-xhdpi/notification_icon.png new file mode 100644 index 000000000..d237fbc95 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/notification_icon.png differ diff --git a/app/src/main/res/drawable-xxhdpi-v11/notification_icon.png b/app/src/main/res/drawable-xxhdpi-v11/notification_icon.png new file mode 100644 index 000000000..c107bbdd5 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi-v11/notification_icon.png differ diff --git a/app/src/main/res/drawable-xxhdpi-v9/notification_icon.png b/app/src/main/res/drawable-xxhdpi-v9/notification_icon.png new file mode 100644 index 000000000..07e6b5dd9 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi-v9/notification_icon.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_camera.png b/app/src/main/res/drawable-xxhdpi/ic_action_camera.png new file mode 100644 index 000000000..2d24bb453 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_camera.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_clock.png b/app/src/main/res/drawable-xxhdpi/ic_action_clock.png new file mode 100644 index 000000000..1fd1c45c4 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_clock.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_edit.png b/app/src/main/res/drawable-xxhdpi/ic_action_edit.png new file mode 100644 index 000000000..3ee6e4e9b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_edit.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_gear.png b/app/src/main/res/drawable-xxhdpi/ic_action_gear.png new file mode 100644 index 000000000..412c4ce6a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_gear.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_globe.png b/app/src/main/res/drawable-xxhdpi/ic_action_globe.png new file mode 100644 index 000000000..a51cef202 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_globe.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_home.png b/app/src/main/res/drawable-xxhdpi/ic_action_home.png new file mode 100644 index 000000000..378a25f42 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_home.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_lock_closed.png b/app/src/main/res/drawable-xxhdpi/ic_action_lock_closed.png new file mode 100644 index 000000000..4200249d8 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_lock_closed.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_lock_open.png b/app/src/main/res/drawable-xxhdpi/ic_action_lock_open.png new file mode 100644 index 000000000..29d700ffe Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_lock_open.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_more.png b/app/src/main/res/drawable-xxhdpi/ic_action_more.png new file mode 100644 index 000000000..9d1de2890 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_more.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_search.png b/app/src/main/res/drawable-xxhdpi/ic_action_search.png new file mode 100644 index 000000000..7d8c57bfc Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_search.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_star.png b/app/src/main/res/drawable-xxhdpi/ic_action_star.png new file mode 100644 index 000000000..6754759e0 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_star.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_users.png b/app/src/main/res/drawable-xxhdpi/ic_action_users.png new file mode 100644 index 000000000..8959c6682 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_users.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_action_volume_mute.png b/app/src/main/res/drawable-xxhdpi/ic_action_volume_mute.png new file mode 100644 index 000000000..afbeedd34 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_action_volume_mute.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_block.png b/app/src/main/res/drawable-xxhdpi/ic_block.png new file mode 100644 index 000000000..555170e8b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_block.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_block_white.png b/app/src/main/res/drawable-xxhdpi/ic_block_white.png new file mode 100644 index 000000000..2b4c5bb4d Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_block_white.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_close.png b/app/src/main/res/drawable-xxhdpi/ic_close.png new file mode 100644 index 000000000..783a624a3 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_close.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_email.png b/app/src/main/res/drawable-xxhdpi/ic_email.png new file mode 100644 index 000000000..bb00df40a Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_email.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_fav_black.png b/app/src/main/res/drawable-xxhdpi/ic_fav_black.png new file mode 100644 index 000000000..d796e3803 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_fav_black.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_fav_yellow.png b/app/src/main/res/drawable-xxhdpi/ic_fav_yellow.png new file mode 100644 index 000000000..67fc9a1a2 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_fav_yellow.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_favorite_menu.png b/app/src/main/res/drawable-xxhdpi/ic_favorite_menu.png new file mode 100644 index 000000000..3a98f4d4c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_favorite_menu.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_local_post_office.png b/app/src/main/res/drawable-xxhdpi/ic_local_post_office.png new file mode 100644 index 000000000..9abfba56b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_local_post_office.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_message.png b/app/src/main/res/drawable-xxhdpi/ic_message.png new file mode 100644 index 000000000..19fab4736 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_message.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_mute_white.png b/app/src/main/res/drawable-xxhdpi/ic_mute_white.png new file mode 100644 index 000000000..fb221f4c5 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_mute_white.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_notifications.png b/app/src/main/res/drawable-xxhdpi/ic_notifications.png new file mode 100644 index 000000000..bb99098c8 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_notifications.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_photo.png b/app/src/main/res/drawable-xxhdpi/ic_photo.png new file mode 100644 index 000000000..3483c6420 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_photo.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_reply.png b/app/src/main/res/drawable-xxhdpi/ic_reply.png new file mode 100644 index 000000000..26032394c Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_reply.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_retweet.png b/app/src/main/res/drawable-xxhdpi/ic_retweet.png new file mode 100644 index 000000000..530129397 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_retweet.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_retweet_black.png b/app/src/main/res/drawable-xxhdpi/ic_retweet_black.png new file mode 100644 index 000000000..b658d9366 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_retweet_black.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_retweet_header.png b/app/src/main/res/drawable-xxhdpi/ic_retweet_header.png new file mode 100644 index 000000000..530129397 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_retweet_header.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_retweet_yellow.png b/app/src/main/res/drawable-xxhdpi/ic_retweet_yellow.png new file mode 100644 index 000000000..949da5e6b Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_retweet_yellow.png differ diff --git a/app/src/main/res/drawable-xxhdpi/mastodon_icon.png b/app/src/main/res/drawable-xxhdpi/mastodon_icon.png new file mode 100644 index 000000000..1b28aedc7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/mastodon_icon.png differ diff --git a/app/src/main/res/drawable-xxhdpi/notification_icon.png b/app/src/main/res/drawable-xxhdpi/notification_icon.png new file mode 100644 index 000000000..e822ba7a7 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/notification_icon.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_camera.png b/app/src/main/res/drawable-xxxhdpi/ic_action_camera.png new file mode 100644 index 000000000..66ca0488a Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_camera.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_clock.png b/app/src/main/res/drawable-xxxhdpi/ic_action_clock.png new file mode 100644 index 000000000..e641049d7 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_clock.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_edit.png b/app/src/main/res/drawable-xxxhdpi/ic_action_edit.png new file mode 100644 index 000000000..7208a4203 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_edit.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_gear.png b/app/src/main/res/drawable-xxxhdpi/ic_action_gear.png new file mode 100644 index 000000000..c86213e86 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_gear.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_globe.png b/app/src/main/res/drawable-xxxhdpi/ic_action_globe.png new file mode 100644 index 000000000..332518dcb Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_globe.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_home.png b/app/src/main/res/drawable-xxxhdpi/ic_action_home.png new file mode 100644 index 000000000..2ff620776 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_home.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_lock_closed.png b/app/src/main/res/drawable-xxxhdpi/ic_action_lock_closed.png new file mode 100644 index 000000000..d0cf3ea96 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_lock_closed.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_lock_open.png b/app/src/main/res/drawable-xxxhdpi/ic_action_lock_open.png new file mode 100644 index 000000000..94ea1a42c Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_lock_open.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_more.png b/app/src/main/res/drawable-xxxhdpi/ic_action_more.png new file mode 100644 index 000000000..e91cc74b5 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_more.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_search.png b/app/src/main/res/drawable-xxxhdpi/ic_action_search.png new file mode 100644 index 000000000..aa17f3137 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_search.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_star.png b/app/src/main/res/drawable-xxxhdpi/ic_action_star.png new file mode 100644 index 000000000..313bfe228 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_star.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_users.png b/app/src/main/res/drawable-xxxhdpi/ic_action_users.png new file mode 100644 index 000000000..29767b9d0 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_users.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_action_volume_mute.png b/app/src/main/res/drawable-xxxhdpi/ic_action_volume_mute.png new file mode 100644 index 000000000..cedee0719 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_action_volume_mute.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_block.png b/app/src/main/res/drawable-xxxhdpi/ic_block.png new file mode 100644 index 000000000..266947948 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_block.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_block_white.png b/app/src/main/res/drawable-xxxhdpi/ic_block_white.png new file mode 100644 index 000000000..b1f309fc7 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_block_white.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_close.png b/app/src/main/res/drawable-xxxhdpi/ic_close.png new file mode 100644 index 000000000..6e484ec63 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_close.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_email.png b/app/src/main/res/drawable-xxxhdpi/ic_email.png new file mode 100644 index 000000000..9abfba56b Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_email.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_fav_black.png b/app/src/main/res/drawable-xxxhdpi/ic_fav_black.png new file mode 100644 index 000000000..43e598415 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_fav_black.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_fav_yellow.png b/app/src/main/res/drawable-xxxhdpi/ic_fav_yellow.png new file mode 100644 index 000000000..226cea06f Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_fav_yellow.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_favorite_menu.png b/app/src/main/res/drawable-xxxhdpi/ic_favorite_menu.png new file mode 100644 index 000000000..f8b1e4176 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_favorite_menu.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_local_post_office.png b/app/src/main/res/drawable-xxxhdpi/ic_local_post_office.png new file mode 100644 index 000000000..766a707d6 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_local_post_office.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_message.png b/app/src/main/res/drawable-xxxhdpi/ic_message.png new file mode 100644 index 000000000..672e3e753 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_message.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_mute_white.png b/app/src/main/res/drawable-xxxhdpi/ic_mute_white.png new file mode 100644 index 000000000..41fd6b67b Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_mute_white.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_notifications.png b/app/src/main/res/drawable-xxxhdpi/ic_notifications.png new file mode 100644 index 000000000..e498a2f21 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_notifications.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_photo.png b/app/src/main/res/drawable-xxxhdpi/ic_photo.png new file mode 100644 index 000000000..225084b47 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_photo.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_reply.png b/app/src/main/res/drawable-xxxhdpi/ic_reply.png new file mode 100644 index 000000000..b0832b0f4 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_reply.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_retweet.png b/app/src/main/res/drawable-xxxhdpi/ic_retweet.png new file mode 100644 index 000000000..8657eb3a6 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_retweet.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_retweet_black.png b/app/src/main/res/drawable-xxxhdpi/ic_retweet_black.png new file mode 100644 index 000000000..120a01dd5 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_retweet_black.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_retweet_header.png b/app/src/main/res/drawable-xxxhdpi/ic_retweet_header.png new file mode 100644 index 000000000..8657eb3a6 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_retweet_header.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_retweet_yellow.png b/app/src/main/res/drawable-xxxhdpi/ic_retweet_yellow.png new file mode 100644 index 000000000..41ce77db0 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_retweet_yellow.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/mastodon_icon.png b/app/src/main/res/drawable-xxxhdpi/mastodon_icon.png new file mode 100644 index 000000000..298515f1e Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/mastodon_icon.png differ diff --git a/app/src/main/res/drawable/background_splash.xml b/app/src/main/res/drawable/background_splash.xml new file mode 100644 index 000000000..3b4b972b7 --- /dev/null +++ b/app/src/main/res/drawable/background_splash.xml @@ -0,0 +1,28 @@ + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dr_date.xml b/app/src/main/res/drawable/dr_date.xml new file mode 100644 index 000000000..dc90caa02 --- /dev/null +++ b/app/src/main/res/drawable/dr_date.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dr_favorites.xml b/app/src/main/res/drawable/dr_favorites.xml new file mode 100644 index 000000000..9a3fac5f3 --- /dev/null +++ b/app/src/main/res/drawable/dr_favorites.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/dr_reblogs.xml b/app/src/main/res/drawable/dr_reblogs.xml new file mode 100644 index 000000000..3cac87720 --- /dev/null +++ b/app/src/main/res/drawable/dr_reblogs.xml @@ -0,0 +1,9 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/mastodon_icon.png b/app/src/main/res/drawable/mastodon_icon.png new file mode 100644 index 000000000..920488763 Binary files /dev/null and b/app/src/main/res/drawable/mastodon_icon.png differ diff --git a/app/src/main/res/drawable/mastodon_logo.png b/app/src/main/res/drawable/mastodon_logo.png new file mode 100644 index 000000000..f0df29927 Binary files /dev/null and b/app/src/main/res/drawable/mastodon_logo.png differ diff --git a/app/src/main/res/drawable/shape_border_bottom_settings.xml b/app/src/main/res/drawable/shape_border_bottom_settings.xml new file mode 100644 index 000000000..1fd1b9c17 --- /dev/null +++ b/app/src/main/res/drawable/shape_border_bottom_settings.xml @@ -0,0 +1,19 @@ + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/drawable/side_nav_bar.xml b/app/src/main/res/drawable/side_nav_bar.xml new file mode 100644 index 000000000..ce846636e --- /dev/null +++ b/app/src/main/res/drawable/side_nav_bar.xml @@ -0,0 +1,9 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_about.xml b/app/src/main/res/layout/activity_about.xml new file mode 100644 index 000000000..988185f89 --- /dev/null +++ b/app/src/main/res/layout/activity_about.xml @@ -0,0 +1,56 @@ + + + + + + + + \ No newline at end of file diff --git a/app/src/main/res/layout/activity_login.xml b/app/src/main/res/layout/activity_login.xml new file mode 100644 index 000000000..97fd84b21 --- /dev/null +++ b/app/src/main/res/layout/activity_login.xml @@ -0,0 +1,60 @@ + + + + + +