Issue #597 - Improve the way to manage tags

This commit is contained in:
stom79 2018-12-01 15:32:07 +01:00
parent 63a20aa22a
commit a8152e272d
12 changed files with 463 additions and 48 deletions

View File

@ -221,7 +221,10 @@
android:windowSoftInputMode="adjustPan"
android:theme="@style/Base.V7.Theme.AppCompat.Dialog"
android:excludeFromRecents="true"/>
<activity android:name=".activities.TagCacheActivity"
android:theme="@style/Base.V7.Theme.AppCompat.Dialog"
android:excludeFromRecents="true"
/>
<provider
android:name="android.support.v4.content.FileProvider"
android:authorities="fr.gouv.etalab.mastodon.fileProvider"

View File

@ -1482,6 +1482,8 @@ public abstract class BaseMainActivity extends BaseActivity
// Retrieves instance
new RetrieveInstanceAsyncTask(getApplicationContext(), BaseMainActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
// Retrieves filters
new ManageFiltersAsyncTask(getApplicationContext(), GET_ALL_FILTER, null,BaseMainActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
@ -1884,7 +1886,6 @@ public abstract class BaseMainActivity extends BaseActivity
//Proceeds to update of the authenticated account
if(Helper.isLoggedIn(getApplicationContext())) {
new UpdateAccountInfoByIDAsyncTask(getApplicationContext(), BaseMainActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
new ManageFiltersAsyncTask(getApplicationContext(), GET_ALL_FILTER, null,BaseMainActivity.this).executeOnExecutor(AsyncTask.THREAD_POOL_EXECUTOR);
}
if( lastHomeId != null && homeFragment != null){
homeFragment.retrieveMissingToots(lastHomeId);

View File

@ -0,0 +1,128 @@
/* 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.content.Context;
import android.content.SharedPreferences;
import android.database.sqlite.SQLiteDatabase;
import android.os.AsyncTask;
import android.os.Bundle;
import android.support.v4.content.ContextCompat;
import android.support.v7.widget.LinearLayoutManager;
import android.support.v7.widget.RecyclerView;
import android.util.Log;
import android.view.View;
import android.view.ViewGroup;
import android.view.Window;
import android.widget.EditText;
import android.widget.ImageButton;
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.drawers.TagsEditAdapter;
import fr.gouv.etalab.mastodon.helper.Helper;
import fr.gouv.etalab.mastodon.sqlite.Sqlite;
import fr.gouv.etalab.mastodon.sqlite.TagsCacheDAO;
/**
* Created by Thomas on 01/12/2018.
* Tag cache activity class
*/
public class TagCacheActivity extends BaseActivity {
private RecyclerView tag_list;
private List<String> tags;
private TagsEditAdapter tagsEditAdapter;
@Override
protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
requestWindowFeature(Window.FEATURE_NO_TITLE);
requestWindowFeature(Window.FEATURE_NO_TITLE);
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_NoActionBar);
getWindow().getDecorView().setBackgroundColor(ContextCompat.getColor(TagCacheActivity.this, R.color.mastodonC3__));
break;
case Helper.THEME_DARK:
setTheme(R.style.AppThemeDark_NoActionBar);
getWindow().getDecorView().setBackgroundColor(ContextCompat.getColor(TagCacheActivity.this, R.color.mastodonC1));
break;
case Helper.THEME_BLACK:
setTheme(R.style.AppThemeBlack_NoActionBar);
getWindow().getDecorView().setBackgroundColor(ContextCompat.getColor(TagCacheActivity.this, R.color.black_3));
break;
default:
setTheme(R.style.AppThemeDark_NoActionBar);
getWindow().getDecorView().setBackgroundColor(ContextCompat.getColor(TagCacheActivity.this, R.color.mastodonC1));
}
getWindow().setLayout(ViewGroup.LayoutParams.WRAP_CONTENT, ViewGroup.LayoutParams.WRAP_CONTENT);
tags = new ArrayList<>();
setContentView(R.layout.activity_tag_cache);
tag_list = findViewById(R.id.tag_list);
SQLiteDatabase db = Sqlite.getInstance(TagCacheActivity.this, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
EditText tag_add = findViewById(R.id.tag_add);
ImageButton save_tag = findViewById(R.id.save_tag);
save_tag.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if( tag_add.getText() != null && tag_add.getText().toString().trim().replaceAll("\\#","").length() > 0) {
String tagToInsert = tag_add.getText().toString().trim().replaceAll("\\#","");
boolean isPresent = new TagsCacheDAO(TagCacheActivity.this, db).isPresent(tagToInsert);
if( isPresent)
Toasty.warning(TagCacheActivity.this, getString(R.string.tags_already_stored), Toast.LENGTH_LONG).show();
else {
new TagsCacheDAO(TagCacheActivity.this, db).insert(tagToInsert);
int position = tags.size();
tags.add(tagToInsert);
Toasty.success(TagCacheActivity.this, getString(R.string.tags_stored), Toast.LENGTH_LONG).show();
tag_add.setText("");
tagsEditAdapter.notifyItemInserted(position);
}
}
}
});
setTitle(R.string.manage_tags);
AsyncTask.execute(new Runnable() {
@Override
public void run() {
List<String> tagsTemp = new TagsCacheDAO(TagCacheActivity.this, db).getAll();
if( tagsTemp != null)
tags = tagsTemp;
if( tags != null){
tagsEditAdapter = new TagsEditAdapter(TagCacheActivity.this, tags);
tag_list.setAdapter(tagsEditAdapter);
LinearLayoutManager mLayoutManager = new LinearLayoutManager(TagCacheActivity.this);
tag_list.setLayoutManager(mLayoutManager);
}
}
});
}
}

View File

@ -1126,6 +1126,11 @@ public class TootActivity extends BaseActivity implements OnRetrieveSearcAccount
case R.id.action_store:
storeToot(true, true);
return true;
case R.id.action_tags:
Intent intentTags = new Intent(TootActivity.this, TagCacheActivity.class);
intentTags.addFlags(Intent.FLAG_ACTIVITY_NO_ANIMATION);
startActivity(intentTags);
return true;
case R.id.action_restore:
try{
final List<StoredStatus> drafts = new StatusStoredDAO(TootActivity.this, db).getAllDrafts();

View File

@ -0,0 +1,120 @@
package fr.gouv.etalab.mastodon.drawers;
/* Copyright 2018 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>. */
import android.content.Context;
import android.database.sqlite.SQLiteDatabase;
import android.support.annotation.NonNull;
import android.support.v7.widget.RecyclerView;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import java.util.List;
import es.dmoral.toasty.Toasty;
import fr.gouv.etalab.mastodon.R;
import fr.gouv.etalab.mastodon.sqlite.Sqlite;
import fr.gouv.etalab.mastodon.sqlite.TagsCacheDAO;
/**
* Created by Thomas on 01/12/2018.
* Adapter for tags when editing
*/
public class TagsEditAdapter extends RecyclerView.Adapter {
private Context context;
private List<String> tags;
private LayoutInflater layoutInflater;
private TagsEditAdapter tagsEditAdapter;
public TagsEditAdapter(Context context, List<String> tags){
this.tags = tags;
this.layoutInflater = LayoutInflater.from(context);
this.context = context;
tagsEditAdapter = this;
}
@NonNull
@Override
public RecyclerView.ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int position) {
return new ViewHolder(layoutInflater.inflate(R.layout.drawer_tag_edit, parent, false));
}
@Override
public void onBindViewHolder(@NonNull RecyclerView.ViewHolder viewHolder, int i) {
final String[] tag = {tags.get(viewHolder.getAdapterPosition())};
ViewHolder holder = (ViewHolder) viewHolder;
holder.tag_name.setText(String.format("#%s", tag[0]));
SQLiteDatabase db = Sqlite.getInstance(context, Sqlite.DB_NAME, null, Sqlite.DB_VERSION).open();
holder.save_tag.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
if( holder.tag_name.getText() != null && holder.tag_name.getText().toString().trim().replaceAll("\\#","").length() > 0) {
String tagToInsert = holder.tag_name.getText().toString().trim().replaceAll("\\#","");
boolean isPresent = new TagsCacheDAO(context, db).isPresent(tagToInsert);
if( isPresent)
Toasty.warning(context, context.getString(R.string.tags_already_stored), Toast.LENGTH_LONG).show();
else {
new TagsCacheDAO(context, db).update(tag[0], tagToInsert);
Toasty.success(context, context.getString(R.string.tags_renamed), Toast.LENGTH_LONG).show();
}
}
}
});
holder.delete_tag.setOnClickListener(new View.OnClickListener() {
@Override
public void onClick(View v) {
new TagsCacheDAO(context, db).removeTag(tag[0]);
tags.remove(tag[0]);
tagsEditAdapter.notifyItemRemoved(viewHolder.getAdapterPosition());
Toasty.success(context, context.getString(R.string.tags_deleted), Toast.LENGTH_LONG).show();
}
});
}
@Override
public long getItemId(int position) {
return position;
}
@Override
public int getItemCount() {
return tags.size();
}
class ViewHolder extends RecyclerView.ViewHolder{
TextView tag_name;
ImageButton save_tag, delete_tag;
public ViewHolder(@NonNull View itemView) {
super(itemView);
tag_name = itemView.findViewById(R.id.tag_name);
delete_tag = itemView.findViewById(R.id.delete_tag);
save_tag = itemView.findViewById(R.id.save_tag);
}
}
}

View File

@ -261,12 +261,9 @@ public class LiveNotificationService extends Service implements NetworkStateRece
event = Helper.EventStreaming.NOTIFICATION;
notification = API.parseNotificationResponse(getApplicationContext(), new JSONObject(response.get("payload").toString()));
b.putParcelable("data", notification);
boolean liveNotifications = sharedpreferences.getBoolean(Helper.SET_LIVE_NOTIFICATIONS, true);
boolean canNotify = Helper.canNotify(getApplicationContext());
boolean notify = sharedpreferences.getBoolean(Helper.SET_NOTIFY, true);
String targeted_account = null;
Helper.NotifType notifType = Helper.NotifType.MENTION;
boolean activityRunning = PreferenceManager.getDefaultSharedPreferences(this).getBoolean("isMainActivityRunning", false);
@ -336,53 +333,51 @@ public class LiveNotificationService extends Service implements NetworkStateRece
}
long notif_id = Long.parseLong(account.getId());
final int notificationId = ((notif_id + 1) > 2147483647) ? (int) (2147483647 - notif_id - 1) : (int) (notif_id + 1);
if (notification.getAccount().getAvatar() != null) {
final String finalTitle = title;
Handler mainHandler = new Handler(Looper.getMainLooper());
Helper.NotifType finalNotifType = notifType;
Runnable myRunnable = new Runnable() {
@Override
public void run() {
if (finalTitle != null) {
Glide.with(getApplicationContext())
.asBitmap()
.load(notification.getAccount().getAvatar())
.listener(new RequestListener<Bitmap>() {
@Override
public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
return false;
}
final String finalTitle = title;
Handler mainHandler = new Handler(Looper.getMainLooper());
Helper.NotifType finalNotifType = notifType;
Runnable myRunnable = new Runnable() {
@Override
public void run() {
if (finalTitle != null) {
Glide.with(getApplicationContext())
.asBitmap()
.load(notification.getAccount().getAvatar())
.listener(new RequestListener<Bitmap>() {
@Override
public boolean onResourceReady(Bitmap resource, Object model, Target<Bitmap> target, DataSource dataSource, boolean isFirstResource) {
return false;
}
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) {
notify_user(getApplicationContext(), intent, notificationId, BitmapFactory.decodeResource(getResources(),
R.drawable.mastodonlogo), finalNotifType, finalTitle, "@" + account.getAcct() + "@" + account.getInstance());
String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null);
if (lastNotif == null || Long.parseLong(notification.getId()) > Long.parseLong(lastNotif)) {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notification.getId());
editor.apply();
}
return false;
@Override
public boolean onLoadFailed(@Nullable GlideException e, Object model, Target target, boolean isFirstResource) {
notify_user(getApplicationContext(), intent, notificationId, BitmapFactory.decodeResource(getResources(),
R.drawable.mastodonlogo), finalNotifType, finalTitle, "@" + account.getAcct() + "@" + account.getInstance());
String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null);
if (lastNotif == null || Long.parseLong(notification.getId()) > Long.parseLong(lastNotif)) {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notification.getId());
editor.apply();
}
})
.into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
notify_user(getApplicationContext(), intent, notificationId, resource, finalNotifType, finalTitle, "@" + account.getAcct() + "@" + account.getInstance());
String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null);
if (lastNotif == null || Long.parseLong(notification.getId()) > Long.parseLong(lastNotif)) {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notification.getId());
editor.apply();
}
return false;
}
})
.into(new SimpleTarget<Bitmap>() {
@Override
public void onResourceReady(@NonNull Bitmap resource, Transition<? super Bitmap> transition) {
notify_user(getApplicationContext(), intent, notificationId, resource, finalNotifType, finalTitle, "@" + account.getAcct() + "@" + account.getInstance());
String lastNotif = sharedpreferences.getString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), null);
if (lastNotif == null || Long.parseLong(notification.getId()) > Long.parseLong(lastNotif)) {
SharedPreferences.Editor editor = sharedpreferences.edit();
editor.putString(Helper.LAST_NOTIFICATION_MAX_ID + account.getId() + account.getInstance(), notification.getId());
editor.apply();
}
});
}
}
});
}
};
mainHandler.post(myRunnable);
}
}
};
mainHandler.post(myRunnable);
}
}
break;

View File

@ -52,6 +52,19 @@ public class TagsCacheDAO {
}catch (Exception ignored) {}
}
/**
* update a tag in database
* @param oldTag String
* @param newTag String
*/
public void update(String oldTag, String newTag) {
ContentValues values = new ContentValues();
values.put(Sqlite.COL_TAGS, newTag);
try{
db.update(Sqlite.TABLE_CACHE_TAGS, values, Sqlite.COL_TAGS + " = ?",new String[]{ oldTag});
}catch (Exception ignored) {}
}
/***
* Remove all tags
*/
@ -59,6 +72,11 @@ public class TagsCacheDAO {
db.delete(Sqlite.TABLE_CACHE_TAGS, null, null);
}
public void removeTag(String tag) {
db.delete(Sqlite.TABLE_CACHE_TAGS, Sqlite.COL_TAGS + " = ?", new String[]{tag});
}
/**
* Returns all tags in db
* @return string tags List<String>
@ -72,6 +90,18 @@ public class TagsCacheDAO {
}
}
/**
* Returns tags starting by "search"
* @return boolean present
*/
public boolean isPresent(String search){
Cursor c = db.query(Sqlite.TABLE_CACHE_TAGS, null, Sqlite.COL_TAGS + " = \"" + search + "\"", null, null, null, null, null);
boolean isPresent = (c!= null && c.getCount() > 0);
assert c != null;
c.close();
return isPresent;
}
/**
* Returns tags starting by "search"
* @return tags List<String>

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="M17,3L5,3c-1.11,0 -2,0.9 -2,2v14c0,1.1 0.89,2 2,2h14c1.1,0 2,-0.9 2,-2L21,7l-4,-4zM12,19c-1.66,0 -3,-1.34 -3,-3s1.34,-3 3,-3 3,1.34 3,3 -1.34,3 -3,3zM15,9L5,9L5,5h10v4z"/>
</vector>

View File

@ -0,0 +1,61 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2018 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
android:layout_width="match_parent"
android:layout_height="wrap_content"
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
tools:context="fr.gouv.etalab.mastodon.activities.InstanceHealthActivity">
<LinearLayout
android:layout_margin="@dimen/fab_margin"
android:id="@+id/container"
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/account_container"
android:orientation="horizontal">
<EditText
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:padding="20dp"
android:id="@+id/tag_add"
android:layout_gravity="center_vertical"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:inputType="text" />
<ImageButton
android:layout_margin="5dp"
android:id="@+id/save_tag"
android:layout_gravity="center"
android:gravity="center"
android:src="@drawable/ic_add"
style="@style/Base.Widget.AppCompat.Button.Colored"
android:text="@string/set_save_changes"
android:layout_width="40dp"
android:layout_height="40dp" />
</LinearLayout>
<android.support.v7.widget.RecyclerView
android:id="@+id/tag_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"/>
</LinearLayout>
</ScrollView>

View File

@ -0,0 +1,53 @@
<?xml version="1.0" encoding="utf-8"?>
<!--
Copyright 2018 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>.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:id="@+id/account_container"
android:orientation="horizontal">
<EditText
android:layout_marginStart="10dp"
android:layout_marginLeft="10dp"
android:padding="20dp"
android:id="@+id/tag_name"
android:layout_gravity="center_vertical"
android:layout_width="0dp"
android:layout_weight="1"
android:layout_height="wrap_content"
android:inputType="text" />
<ImageButton
android:layout_margin="5dp"
android:id="@+id/save_tag"
android:layout_gravity="center"
android:gravity="center"
android:src="@drawable/ic_save_white"
style="@style/Base.Widget.AppCompat.Button.Colored"
android:text="@string/set_save_changes"
android:layout_width="40dp"
android:layout_height="40dp" />
<ImageButton
android:id="@+id/delete_tag"
android:layout_margin="5dp"
android:layout_gravity="center"
android:gravity="center"
android:src="@drawable/ic_delete"
style="@style/Base.Widget.AppCompat.Button.Colored"
android:text="@string/set_save_changes"
android:layout_width="40dp"
android:layout_height="40dp" />
</LinearLayout>

View File

@ -41,4 +41,9 @@
android:title="@string/schedule"
android:icon="@drawable/ic_schedule"
app:showAsAction="never" />
<item
android:id="@+id/action_tags"
android:title="@string/tags"
android:icon="@drawable/ic_edit"
app:showAsAction="never" />
</menu>

View File

@ -680,6 +680,11 @@
<string name="set_truncate_toot">Truncate toots over \'x\' lines. Zero means disabled.</string>
<string name="display_toot_truncate">Display more</string>
<string name="hide_toot_truncate">Display less</string>
<string name="manage_tags">Manage tags</string>
<string name="tags_already_stored">The tag already exists!</string>
<string name="tags_stored">The tag has been stored!</string>
<string name="tags_renamed">The tag has been changed!</string>
<string name="tags_deleted">The tag has been deleted!</string>
<string-array name="filter_expire">
<item>Never</item>