Include opencollective

This commit is contained in:
stom79 2019-02-07 19:47:53 +01:00
parent 5f42aa258e
commit 9138dbd1b6
12 changed files with 527 additions and 44 deletions

View File

@ -194,6 +194,11 @@
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
/>
<activity android:name=".activities.OpencollectiveActivity"
android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="orientation|screenSize"
android:label="@string/app_name"
/>
<activity android:name=".activities.PartnerShipActivity"
android:windowSoftInputMode="stateAlwaysHidden"
android:configChanges="orientation|screenSize"

View File

@ -2210,6 +2210,10 @@ public abstract class BaseMainActivity extends BaseActivity
fragmentTag = "HOW_TO_VIDEOS";
fragmentManager.beginTransaction()
.replace(R.id.main_app_container, displayHowToFragment, fragmentTag).commit();
}else if (id == R.id.nav_opencollective) {
Intent intent = new Intent(getApplicationContext(), OpencollectiveActivity.class);
startActivity(intent);
return false;
}else if (id == R.id.nav_muted || id == R.id.nav_pixelfed_muted) {
toot.hide();
accountsFragment = new DisplayAccountsFragment();

View File

@ -0,0 +1,171 @@
/* Copyright 2017 Thomas Schneider
*
* This file is a part of Mastalab
*
* 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.
*
* Mastalab 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 Mastalab; if not,
* see <http://www.gnu.org/licenses>. */
package fr.gouv.etalab.mastodon.activities;
import android.annotation.SuppressLint;
import android.content.Context;
import android.content.Intent;
import android.content.SharedPreferences;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v7.app.ActionBar;
import android.support.v7.widget.Toolbar;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import java.util.ArrayList;
import java.util.List;
import es.dmoral.toasty.Toasty;
import fr.gouv.etalab.mastodon.R;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveOpenCollectiveAsyncTask;
import fr.gouv.etalab.mastodon.client.Entities.Account;
import fr.gouv.etalab.mastodon.client.Entities.Results;
import fr.gouv.etalab.mastodon.drawers.AccountSearchDevAdapter;
import fr.gouv.etalab.mastodon.helper.ExpandableHeightListView;
import fr.gouv.etalab.mastodon.helper.Helper;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveRemoteAccountInterface;
import static fr.gouv.etalab.mastodon.helper.Helper.THEME_LIGHT;
/**
* Created by Thomas on 08/02/2019.
* Opencollective activity
*/
public class OpencollectiveActivity extends BaseActivity implements OnRetrieveRemoteAccountInterface {
private List<Account> bakers = new ArrayList<>();
private List<Account> sponsors = new ArrayList<>();
private AccountSearchDevAdapter backersAdapter;
private AccountSearchDevAdapter sponsorsAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
int theme = sharedpreferences.getInt(Helper.SET_THEME, Helper.THEME_DARK);
switch (theme){
case Helper.THEME_LIGHT:
setTheme(R.style.AppTheme);
break;
case Helper.THEME_DARK:
setTheme(R.style.AppThemeDark);
break;
case Helper.THEME_BLACK:
setTheme(R.style.AppThemeBlack);
break;
default:
setTheme(R.style.AppThemeDark);
}
if( getSupportActionBar() != null)
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
ActionBar actionBar = getSupportActionBar();
if( actionBar != null ) {
LayoutInflater inflater = (LayoutInflater) this.getSystemService(Context.LAYOUT_INFLATER_SERVICE);
assert inflater != null;
@SuppressLint("InflateParams") View view = inflater.inflate(R.layout.simple_bar, null);
actionBar.setCustomView(view, new ActionBar.LayoutParams(ViewGroup.LayoutParams.MATCH_PARENT, ViewGroup.LayoutParams.MATCH_PARENT));
actionBar.setDisplayOptions(ActionBar.DISPLAY_SHOW_CUSTOM);
ImageView toolbar_close = actionBar.getCustomView().findViewById(R.id.toolbar_close);
TextView toolbar_title = actionBar.getCustomView().findViewById(R.id.toolbar_title);
toolbar_close.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
finish();
}
});
toolbar_title.setText(R.string.action_about);
if (theme == THEME_LIGHT){
Toolbar toolbar = actionBar.getCustomView().findViewById(R.id.toolbar);
Helper.colorizeToolbar(toolbar, R.color.black, OpencollectiveActivity.this);
}
}
setContentView(R.layout.activity_opencollective);
ExpandableHeightListView lv_backers = findViewById(R.id.lv_backers);
ExpandableHeightListView lv_sponsors = findViewById(R.id.lv_sponsors);
Button about_opencollective = findViewById(R.id.about_opencollective);
about_opencollective.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent browserIntent = new Intent(Intent.ACTION_VIEW, Uri.parse("https://opencollective.com/mastalab"));
startActivity(browserIntent);
}
});
setTitle("Open Collective");
lv_backers.setExpanded(true);
lv_sponsors.setExpanded(true);
backersAdapter = new AccountSearchDevAdapter(OpencollectiveActivity.this, bakers);
lv_backers.setAdapter(backersAdapter);
sponsorsAdapter = new AccountSearchDevAdapter(OpencollectiveActivity.this, sponsors);
lv_sponsors.setAdapter(sponsorsAdapter);
new RetrieveOpenCollectiveAsyncTask(getApplicationContext(), RetrieveOpenCollectiveAsyncTask.Type.BACKERS, OpencollectiveActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new RetrieveOpenCollectiveAsyncTask(getApplicationContext(), RetrieveOpenCollectiveAsyncTask.Type.SPONSORS, OpencollectiveActivity.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 onRetrieveRemoteAccount(Results results) {
SharedPreferences sharedpreferences = getSharedPreferences(Helper.APP_PREFS, Context.MODE_PRIVATE);
if( results == null){
Toasty.error(getApplicationContext(), getString(R.string.toast_error),Toast.LENGTH_LONG).show();
return;
}
List<Account> accounts = results.getAccounts();
if( accounts != null && accounts.size() > 0){
if( accounts.get(0).getSocial().equals("OPENCOLLECTIVE_BACKER")){
bakers.addAll(accounts);
backersAdapter.notifyDataSetChanged();
}else if( accounts.get(0).getSocial().equals("OPENCOLLECTIVE_SPONSOR")){
sponsors.addAll(accounts);
sponsorsAdapter.notifyDataSetChanged();
}
}
}
}

View File

@ -0,0 +1,64 @@
/* Copyright 2019 Thomas Schneider
*
* This file is a part of Mastalab
*
* 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.
*
* Mastalab 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 Mastalab; if not,
* see <http://www.gnu.org/licenses>. */
package fr.gouv.etalab.mastodon.asynctasks;
import android.content.Context;
import android.os.AsyncTask;
import java.lang.ref.WeakReference;
import fr.gouv.etalab.mastodon.client.API;
import fr.gouv.etalab.mastodon.client.Entities.Results;
import fr.gouv.etalab.mastodon.interfaces.OnRetrieveRemoteAccountInterface;
/**
* Created by Thomas on 07/02/2019.
* Retrieve backers and sponsors
*/
public class RetrieveOpenCollectiveAsyncTask extends AsyncTask<Void, Void, Void> {
private OnRetrieveRemoteAccountInterface listener;
private String url;
private Results results;
private WeakReference<Context> contextReference;
private Type type;
public enum Type{
BACKERS,
SPONSORS
}
public RetrieveOpenCollectiveAsyncTask(Context context, Type type, OnRetrieveRemoteAccountInterface onRetrieveRemoteAccountInterface){
this.type = type;
this.listener = onRetrieveRemoteAccountInterface;
this.contextReference = new WeakReference<>(context);
}
@Override
protected Void doInBackground(Void... params) {
API api = new API(this.contextReference.get());
results = api.getOpencollectiveAccounts(type);
return null;
}
@Override
protected void onPostExecute(Void result) {
listener.onRetrieveRemoteAccount(results);
}
}

View File

@ -20,6 +20,7 @@ import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.os.Bundle;
import android.support.v4.content.LocalBroadcastManager;
import android.util.Log;
import org.json.JSONArray;
import org.json.JSONException;
@ -42,6 +43,7 @@ import java.util.Map;
import fr.gouv.etalab.mastodon.R;
import fr.gouv.etalab.mastodon.activities.MainActivity;
import fr.gouv.etalab.mastodon.asynctasks.RetrieveOpenCollectiveAsyncTask;
import fr.gouv.etalab.mastodon.asynctasks.UpdateAccountInfoAsyncTask;
import fr.gouv.etalab.mastodon.client.Entities.Account;
import fr.gouv.etalab.mastodon.client.Entities.Application;
@ -1492,6 +1494,35 @@ public class API {
}
/**
* Retrieves opencollective accounts *synchronously*
* @return APIResponse
*/
public Results getOpencollectiveAccounts(RetrieveOpenCollectiveAsyncTask.Type type){
results = new Results();
accounts = new ArrayList<>();
try {
HttpsConnection httpsConnection = new HttpsConnection(context);
String response = httpsConnection.get("https://opencollective.com/mastalab/members/all.json", 60, null, prefKeyOauthTokenT);
accounts = parseOpencollectiveAccountResponse(context, type, new JSONArray(response));
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
} catch (IOException e) {
e.printStackTrace();
} catch (KeyManagementException e) {
e.printStackTrace();
} catch (JSONException e) {
e.printStackTrace();
}
Log.v(Helper.TAG,"accounts: " + accounts.size());
results.setAccounts(accounts);
return results;
}
/**
* Retrieves blocked domains for the authenticated account *synchronously*
@ -4045,6 +4076,66 @@ public class API {
}
private List<Account> parseOpencollectiveAccountResponse(Context context, RetrieveOpenCollectiveAsyncTask.Type type, JSONArray jsonArray){
List<Account> accounts = new ArrayList<>();
try {
int i = 0;
while (i < jsonArray.length() ) {
JSONObject resobj = jsonArray.getJSONObject(i);
Account account = parseOpencollectiveAccountResponse(context, type, resobj);
if( type == RetrieveOpenCollectiveAsyncTask.Type.BACKERS && account.getSocial() != null && account.getSocial().equals("OPENCOLLECTIVE_BACKER"))
accounts.add(account);
else if( type == RetrieveOpenCollectiveAsyncTask.Type.SPONSORS && account.getSocial() != null && account.getSocial().equals("OPENCOLLECTIVE_SPONSOR"))
accounts.add(account);
i++;
}
} catch (JSONException e) {
setDefaultError(e);
}
return accounts;
}
/**
* Parse json response an unique account
* @param resobj JSONObject
* @return Account
*/
@SuppressWarnings("InfiniteRecursion")
private static Account parseOpencollectiveAccountResponse(Context context, RetrieveOpenCollectiveAsyncTask.Type type, JSONObject resobj){
Account account = new Account();
try {
account.setId(resobj.get("MemberId").toString());
account.setUuid(resobj.get("MemberId").toString());
account.setUsername(resobj.get("name").toString());
account.setAcct(resobj.get("tier").toString());
account.setDisplay_name(resobj.get("name").toString());
account.setLocked(false);
account.setCreated_at(Helper.opencollectivetStringToDate(context, resobj.get("createdAt").toString()));
account.setFollowers_count(0);
account.setFollowing_count(0);
account.setStatuses_count(0);
account.setNote(resobj.get("description").toString());
account.setBot(false);
account.setMoved_to_account(null);
account.setUrl(resobj.get("profile").toString());
account.setAvatar(resobj.get("image").toString());
account.setAvatar_static(resobj.get("image").toString());
account.setHeader(null);
account.setHeader_static(null);
if(resobj.get("role").toString().equals("BACKER"))
account.setSocial("OPENCOLLECTIVE_BACKER");
else if(resobj.get("role").toString().equals("SPONSOR"))
account.setSocial("OPENCOLLECTIVE_SPONSOR");
else
account.setSocial("OPENCOLLECTIVE");
} catch (JSONException ignored) {} catch (ParseException e) {
e.printStackTrace();
}
return account;
}
/**
* Parse json response an unique account
* @param resobj JSONObject

View File

@ -235,15 +235,13 @@ public class GNUAPI {
*/
public Relationship getRelationship(String accountId) {
List<Relationship> relationships;
Relationship relationship = null;
HashMap<String, String> params = new HashMap<>();
params.put("user_id",accountId);
params.put("target_id",accountId);
try {
String response = new HttpsConnection(context).get(getAbsoluteUrl("/friendships/show.json"), 60, params, prefKeyOauthTokenT);
relationships = parseRelationshipResponse(new JSONArray(response));
if( relationships != null && relationships.size() > 0)
relationship = relationships.get(0);
relationship = parseRelationshipResponse(new JSONObject(response));
} catch (HttpsConnection.HttpsConnectionException e) {
setError(e.getStatusCode(), e);
} catch (NoSuchAlgorithmException e) {
@ -271,9 +269,9 @@ public class GNUAPI {
if( accounts != null && accounts.size() > 0 ) {
StringBuilder parameters = new StringBuilder();
for(Account account: accounts)
parameters.append("user_id[]=").append(account.getId()).append("&");
parameters = new StringBuilder(parameters.substring(0, parameters.length() - 1).substring(10));
params.put("user_id[]", parameters.toString());
parameters.append("target_id[]=").append(account.getId()).append("&");
parameters = new StringBuilder(parameters.substring(0, parameters.length() - 1).substring(12));
params.put("target_id[]", parameters.toString());
List<Relationship> relationships = new ArrayList<>();
try {
HttpsConnection httpsConnection = new HttpsConnection(context);
@ -1895,22 +1893,28 @@ public class GNUAPI {
/**
* Parse json response an unique relationship
* @param resobj JSONObject
* @param resobjIni JSONObject
* @return Relationship
*/
private Relationship parseRelationshipResponse(JSONObject resobj){
private Relationship parseRelationshipResponse(JSONObject resobjIni){
Relationship relationship = new Relationship();
try {
JSONObject resobj = resobjIni.getJSONObject("relationship").getJSONObject("source");
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()));
try {
relationship.setMuting(Boolean.valueOf(resobj.get("muting").toString()));
}catch (Exception ignored){
relationship.setMuting(false);
}
try {
relationship.setMuting_notifications(!Boolean.valueOf(resobj.get("notifications_enabled").toString()));
}catch (Exception ignored){
relationship.setMuting_notifications(true);
relationship.setMuting_notifications(false);
}
relationship.setEndorsed(false);
relationship.setShowing_reblogs(true);

View File

@ -110,45 +110,53 @@ public class AccountSearchDevAdapter extends BaseAdapter implements OnPostAction
}else{
holder.account_dn.setCompoundDrawables( null, null, null, null);
}
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
assert account != null;
holder.account_dn.setText(Helper.shortnameToUnicode(account.getDisplay_name(), true));
holder.account_un.setText(String.format("@%s",account.getAcct()));
}else {
assert account != null;
holder.account_dn.setText(Helper.shortnameToUnicode(account.getDisplay_name(), true));
holder.account_un.setText(String.format("@%s",account.getAcct()));
}
Helper.changeDrawableColor(context, R.drawable.ic_lock_outline,R.color.mastodonC4);
//Profile picture
Glide.with(holder.account_pp.getContext())
.load(account.getAvatar())
.into(holder.account_pp);
if( account.isFollowing()){
holder.account_follow.hide();
}else{
holder.account_follow.show();
}
holder.account_follow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
holder.account_follow.setEnabled(false);
new PostActionAsyncTask(context, API.StatusAction.FOLLOW, account.getId(), AccountSearchDevAdapter.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
if( !account.getSocial().contains("OPENCOLLECTIVE")) {
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
assert account != null;
holder.account_dn.setText(Helper.shortnameToUnicode(account.getDisplay_name(), true));
holder.account_un.setText(String.format("@%s", account.getAcct()));
} else {
assert account != null;
holder.account_dn.setText(Helper.shortnameToUnicode(account.getDisplay_name(), true));
holder.account_un.setText(String.format("@%s", account.getAcct()));
}
});
holder.acccount_container.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(context, ShowAccountActivity.class);
Bundle b = new Bundle();
b.putParcelable("account", account);
intent.putExtras(b);
context.startActivity(intent);
}
});
}else{
holder.account_un.setText(account.getAcct());
holder.account_follow.hide();
}
Helper.changeDrawableColor(context, R.drawable.ic_lock_outline,R.color.mastodonC4);
//Profile picture
Glide.with(holder.account_pp.getContext())
.load(account.getAvatar())
.into(holder.account_pp);
if( !account.getSocial().contains("OPENCOLLECTIVE")) {
holder.account_follow.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
holder.account_follow.setEnabled(false);
new PostActionAsyncTask(context, API.StatusAction.FOLLOW, account.getId(), AccountSearchDevAdapter.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
});
holder.acccount_container.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
Intent intent = new Intent(context, ShowAccountActivity.class);
Bundle b = new Bundle();
b.putParcelable("account", account);
intent.putExtras(b);
context.startActivity(intent);
}
});
}
return convertView;
}

View File

@ -618,6 +618,29 @@ public class Helper {
}
}
/**
* Convert String date from Mastodon
* @param context Context
* @param date String
* @return Date
*/
public static Date opencollectivetStringToDate(Context context, String date) throws ParseException {
Locale userLocale;
if (date == null )
return null;
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.N) {
userLocale = context.getResources().getConfiguration().getLocales().get(0);
} else {
//noinspection deprecation
userLocale = context.getResources().getConfiguration().locale;
}
String STRING_DATE_FORMAT;
STRING_DATE_FORMAT = "yyyy-MM-dd HH:mm";
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(STRING_DATE_FORMAT, userLocale);
simpleDateFormat.setTimeZone(TimeZone.getTimeZone("gmt"));
simpleDateFormat.setLenient(true);
return simpleDateFormat.parse(date);
}
/**
* Convert a date in String -> format yyyy-MM-dd HH:mm:ss

View File

@ -0,0 +1,9 @@
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="24dp"
android:height="24dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FF000000"
android:pathData="M11.8,10.9c-2.27,-0.59 -3,-1.2 -3,-2.15 0,-1.09 1.01,-1.85 2.7,-1.85 1.78,0 2.44,0.85 2.5,2.1h2.21c-0.07,-1.72 -1.12,-3.3 -3.21,-3.81V3h-3v2.16c-1.94,0.42 -3.5,1.68 -3.5,3.61 0,2.31 1.91,3.46 4.7,4.13 2.5,0.6 3,1.48 3,2.41 0,0.69 -0.49,1.79 -2.7,1.79 -2.06,0 -2.87,-0.92 -2.98,-2.1h-2.2c0.12,2.19 1.76,3.42 3.68,3.83V21h3v-2.15c1.95,-0.37 3.5,-1.5 3.5,-3.55 0,-2.84 -2.43,-3.81 -4.7,-4.4z"/>
</vector>

View File

@ -0,0 +1,99 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2019 Thomas Schneider
This file is a part of Mastalab
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.
Mastalab 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 Mastalab; if not,
see <http://www.gnu.org/licenses>.
-->
<ScrollView xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:orientation="vertical"
>
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
>
<!-- About OpenCollective -->
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="5dp">
<TextView
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="2"
android:gravity="center"
android:text="@string/about_opencollective"
android:textSize="16sp"/>
<Button
android:id="@+id/about_opencollective"
style="@style/Base.Widget.AppCompat.Button.Colored"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_weight="3"
android:text="Open Collective"
tools:ignore="HardcodedText" />
</LinearLayout>
<!-- About developer -->
<TextView
android:padding="5dp"
android:layout_marginTop="10dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:textColor="?colorAccent"
android:gravity="center_vertical"
android:text="Backers"
android:textSize="16sp"
tools:ignore="HardcodedText" />
<fr.gouv.etalab.mastodon.helper.ExpandableHeightListView
android:id="@+id/lv_backers"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
android:divider="@null"/>
<!-- About UX/UI designer -->
<TextView
android:padding="5dp"
android:layout_marginTop="10dp"
android:id="@+id/about_thanks_ux"
android:layout_width="match_parent"
android:textColor="?colorAccent"
android:layout_height="wrap_content"
android:layout_gravity="center_vertical"
android:gravity="center_vertical"
android:textSize="16sp"
android:text="Sponsors"
tools:ignore="HardcodedText" />
<fr.gouv.etalab.mastodon.helper.ExpandableHeightListView
android:id="@+id/lv_sponsors"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:scrollbars="none"
android:divider="@null"/>
</LinearLayout>
</ScrollView>

View File

@ -159,6 +159,10 @@
android:id="@+id/nav_main_info">
<menu>
<group android:checkableBehavior="single">
<item
android:id="@+id/nav_opencollective"
android:icon="@drawable/ic_attach_money"
android:title="Open Collective" />
<item
android:id="@+id/nav_how_to"
android:icon="@drawable/ic_videocam"

View File

@ -869,6 +869,7 @@
<string name="action_logout_account">Logout account</string>
<string name="set_optimize_loading">Optimize loading time</string>
<string name="all">All</string>
<string name="about_opencollective">Support the app</string>
<!-- end languages -->