diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml index 99b9a48f4..afdee598c 100644 --- a/app/src/main/AndroidManifest.xml +++ b/app/src/main/AndroidManifest.xml @@ -80,6 +80,11 @@ android:configChanges="orientation|screenSize" android:label="@string/app_name" /> + . */ +package fr.gouv.etalab.mastodon.activities; + + +import android.content.Intent; +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.v7.app.AppCompatActivity; +import android.text.Html; +import android.util.Log; +import android.view.MenuItem; +import android.view.View; +import android.widget.Button; +import android.widget.LinearLayout; +import android.widget.RelativeLayout; +import android.widget.TextView; +import android.widget.Toast; + +import fr.gouv.etalab.mastodon.asynctasks.RetrieveInstanceAsyncTask; +import fr.gouv.etalab.mastodon.client.APIResponse; +import fr.gouv.etalab.mastodon.client.Entities.Instance; +import fr.gouv.etalab.mastodon.helper.Helper; +import fr.gouv.etalab.mastodon.interfaces.OnRetrieveInstanceInterface; +import mastodon.etalab.gouv.fr.mastodon.R; + + +/** + * Created by Thomas on 05/06/2017. + * Instance activity + */ + +public class InstanceActivity extends AppCompatActivity implements OnRetrieveInstanceInterface { + + private Button about_developer; + private LinearLayout instance_container; + private RelativeLayout loader; + + @SuppressWarnings("deprecation") + @Override + protected void onCreate(Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + if( getSupportActionBar() != null) + getSupportActionBar().setDisplayHomeAsUpEnabled(true); + setContentView(R.layout.activity_instance); + + instance_container = (LinearLayout) findViewById(R.id.instance_container); + loader = (RelativeLayout) findViewById(R.id.loader); + instance_container.setVisibility(View.GONE); + loader.setVisibility(View.VISIBLE); + setTitle(getString(R.string.action_about_instance)); + new RetrieveInstanceAsyncTask(getApplicationContext(), InstanceActivity.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 onRetrieveInstance(APIResponse apiResponse) { + instance_container.setVisibility(View.VISIBLE); + loader.setVisibility(View.GONE); + if( apiResponse.getError() != null){ + Toast.makeText(getApplicationContext(), R.string.toast_error, Toast.LENGTH_LONG).show(); + return; + } + final Instance instance = apiResponse.getInstance(); + TextView instance_title = (TextView) findViewById(R.id.instance_title); + TextView instance_description = (TextView) findViewById(R.id.instance_description); + TextView instance_version = (TextView) findViewById(R.id.instance_version); + TextView instance_uri = (TextView) findViewById(R.id.instance_uri); + FloatingActionButton instance_contact = (FloatingActionButton) findViewById(R.id.instance_contact); + + instance_title.setText(instance.getTitle()); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + instance_description.setText(Html.fromHtml(instance.getDescription(), Html.FROM_HTML_MODE_COMPACT)); + else + //noinspection deprecation + instance_description.setText(Html.fromHtml(instance.getDescription())); + if( instance.getDescription() == null || instance.getDescription().trim().length() == 0 ) + instance_description.setText(getString(R.string.instance_no_description)); + instance_version.setText(instance.getVersion()); + instance_uri.setText(instance.getUri()); + if( instance.getEmail() == null){ + instance_contact.setVisibility(View.GONE); + } + + instance_contact.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + Intent emailIntent = new Intent(Intent.ACTION_SENDTO, Uri.fromParts("mailto",instance.getEmail(), null)); + emailIntent.putExtra(Intent.EXTRA_SUBJECT, "[Mastodon] - " + instance.getUri()); + startActivity(Intent.createChooser(emailIntent, getString(R.string.send_email))); + } + }); + } +} 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 index ec6f329f6..903342767 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainActivity.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/activities/MainActivity.java @@ -285,7 +285,10 @@ public class MainActivity extends AppCompatActivity }else if(id == R.id.action_privacy){ Intent intent = new Intent(getApplicationContext(), PrivacyActivity.class); startActivity(intent); - }else if(id == R.id.action_search){ + }else if(id == R.id.action_about_instance){ + Intent intent = new Intent(getApplicationContext(), InstanceActivity.class); + startActivity(intent); + } else if(id == R.id.action_search){ if( toolbar.getChildCount() > 0){ for(int i = 0 ; i < toolbar.getChildCount() ; i++){ diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveAccountInfoAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveAccountInfoAsyncTask.java new file mode 100644 index 000000000..be739d7f3 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveAccountInfoAsyncTask.java @@ -0,0 +1,52 @@ +/* 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.OnRetrieveAccountInterface; + +/** + * Created by Thomas on 04/06/2017. + * Verify credential + */ + +public class RetrieveAccountInfoAsyncTask extends AsyncTask { + + private Context context; + private OnRetrieveAccountInterface listener; + private Account account; + private API api; + + public RetrieveAccountInfoAsyncTask(Context context, OnRetrieveAccountInterface onRetrieveAccountInterface){ + this.context = context; + this.listener = onRetrieveAccountInterface; + } + + @Override + protected Void doInBackground(Void... params) { + api = new API(context); + account = api.verifyCredentials(); + return null; + } + + @Override + protected void onPostExecute(Void result) { + listener.onRetrieveAccount(account, api.getError()); + } + +} \ No newline at end of file diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveInstanceAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveInstanceAsyncTask.java new file mode 100644 index 000000000..48d18e729 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/RetrieveInstanceAsyncTask.java @@ -0,0 +1,52 @@ +/* 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.APIResponse; +import fr.gouv.etalab.mastodon.interfaces.OnRetrieveInstanceInterface; + + +/** + * Created by Thomas on 05/06/2017. + * Retrieves the current instance + */ + +public class RetrieveInstanceAsyncTask extends AsyncTask { + + private Context context; + private OnRetrieveInstanceInterface listener; + private APIResponse apiResponse; + + public RetrieveInstanceAsyncTask(Context context, OnRetrieveInstanceInterface onRetrieveInstanceInterface){ + this.context = context; + this.listener = onRetrieveInstanceInterface; + } + + @Override + protected Void doInBackground(Void... params) { + apiResponse = new API(context).getInstance(); + return null; + } + + @Override + protected void onPostExecute(Void result) { + listener.onRetrieveInstance(apiResponse); + } + +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/UpdateCredentialAsyncTask.java b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/UpdateCredentialAsyncTask.java new file mode 100644 index 000000000..c99b8f97a --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/asynctasks/UpdateCredentialAsyncTask.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.APIResponse; +import fr.gouv.etalab.mastodon.interfaces.OnUpdateCredentialInterface; + +/** + * Created by Thomas on 05/06/2017. + * Update account credential + */ + +public class UpdateCredentialAsyncTask extends AsyncTask { + + private Context context; + private String display_name, note, avatar, header; + private APIResponse apiResponse; + private OnUpdateCredentialInterface listener; + + public UpdateCredentialAsyncTask(Context context, String display_name, String note, String avatar, String header, OnUpdateCredentialInterface onUpdateCredentialInterface){ + this.context = context; + this.display_name = display_name; + this.note = note; + this.avatar = avatar; + this.header = header; + this.listener = onUpdateCredentialInterface; + } + + @Override + protected Void doInBackground(Void... params) { + apiResponse = new API(context).updateCredential(display_name, note, avatar, header); + return null; + } + + @Override + protected void onPostExecute(Void result) { + listener.onUpdateCredential(apiResponse); + } + +} \ No newline at end of file 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 index 5363871e5..8377ee75a 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/API.java @@ -16,6 +16,7 @@ package fr.gouv.etalab.mastodon.client; import android.content.Context; import android.content.SharedPreferences; +import android.util.Log; import android.widget.Toast; import com.loopj.android.http.AsyncHttpResponseHandler; @@ -71,6 +72,7 @@ public class API { private int tootPerPage, accountPerPage, notificationPerPage; private int actionCode; private String instance; + private Instance instanceEntity; private String prefKeyOauthTokenT; private APIResponse apiResponse; private Error APIError; @@ -123,6 +125,57 @@ public class API { } + /*** + * Get info on the current Instance *synchronously* + * @return APIResponse + */ + public APIResponse getInstance() { + get("/instance", null, new JsonHttpResponseHandler() { + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + instanceEntity = parseInstance(response); + } + @Override + public void onFailure(int statusCode, Header[] headers, Throwable error, JSONObject response){ + setError(statusCode, error); + } + }); + apiResponse.setInstance(instanceEntity); + return apiResponse; + } + + /*** + * Update credential of the authenticated user *synchronously* + * @return APIResponse + */ + public APIResponse updateCredential(String display_name, String note, String avatar, String header) { + RequestParams requestParams = new RequestParams(); + + if( display_name != null) + requestParams.add("display_name",display_name); + if( note != null) + requestParams.add("note",note); + if( avatar != null) + requestParams.add("avatar",avatar); + if( header != null) + requestParams.add("header",header); + patch("/accounts/update_credentials", requestParams, new JsonHttpResponseHandler() { + @Override + public void onSuccess(int statusCode, Header[] headers, JSONObject response) { + } + @Override + public void onSuccess(int statusCode, Header[] headers, JSONArray response) { + } + + @Override + public void onFailure(int statusCode, Header[] headers, Throwable error, JSONObject response){ + setError(statusCode, error); + } + }); + return apiResponse; + } + + /*** * Verifiy credential of the authenticated user *synchronously* * @return Account @@ -215,7 +268,7 @@ public class API { * Retrieves status for the account *synchronously* * * @param accountId String Id of the account - * @return List + * @return APIResponse */ public APIResponse getStatus(String accountId) { return getStatus(accountId, false, false, null, null, tootPerPage); @@ -226,7 +279,7 @@ public class API { * * @param accountId String Id of the account * @param max_id String id max - * @return List + * @return APIResponse */ public APIResponse getStatus(String accountId, String max_id) { return getStatus(accountId, false, false, max_id, null, tootPerPage); @@ -288,7 +341,7 @@ public class API { * Retrieves one status *synchronously* * * @param statusId String Id of the status - * @return List + * @return APIResponse */ public APIResponse getStatusbyId(String statusId) { statuses = new ArrayList<>(); @@ -341,7 +394,7 @@ public class API { /** * Retrieves home timeline for the account *synchronously* * @param max_id String id max - * @return List + * @return APIResponse */ public APIResponse getHomeTimeline( String max_id) { return getHomeTimeline(max_id, null, tootPerPage); @@ -349,7 +402,7 @@ public class API { /** * Retrieves home timeline for the account since an Id value *synchronously* - * @return List + * @return APIResponse */ public APIResponse getHomeTimelineSinceId(String since_id) { return getHomeTimeline(null, since_id, tootPerPage); @@ -402,7 +455,7 @@ public class API { * Retrieves public timeline for the account *synchronously* * @param local boolean only local timeline * @param max_id String id max - * @return List + * @return APIResponse */ public APIResponse getPublicTimeline(boolean local, String max_id){ return getPublicTimeline(local, max_id, null, tootPerPage); @@ -457,7 +510,7 @@ public class API { * @param tag String * @param local boolean only local timeline * @param max_id String id max - * @return List + * @return APIResponse */ public APIResponse getPublicTimelineTag(String tag, boolean local, String max_id){ return getPublicTimelineTag(tag, local, max_id, null, tootPerPage); @@ -513,7 +566,7 @@ public class API { /** * Retrieves muted users by the authenticated account *synchronously* * @param max_id String id max - * @return List + * @return APIResponse */ public APIResponse getMuted(String max_id){ return getAccounts("/mutes", max_id, null, accountPerPage); @@ -522,7 +575,7 @@ public class API { /** * Retrieves blocked users by the authenticated account *synchronously* * @param max_id String id max - * @return List + * @return APIResponse */ public APIResponse getBlocks(String max_id){ return getAccounts("/blocks", max_id, null, accountPerPage); @@ -533,7 +586,7 @@ public class API { * Retrieves following for the account specified by targetedId *synchronously* * @param targetedId String targetedId * @param max_id String id max - * @return List + * @return APIResponse */ public APIResponse getFollowing(String targetedId, String max_id){ return getAccounts(String.format("/accounts/%s/following",targetedId),max_id, null, accountPerPage); @@ -543,7 +596,7 @@ public class API { * Retrieves followers for the account specified by targetedId *synchronously* * @param targetedId String targetedId * @param max_id String id max - * @return List + * @return APIResponse */ public APIResponse getFollowers(String targetedId, String max_id){ return getAccounts(String.format("/accounts/%s/followers",targetedId),max_id, null, accountPerPage); @@ -595,7 +648,7 @@ public class API { /** * Retrieves favourited status for the authenticated account *synchronously* * @param max_id String id max - * @return List + * @return APIResponse */ public APIResponse getFavourites(String max_id){ return getFavourites(max_id, null, tootPerPage); @@ -788,7 +841,7 @@ public class API { /** * Retrieves notifications for the authenticated account since an id*synchronously* * @param since_id String since max - * @return List + * @return APIResponse */ public APIResponse getNotificationsSince(String since_id){ return getNotifications(null, since_id, notificationPerPage); @@ -797,7 +850,7 @@ public class API { /** * Retrieves notifications for the authenticated account *synchronously* * @param max_id String id max - * @return List + * @return APIResponse */ public APIResponse getNotifications(String max_id){ return getNotifications(max_id, null, notificationPerPage); @@ -899,7 +952,7 @@ public class API { /** * Retrieves Developer account when searching (ie: via @...) *synchronously* * - * @return List + * @return APIResponse */ public APIResponse searchDeveloper() { RequestParams params = new RequestParams(); @@ -931,7 +984,7 @@ public class API { * Retrieves Accounts when searching (ie: via @...) *synchronously* * * @param query String search - * @return List + * @return APIResponse */ public APIResponse searchAccounts(String query) { @@ -1086,6 +1139,25 @@ public class API { return status; } + /** + * Parse json response an unique instance + * @param resobj JSONObject + * @return Instance + */ + private Instance parseInstance(JSONObject resobj){ + + Instance instance = new Instance(); + try { + instance.setUri(resobj.get("uri").toString()); + instance.setTitle(resobj.get("title").toString()); + instance.setDescription(resobj.get("description").toString()); + instance.setEmail(resobj.get("email").toString()); + instance.setVersion(resobj.get("version").toString()); + } catch (JSONException e) { + e.printStackTrace(); + } + return instance; + } /** * Parse json response an unique account @@ -1340,6 +1412,21 @@ public class API { } } + private void patch(String action, RequestParams params, AsyncHttpResponseHandler responseHandler){ + try { + client.setConnectTimeout(10000); //10s timeout + client.setUserAgent(USER_AGENT); + client.addHeader("Authorization", "Bearer "+prefKeyOauthTokenT); + MastalabSSLSocketFactory mastalabSSLSocketFactory = new MastalabSSLSocketFactory(MastalabSSLSocketFactory.getKeystore()); + mastalabSSLSocketFactory.setHostnameVerifier(MastalabSSLSocketFactory.ALLOW_ALL_HOSTNAME_VERIFIER); + client.setSSLSocketFactory(mastalabSSLSocketFactory); + client.patch(getAbsoluteUrl(action), params, responseHandler); + } catch (NoSuchAlgorithmException | KeyManagementException | KeyStoreException | UnrecoverableKeyException e) { + Toast.makeText(context, R.string.toast_error,Toast.LENGTH_LONG).show(); + e.printStackTrace(); + } + } + public Error getError(){ return APIError; } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/APIResponse.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/APIResponse.java index 57940d922..9ad0872ca 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/client/APIResponse.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/APIResponse.java @@ -20,6 +20,7 @@ public class APIResponse { private List notifications = null; private fr.gouv.etalab.mastodon.client.Entities.Error error = null; private String since_id, max_id; + private Instance instance; public List getAccounts() { return accounts; @@ -76,4 +77,12 @@ public class APIResponse { public void setSince_id(String since_id) { this.since_id = since_id; } + + public Instance getInstance() { + return instance; + } + + public void setInstance(Instance instance) { + this.instance = instance; + } } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Instance.java b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Instance.java new file mode 100644 index 000000000..df115abab --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/client/Entities/Instance.java @@ -0,0 +1,69 @@ +/* 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 05/06/2017. + * Describes instance + */ + +public class Instance { + + private String uri; + private String title; + private String description; + private String email; + private String version; + + public String getUri() { + return uri; + } + + public void setUri(String uri) { + this.uri = uri; + } + + public String getTitle() { + return title; + } + + public void setTitle(String title) { + this.title = title; + } + + public String getDescription() { + return description; + } + + public void setDescription(String description) { + this.description = description; + } + + public String getEmail() { + return email; + } + + public void setEmail(String email) { + this.email = email; + } + + public String getVersion() { + return version; + } + + public void setVersion(String version) { + this.version = version; + } +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/fragments/SettingsProfileFragment.java b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/SettingsProfileFragment.java new file mode 100644 index 000000000..adeacd465 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/SettingsProfileFragment.java @@ -0,0 +1,361 @@ +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.app.Activity; +import android.app.AlertDialog; +import android.content.Context; +import android.content.DialogInterface; +import android.content.Intent; +import android.graphics.Bitmap; +import android.graphics.BitmapFactory; +import android.graphics.drawable.BitmapDrawable; +import android.os.AsyncTask; +import android.os.Build; +import android.os.Bundle; +import android.support.v4.app.Fragment; +import android.text.Editable; +import android.text.Html; +import android.text.TextWatcher; +import android.util.Base64; +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.Button; +import android.widget.EditText; +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.io.BufferedInputStream; +import java.io.ByteArrayOutputStream; +import java.io.FileNotFoundException; +import java.io.InputStream; + +import fr.gouv.etalab.mastodon.activities.MainActivity; +import fr.gouv.etalab.mastodon.asynctasks.RetrieveAccountInfoAsyncTask; +import fr.gouv.etalab.mastodon.asynctasks.UpdateCredentialAsyncTask; +import fr.gouv.etalab.mastodon.client.APIResponse; +import fr.gouv.etalab.mastodon.client.Entities.Account; +import fr.gouv.etalab.mastodon.client.Entities.Error; +import fr.gouv.etalab.mastodon.interfaces.OnRetrieveAccountInterface; +import fr.gouv.etalab.mastodon.interfaces.OnUpdateCredentialInterface; +import mastodon.etalab.gouv.fr.mastodon.R; + + +/** + * Created by Thomas on 04/06/2017. + * Fragment for profile settings + */ +public class SettingsProfileFragment extends Fragment implements OnRetrieveAccountInterface, OnUpdateCredentialInterface { + + + private Context context; + private EditText set_profile_name, set_profile_description; + private ImageView set_profile_picture, set_header_picture; + private Button set_change_profile_picture, set_change_header_picture, set_profile_save; + private TextView set_header_picture_overlay; + private ImageLoader imageLoader; + private DisplayImageOptions options; + private static final int PICK_IMAGE_HEADER = 4565; + private static final int PICK_IMAGE_PROFILE = 6545; + private String profile_picture, header_picture, profile_username, profile_note; + private Bitmap profile_picture_bmp, profile_header_bmp; + + @Override + public View onCreateView(LayoutInflater inflater, ViewGroup container, Bundle savedInstanceState) { + + View rootView = inflater.inflate(R.layout.fragment_settings_profile, container, false); + + set_profile_name = (EditText) rootView.findViewById(R.id.set_profile_name); + set_profile_description = (EditText) rootView.findViewById(R.id.set_profile_description); + set_profile_picture = (ImageView) rootView.findViewById(R.id.set_profile_picture); + set_header_picture = (ImageView) rootView.findViewById(R.id.set_header_picture); + set_change_profile_picture = (Button) rootView.findViewById(R.id.set_change_profile_picture); + set_change_header_picture = (Button) rootView.findViewById(R.id.set_change_header_picture); + set_profile_save = (Button) rootView.findViewById(R.id.set_profile_save); + set_header_picture_overlay = (TextView) rootView.findViewById(R.id.set_header_picture_overlay); + + set_profile_save.setEnabled(false); + set_change_header_picture.setEnabled(false); + set_change_profile_picture.setEnabled(false); + set_profile_name.setEnabled(false); + set_profile_description.setEnabled(false); + context = getContext(); + imageLoader = ImageLoader.getInstance(); + options = new DisplayImageOptions.Builder().displayer(new SimpleBitmapDisplayer()).cacheInMemory(false) + .cacheOnDisk(true).resetViewBeforeLoading(true).build(); + new RetrieveAccountInfoAsyncTask(context, SettingsProfileFragment.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; + } + + + @Override + public void onRetrieveAccount(Account account, Error error) { + if( error != null ){ + Toast.makeText(context,R.string.toast_error, Toast.LENGTH_LONG).show(); + return; + } + set_profile_name.setText(account.getDisplay_name()); + + final String content; + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) + content = Html.fromHtml(account.getNote(), Html.FROM_HTML_MODE_COMPACT).toString(); + else + //noinspection deprecation + content = Html.fromHtml(account.getNote()).toString(); + set_profile_description.setText(content); + + set_profile_save.setEnabled(true); + set_change_header_picture.setEnabled(true); + set_change_profile_picture.setEnabled(true); + set_profile_name.setEnabled(true); + set_profile_description.setEnabled(true); + + set_profile_description.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() > 160){ + String content = s.toString().substring(0,160); + set_profile_description.setText(content); + set_profile_description.setSelection(set_profile_description.getText().length()); + Toast.makeText(context,R.string.note_no_space,Toast.LENGTH_LONG).show(); + } + } + }); + + set_profile_name.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() > 30){ + String content = s.toString().substring(0,30); + set_profile_name.setText(content); + set_profile_name.setSelection(set_profile_name.getText().length()); + Toast.makeText(context,R.string.username_no_space,Toast.LENGTH_LONG).show(); + } + } + }); + + + set_change_header_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_HEADER); + } + }); + + set_change_profile_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_PROFILE); + } + }); + + imageLoader.displayImage(account.getAvatar(), set_profile_picture, options); + imageLoader.displayImage(account.getHeader(), set_header_picture, options); + + if( account.getHeader() == null || account.getHeader().contains("missing.png")) + set_header_picture_overlay.setVisibility(View.VISIBLE); + + + set_profile_save.setOnClickListener(new View.OnClickListener() { + @Override + public void onClick(View v) { + if(set_profile_name.getText() != null && !set_profile_name.getText().toString().equals(set_profile_name.getHint())) + profile_username = set_profile_name.getText().toString().trim(); + else + profile_username = null; + + if(set_profile_description.getText() != null && !set_profile_description.getText().toString().equals(set_profile_description.getHint())) + profile_note = set_profile_description.getText().toString().trim(); + else + profile_note = null; + + AlertDialog.Builder dialogBuilder = new AlertDialog.Builder(context); + LayoutInflater inflater = ((MainActivity) context).getLayoutInflater(); + View dialogView = inflater.inflate(R.layout.dialog_profile, null); + dialogBuilder.setView(dialogView); + + ImageView back_ground_image = (ImageView) dialogView.findViewById(R.id.back_ground_image); + ImageView dialog_profile_picture = (ImageView) dialogView.findViewById(R.id.dialog_profile_picture); + TextView dialog_profile_name = (TextView) dialogView.findViewById(R.id.dialog_profile_name); + TextView dialog_profile_description = (TextView) dialogView.findViewById(R.id.dialog_profile_description); + + if( profile_username != null) + dialog_profile_name.setText(profile_username); + if( profile_note != null) + dialog_profile_description.setText(profile_note); + if( profile_header_bmp != null) { + BitmapDrawable background = new BitmapDrawable(context.getResources(), profile_header_bmp); + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + //noinspection deprecation + back_ground_image.setBackgroundDrawable(background); + } else { + back_ground_image.setBackground(background); + } + }else { + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + //noinspection deprecation + back_ground_image.setBackgroundDrawable(set_header_picture.getDrawable()); + } else { + back_ground_image.setBackground(set_header_picture.getDrawable()); + } + } + if( profile_picture_bmp != null) { + BitmapDrawable background = new BitmapDrawable(context.getResources(), profile_picture_bmp); + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + //noinspection deprecation + dialog_profile_picture.setBackgroundDrawable(background); + } else { + dialog_profile_picture.setBackground(background); + } + }else { + if(Build.VERSION.SDK_INT < Build.VERSION_CODES.JELLY_BEAN) { + //noinspection deprecation + dialog_profile_picture.setBackgroundDrawable(set_profile_picture.getDrawable()); + } else { + dialog_profile_picture.setBackground(set_profile_picture.getDrawable()); + } + } + dialogBuilder.setPositiveButton(R.string.save, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + set_profile_save.setEnabled(false); + new UpdateCredentialAsyncTask(context, profile_username, profile_note, profile_picture, header_picture, SettingsProfileFragment.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR); + } + }); + dialogBuilder.setNegativeButton(R.string.cancel, new DialogInterface.OnClickListener() { + @Override + public void onClick(DialogInterface dialog, int id) { + dialog.dismiss(); + } + }); + AlertDialog alertDialog = dialogBuilder.create(); + alertDialog.show(); + + } + }); + } + + + @Override + public void onActivityResult(int requestCode, int resultCode, Intent data) { + super.onActivityResult(requestCode, resultCode, data); + if (requestCode == PICK_IMAGE_HEADER && resultCode == Activity.RESULT_OK) { + if (data == null) { + Toast.makeText(context,R.string.toot_select_image_error,Toast.LENGTH_LONG).show(); + return; + } + try { + InputStream inputStream = context.getContentResolver().openInputStream(data.getData()); + BufferedInputStream bufferedInputStream; + if (inputStream != null) { + bufferedInputStream = new BufferedInputStream(inputStream); + }else { + Toast.makeText(context,R.string.toot_select_image_error,Toast.LENGTH_LONG).show(); + return; + } + Bitmap bmp = BitmapFactory.decodeStream(bufferedInputStream); + profile_header_bmp = Bitmap.createScaledBitmap(bmp, 700, 335, true); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + profile_header_bmp.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); + set_header_picture.setImageBitmap(profile_header_bmp); + byte[] byteArray = byteArrayOutputStream .toByteArray(); + header_picture = "data:image/png;base64, " + Base64.encodeToString(byteArray, Base64.DEFAULT); + + } catch (FileNotFoundException e) { + Toast.makeText(context,R.string.toot_select_image_error,Toast.LENGTH_LONG).show(); + e.printStackTrace(); + } + }else if(requestCode == PICK_IMAGE_PROFILE && resultCode == Activity.RESULT_OK) { + if (data == null) { + Toast.makeText(context,R.string.toot_select_image_error,Toast.LENGTH_LONG).show(); + return; + } + try { + InputStream inputStream = context.getContentResolver().openInputStream(data.getData()); + BufferedInputStream bufferedInputStream; + if (inputStream != null) { + bufferedInputStream = new BufferedInputStream(inputStream); + }else { + Toast.makeText(context,R.string.toot_select_image_error,Toast.LENGTH_LONG).show(); + return; + } + Bitmap bmp = BitmapFactory.decodeStream(bufferedInputStream); + profile_picture_bmp = Bitmap.createScaledBitmap(bmp, 120, 120, true); + ByteArrayOutputStream byteArrayOutputStream = new ByteArrayOutputStream(); + profile_picture_bmp.compress(Bitmap.CompressFormat.PNG, 100, byteArrayOutputStream); + set_profile_picture.setImageBitmap(profile_picture_bmp); + byte[] byteArray = byteArrayOutputStream .toByteArray(); + profile_picture = "data:image/png;base64, " + Base64.encodeToString(byteArray, Base64.DEFAULT); + } catch (FileNotFoundException e) { + Toast.makeText(context,R.string.toot_select_image_error,Toast.LENGTH_LONG).show(); + e.printStackTrace(); + } + } + } + + @Override + public void onUpdateCredential(APIResponse apiResponse) { + if( apiResponse.getError() != null){ + Toast.makeText(context, R.string.toast_error, Toast.LENGTH_LONG).show(); + return; + } + Toast.makeText(context, R.string.toast_update_credential_ok, Toast.LENGTH_LONG).show(); + set_profile_save.setEnabled(true); + } +} 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 index 758076190..07f568433 100644 --- a/app/src/main/java/fr/gouv/etalab/mastodon/fragments/TabLayoutSettingsFragment.java +++ b/app/src/main/java/fr/gouv/etalab/mastodon/fragments/TabLayoutSettingsFragment.java @@ -42,6 +42,7 @@ public class TabLayoutSettingsFragment extends Fragment { 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))); + tabLayout.addTab(tabLayout.newTab().setText(getString(R.string.profile))); final ViewPager viewPager = (ViewPager) inflatedView.findViewById(R.id.viewpager); viewPager.setAdapter(new PagerAdapter @@ -86,6 +87,8 @@ public class TabLayoutSettingsFragment extends Fragment { return new SettingsNotificationsFragment(); case 1: return new SettingsOptimizationFragment(); + case 2: + return new SettingsProfileFragment(); default: return new SettingsNotificationsFragment(); } diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveInstanceInterface.java b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveInstanceInterface.java new file mode 100644 index 000000000..9b0b050a2 --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnRetrieveInstanceInterface.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.APIResponse; + +/** + * Created by Thomas on 05/06/2017. + * Interface when an instance has been retrieved + */ +public interface OnRetrieveInstanceInterface { + void onRetrieveInstance(APIResponse apiResponse); +} diff --git a/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnUpdateCredentialInterface.java b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnUpdateCredentialInterface.java new file mode 100644 index 000000000..f89bc728d --- /dev/null +++ b/app/src/main/java/fr/gouv/etalab/mastodon/interfaces/OnUpdateCredentialInterface.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.APIResponse; + +/** + * Created by Thomas on 05/06/2017. + * Interface when credentials are updated + */ +public interface OnUpdateCredentialInterface { + void onUpdateCredential(APIResponse apiResponse); +} diff --git a/app/src/main/res/drawable-hdpi/ic_email_white.png b/app/src/main/res/drawable-hdpi/ic_email_white.png new file mode 100644 index 000000000..715102565 Binary files /dev/null and b/app/src/main/res/drawable-hdpi/ic_email_white.png differ diff --git a/app/src/main/res/drawable-ldpi/ic_email_white.png b/app/src/main/res/drawable-ldpi/ic_email_white.png new file mode 100644 index 000000000..985135adb Binary files /dev/null and b/app/src/main/res/drawable-ldpi/ic_email_white.png differ diff --git a/app/src/main/res/drawable-mdpi/ic_email_white.png b/app/src/main/res/drawable-mdpi/ic_email_white.png new file mode 100644 index 000000000..f1aea58fe Binary files /dev/null and b/app/src/main/res/drawable-mdpi/ic_email_white.png differ diff --git a/app/src/main/res/drawable-xhdpi/ic_email_white.png b/app/src/main/res/drawable-xhdpi/ic_email_white.png new file mode 100644 index 000000000..77ddef573 Binary files /dev/null and b/app/src/main/res/drawable-xhdpi/ic_email_white.png differ diff --git a/app/src/main/res/drawable-xxhdpi/ic_email_white.png b/app/src/main/res/drawable-xxhdpi/ic_email_white.png new file mode 100644 index 000000000..107653456 Binary files /dev/null and b/app/src/main/res/drawable-xxhdpi/ic_email_white.png differ diff --git a/app/src/main/res/drawable-xxxhdpi/ic_email_white.png b/app/src/main/res/drawable-xxxhdpi/ic_email_white.png new file mode 100644 index 000000000..2b7b8cda6 Binary files /dev/null and b/app/src/main/res/drawable-xxxhdpi/ic_email_white.png differ diff --git a/app/src/main/res/layout/activity_instance.xml b/app/src/main/res/layout/activity_instance.xml new file mode 100644 index 000000000..d352661cf --- /dev/null +++ b/app/src/main/res/layout/activity_instance.xml @@ -0,0 +1,99 @@ + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/dialog_profile.xml b/app/src/main/res/layout/dialog_profile.xml new file mode 100644 index 000000000..e6b60e39b --- /dev/null +++ b/app/src/main/res/layout/dialog_profile.xml @@ -0,0 +1,79 @@ + + + + + + + + + + + + + + + + + diff --git a/app/src/main/res/layout/fragment_settings_profile.xml b/app/src/main/res/layout/fragment_settings_profile.xml new file mode 100644 index 000000000..bb725b90f --- /dev/null +++ b/app/src/main/res/layout/fragment_settings_profile.xml @@ -0,0 +1,138 @@ + + + + + + + + + + + + + + +