added hashtag remove button, database update, translation update, bug fix

This commit is contained in:
nuclearfog 2023-09-27 21:42:01 +02:00
parent 774e3fa056
commit fb90a7d99e
No known key found for this signature in database
GPG Key ID: 03488A185C476379
22 changed files with 349 additions and 159 deletions

View File

@ -330,10 +330,10 @@ public interface Connection {
/** /**
* *
* @param name name of the hashtag * @param id of the featured hashtag
* @return updated hashtag information * @return updated hashtag information
*/ */
Hashtag unfeatureHashtag(String name) throws ConnectionException; Hashtag unfeatureHashtag(long id) throws ConnectionException;
/** /**
* show current user's home timeline * show current user's home timeline

View File

@ -541,13 +541,9 @@ public class Mastodon implements Connection {
@Override @Override
public Hashtag unfeatureHashtag(String name) throws ConnectionException { public Hashtag unfeatureHashtag(long id) throws ConnectionException {
try { try {
if (name.startsWith("#")) return createTag(delete(ENDPOINT_HASHTAG_FEATURE + "/" + id, new ArrayList<>()));
name = name.substring(1);
List<String> params = new ArrayList<>();
params.add("name=" + StringUtils.encode(name));
return createTag(delete(ENDPOINT_HASHTAG_FEATURE, params));
} catch (IOException e) { } catch (IOException e) {
throw new MastodonException(e); throw new MastodonException(e);
} }
@ -1466,7 +1462,9 @@ public class Mastodon implements Connection {
long[] cursors = getCursors(response); long[] cursors = getCursors(response);
Trends result = new Trends(cursors[0], cursors[1]); Trends result = new Trends(cursors[0], cursors[1]);
for (int i = 0; i < jsonArray.length(); i++) { for (int i = 0; i < jsonArray.length(); i++) {
result.add(new MastodonHashtag(jsonArray.getJSONObject(i))); MastodonHashtag item = new MastodonHashtag(jsonArray.getJSONObject(i));
item.setRank(i + 1);
result.add(item);
} }
Collections.sort(result); Collections.sort(result);
return result; return result;

View File

@ -5,8 +5,8 @@ import androidx.annotation.Nullable;
import org.json.JSONArray; import org.json.JSONArray;
import org.json.JSONObject; import org.json.JSONObject;
import org.nuclearfog.twidda.model.Location;
import org.nuclearfog.twidda.model.Hashtag; import org.nuclearfog.twidda.model.Hashtag;
import org.nuclearfog.twidda.model.Location;
/** /**
* Hashtag implementation used by Mastodon API * Hashtag implementation used by Mastodon API
@ -20,12 +20,15 @@ public class MastodonHashtag implements Hashtag {
private int popularity; private int popularity;
private String name; private String name;
private boolean following; private boolean following;
private long id;
private int rank;
/** /**
* @param json trend json object * @param json trend json object
*/ */
public MastodonHashtag(JSONObject json) { public MastodonHashtag(JSONObject json) {
JSONArray history = json.optJSONArray("history"); JSONArray history = json.optJSONArray("history");
String idStr = json.optString("id", "0");
name = '#' + json.optString("name", ""); name = '#' + json.optString("name", "");
following = json.optBoolean("following", false); following = json.optBoolean("following", false);
if (history != null && history.length() > 0) { if (history != null && history.length() > 0) {
@ -36,6 +39,17 @@ public class MastodonHashtag implements Hashtag {
} else { } else {
popularity = json.optInt("statuses_count", 0); popularity = json.optInt("statuses_count", 0);
} }
try {
id = Long.parseLong(idStr);
} catch (NumberFormatException exception) {
// proceed without ID
}
}
@Override
public long getId() {
return id;
} }
@ -53,7 +67,7 @@ public class MastodonHashtag implements Hashtag {
@Override @Override
public int getRank() { public int getRank() {
return -1; return rank;
} }
@ -68,6 +82,13 @@ public class MastodonHashtag implements Hashtag {
return following; return following;
} }
/**
*
*/
public void setRank(int rank) {
this.rank = rank;
}
@Override @Override
public boolean equals(@Nullable Object obj) { public boolean equals(@Nullable Object obj) {

View File

@ -15,7 +15,7 @@ import org.nuclearfog.twidda.model.Hashtag;
* *
* @author nuclearfog * @author nuclearfog
*/ */
public class HashtagAction extends AsyncExecutor<HashtagAction.HashtagParam, HashtagAction.HashtagResult> { public class HashtagAction extends AsyncExecutor<HashtagAction.HashtagActionParam, HashtagAction.HashtagActionResult> {
private Connection connection; private Connection connection;
@ -28,41 +28,41 @@ public class HashtagAction extends AsyncExecutor<HashtagAction.HashtagParam, Has
@Override @Override
protected HashtagResult doInBackground(@NonNull HashtagParam param) { protected HashtagActionResult doInBackground(@NonNull HashtagActionParam param) {
try { try {
switch (param.mode) { switch (param.mode) {
case HashtagParam.LOAD: case HashtagActionParam.LOAD:
Hashtag result = connection.showHashtag(param.name); Hashtag result = connection.showHashtag(param.name);
return new HashtagResult(HashtagResult.LOAD, result, null); return new HashtagActionResult(HashtagActionResult.LOAD, result, null);
case HashtagParam.FOLLOW: case HashtagActionParam.FOLLOW:
result = connection.followHashtag(param.name); result = connection.followHashtag(param.name);
return new HashtagResult(HashtagResult.FOLLOW, result, null); return new HashtagActionResult(HashtagActionResult.FOLLOW, result, null);
case HashtagParam.UNFOLLOW: case HashtagActionParam.UNFOLLOW:
result = connection.unfollowHashtag(param.name); result = connection.unfollowHashtag(param.name);
return new HashtagResult(HashtagResult.UNFOLLOW, result, null); return new HashtagActionResult(HashtagActionResult.UNFOLLOW, result, null);
case HashtagParam.FEATURE: case HashtagActionParam.FEATURE:
result = connection.featureHashtag(param.name); result = connection.featureHashtag(param.name);
return new HashtagResult(HashtagResult.FEATURE, result, null); return new HashtagActionResult(HashtagActionResult.FEATURE, result, null);
case HashtagParam.UNFEATURE: case HashtagActionParam.UNFEATURE:
result = connection.unfeatureHashtag(param.name); result = connection.unfeatureHashtag(param.id);
return new HashtagResult(HashtagResult.UNFEATURE, result, null); return new HashtagActionResult(HashtagActionResult.UNFEATURE, result, null);
default: default:
return null; return null;
} }
} catch (ConnectionException exception) { } catch (ConnectionException exception) {
return new HashtagResult(HashtagResult.ERROR, null, exception); return new HashtagActionResult(HashtagActionResult.ERROR, null, exception);
} }
} }
/** /**
* *
*/ */
public static class HashtagParam { public static class HashtagActionParam {
public static final int LOAD = 1; public static final int LOAD = 1;
public static final int FOLLOW = 2; public static final int FOLLOW = 2;
@ -72,17 +72,25 @@ public class HashtagAction extends AsyncExecutor<HashtagAction.HashtagParam, Has
final String name; final String name;
final int mode; final int mode;
final long id;
public HashtagParam(int mode, String name) { public HashtagActionParam(int mode, String name) {
this.name = name; this.name = name;
this.mode = mode; this.mode = mode;
id = 0L;
}
public HashtagActionParam(int mode, String name, long id) {
this.name = name;
this.mode = mode;
this.id = id;
} }
} }
/** /**
* *
*/ */
public static class HashtagResult { public static class HashtagActionResult {
public static final int ERROR = -1; public static final int ERROR = -1;
public static final int LOAD = 10; public static final int LOAD = 10;
@ -97,7 +105,7 @@ public class HashtagAction extends AsyncExecutor<HashtagAction.HashtagParam, Has
public final Hashtag hashtag; public final Hashtag hashtag;
public final int mode; public final int mode;
HashtagResult(int mode, @Nullable Hashtag hashtag, @Nullable ConnectionException exception) { HashtagActionResult(int mode, @Nullable Hashtag hashtag, @Nullable ConnectionException exception) {
this.exception = exception; this.exception = exception;
this.hashtag = hashtag; this.hashtag = hashtag;
this.mode = mode; this.mode = mode;

View File

@ -18,7 +18,7 @@ import org.nuclearfog.twidda.ui.fragments.HashtagFragment;
* @author nuclearfog * @author nuclearfog
* @see HashtagFragment * @see HashtagFragment
*/ */
public class TrendLoader extends AsyncExecutor<TrendLoader.TrendParameter, TrendLoader.TrendResult> { public class HashtagLoader extends AsyncExecutor<HashtagLoader.HashtagLoaderParam, HashtagLoader.HashtagLoaderResult> {
private Connection connection; private Connection connection;
private AppDatabase db; private AppDatabase db;
@ -26,52 +26,52 @@ public class TrendLoader extends AsyncExecutor<TrendLoader.TrendParameter, Trend
/** /**
* *
*/ */
public TrendLoader(Context context) { public HashtagLoader(Context context) {
connection = ConnectionManager.getDefaultConnection(context); connection = ConnectionManager.getDefaultConnection(context);
db = new AppDatabase(context); db = new AppDatabase(context);
} }
@Override @Override
protected TrendResult doInBackground(@NonNull TrendParameter param) { protected HashtagLoaderResult doInBackground(@NonNull HashtagLoaderParam param) {
try { try {
switch (param.mode) { switch (param.mode) {
case TrendParameter.POPULAR_OFFLINE: case HashtagLoaderParam.POPULAR_OFFLINE:
Trends trends = db.getTrends(); Trends trends = db.getTrends();
if (!trends.isEmpty()) { if (!trends.isEmpty()) {
return new TrendResult(TrendResult.POPULAR, trends, param.index, null); return new HashtagLoaderResult(HashtagLoaderResult.POPULAR, trends, param.index, null);
} }
// fall through // fall through
case TrendParameter.POPULAR_ONLINE: case HashtagLoaderParam.POPULAR_ONLINE:
trends = connection.getTrends(); trends = connection.getTrends();
db.saveTrends(trends); db.saveTrends(trends);
return new TrendResult(TrendResult.POPULAR, trends, param.index, null); return new HashtagLoaderResult(HashtagLoaderResult.POPULAR, trends, param.index, null);
case TrendParameter.SEARCH: case HashtagLoaderParam.SEARCH:
trends = connection.searchHashtags(param.trend); trends = connection.searchHashtags(param.trend);
return new TrendResult(TrendResult.SEARCH, trends, param.index, null); return new HashtagLoaderResult(HashtagLoaderResult.SEARCH, trends, param.index, null);
case TrendParameter.FOLLOWING: case HashtagLoaderParam.FOLLOWING:
trends = connection.showHashtagFollowing(param.cursor); trends = connection.showHashtagFollowing(param.cursor);
return new TrendResult(TrendResult.FOLLOWING, trends, param.index, null); return new HashtagLoaderResult(HashtagLoaderResult.FOLLOWING, trends, param.index, null);
case TrendParameter.FEATURING: case HashtagLoaderParam.FEATURING:
trends = connection.showHashtagFeaturing(); trends = connection.showHashtagFeaturing();
return new TrendResult(TrendResult.FEATURING, trends, param.index, null); return new HashtagLoaderResult(HashtagLoaderResult.FEATURING, trends, param.index, null);
default: default:
return null; return null;
} }
} catch (ConnectionException exception) { } catch (ConnectionException exception) {
return new TrendResult(TrendResult.ERROR, null, param.index, exception); return new HashtagLoaderResult(HashtagLoaderResult.ERROR, null, param.index, exception);
} }
} }
/** /**
* *
*/ */
public static class TrendParameter { public static class HashtagLoaderParam {
public static final int POPULAR_OFFLINE = 1; public static final int POPULAR_OFFLINE = 1;
public static final int POPULAR_ONLINE = 2; public static final int POPULAR_ONLINE = 2;
@ -86,7 +86,7 @@ public class TrendLoader extends AsyncExecutor<TrendLoader.TrendParameter, Trend
final int index; final int index;
final long cursor; final long cursor;
public TrendParameter(int mode, int index, String trend, long cursor) { public HashtagLoaderParam(int mode, int index, String trend, long cursor) {
this.mode = mode; this.mode = mode;
this.trend = trend; this.trend = trend;
this.index = index; this.index = index;
@ -97,7 +97,7 @@ public class TrendLoader extends AsyncExecutor<TrendLoader.TrendParameter, Trend
/** /**
* *
*/ */
public static class TrendResult { public static class HashtagLoaderResult {
public static final int ERROR = -1; public static final int ERROR = -1;
public static final int POPULAR = 20; public static final int POPULAR = 20;
@ -112,7 +112,7 @@ public class TrendLoader extends AsyncExecutor<TrendLoader.TrendParameter, Trend
@Nullable @Nullable
public final ConnectionException exception; public final ConnectionException exception;
TrendResult(int mode, @Nullable Trends trends, int index, @Nullable ConnectionException exception) { HashtagLoaderResult(int mode, @Nullable Trends trends, int index, @Nullable ConnectionException exception) {
this.trends = trends; this.trends = trends;
this.exception = exception; this.exception = exception;
this.index = index; this.index = index;

View File

@ -304,7 +304,7 @@ public class AppDatabase {
/** /**
* select trends from trend table with given world ID * select trends from trend table with given world ID
*/ */
private static final String TREND_SELECT = HashtagTable.ID + "=?"; private static final String TREND_SELECT = HashtagTable.LOCATION + "=?";
/** /**
* select status from status table matching ID * select status from status table matching ID
@ -537,10 +537,11 @@ public class AppDatabase {
db.delete(HashtagTable.NAME, TREND_SELECT, args); db.delete(HashtagTable.NAME, TREND_SELECT, args);
for (Hashtag hashtag : hashtags) { for (Hashtag hashtag : hashtags) {
ContentValues column = new ContentValues(4); ContentValues column = new ContentValues(4);
column.put(HashtagTable.ID, hashtag.getLocationId()); column.put(HashtagTable.LOCATION, hashtag.getLocationId());
column.put(HashtagTable.VOL, hashtag.getPopularity()); column.put(HashtagTable.VOL, hashtag.getPopularity());
column.put(HashtagTable.TREND, hashtag.getName()); column.put(HashtagTable.TREND, hashtag.getName());
column.put(HashtagTable.INDEX, hashtag.getRank()); column.put(HashtagTable.INDEX, hashtag.getRank());
column.put(HashtagTable.ID, hashtag.getId());
db.insert(HashtagTable.NAME, null, column); db.insert(HashtagTable.NAME, null, column);
} }
adapter.commit(); adapter.commit();

View File

@ -20,7 +20,7 @@ public class DatabaseAdapter {
/** /**
* database version * database version
*/ */
private static final int DB_VERSION = 20; private static final int DB_VERSION = 21;
/** /**
* database file name * database file name
@ -78,6 +78,7 @@ public class DatabaseAdapter {
private static final String TABLE_TRENDS = "CREATE TABLE IF NOT EXISTS " private static final String TABLE_TRENDS = "CREATE TABLE IF NOT EXISTS "
+ HashtagTable.NAME + "(" + HashtagTable.NAME + "("
+ HashtagTable.ID + " INTEGER," + HashtagTable.ID + " INTEGER,"
+ HashtagTable.LOCATION + " INTEGER,"
+ HashtagTable.INDEX + " INTEGER," + HashtagTable.INDEX + " INTEGER,"
+ HashtagTable.VOL + " INTEGER," + HashtagTable.VOL + " INTEGER,"
+ HashtagTable.TREND + " TEXT);"; + HashtagTable.TREND + " TEXT);";
@ -306,6 +307,11 @@ public class DatabaseAdapter {
*/ */
private static final String UPDATE_MEDIA_ADD_BLUR_HASH = "ALTER TABLE " + MediaTable.NAME + " ADD " + MediaTable.BLUR + " TEXT;"; private static final String UPDATE_MEDIA_ADD_BLUR_HASH = "ALTER TABLE " + MediaTable.NAME + " ADD " + MediaTable.BLUR + " TEXT;";
/**
* add mediatable description
*/
private static final String UPDATE_HASHTAG_ADD_ID = "ALTER TABLE " + HashtagTable.NAME + " ADD " + HashtagTable.ID + " INTEGER;";
/** /**
* singleton instance * singleton instance
*/ */
@ -407,9 +413,13 @@ public class DatabaseAdapter {
db.execSQL(UPDATE_MEDIA_ADD_BLUR_HASH); db.execSQL(UPDATE_MEDIA_ADD_BLUR_HASH);
db.setVersion(19); db.setVersion(19);
} }
if (db.getVersion() < DB_VERSION) { if (db.getVersion() < 20) {
db.delete(EmojiTable.NAME, null, null); db.delete(EmojiTable.NAME, null, null);
db.execSQL(TABLE_EMOJI); db.execSQL(TABLE_EMOJI);
db.setVersion(20);
}
if (db.getVersion() < DB_VERSION) {
db.execSQL(UPDATE_HASHTAG_ADD_ID);
db.setVersion(DB_VERSION); db.setVersion(DB_VERSION);
} }
} }
@ -701,10 +711,15 @@ public class DatabaseAdapter {
*/ */
String NAME = "trend"; String NAME = "trend";
/**
* id of the hashtag
*/
String ID = "id";
/** /**
* Location ID * Location ID
*/ */
String ID = "woeID"; String LOCATION = "woeID";
/** /**
* rank of the hashtag * rank of the hashtag

View File

@ -20,12 +20,13 @@ public class DatabaseHashtag implements Hashtag, HashtagTable {
/** /**
* SQLite columns * SQLite columns
*/ */
public static final String[] COLUMNS = {TREND, VOL, INDEX, ID}; public static final String[] COLUMNS = {TREND, VOL, INDEX, LOCATION, ID};
private String name = ""; private String name = "";
private int popularity; private int popularity;
private int rank; private int rank;
private long id; private long id;
private long locationId;
/** /**
* @param cursor database cursor using this {@link #COLUMNS} projection * @param cursor database cursor using this {@link #COLUMNS} projection
@ -34,13 +35,20 @@ public class DatabaseHashtag implements Hashtag, HashtagTable {
String name = cursor.getString(0); String name = cursor.getString(0);
popularity = cursor.getInt(1); popularity = cursor.getInt(1);
rank = cursor.getInt(2); rank = cursor.getInt(2);
id = cursor.getLong(3); locationId = cursor.getLong(3);
id = cursor.getLong(4);
if (name != null) { if (name != null) {
this.name = name; this.name = name;
} }
} }
@Override
public long getId() {
return id;
}
@Override @Override
public String getName() { public String getName() {
return name; return name;
@ -49,7 +57,7 @@ public class DatabaseHashtag implements Hashtag, HashtagTable {
@Override @Override
public long getLocationId() { public long getLocationId() {
return id; return locationId;
} }
@ -83,6 +91,6 @@ public class DatabaseHashtag implements Hashtag, HashtagTable {
@NonNull @NonNull
@Override @Override
public String toString() { public String toString() {
return "name=\"" + getName() + "\""; return "name=\"" + getName() + "\" rank=" + rank;
} }
} }

View File

@ -10,27 +10,32 @@ import java.io.Serializable;
public interface Hashtag extends Serializable, Comparable<Hashtag> { public interface Hashtag extends Serializable, Comparable<Hashtag> {
/** /**
* @return trend name * @return hashtag name
*/ */
String getName(); String getName();
/** /**
* @return ID of the trend location * @return trend ID if any
*/
long getId();
/**
* @return hashtag ID
*/ */
long getLocationId(); long getLocationId();
/** /**
* @return rank of the trend * @return rank of the hashtag if any
*/ */
int getRank(); int getRank();
/** /**
* @return popularity of the trend * @return popularity of the hashtag
*/ */
int getPopularity(); int getPopularity();
/** /**
* @return true if current user follows trend (hashtag) * @return true if current user follows hashtag
*/ */
boolean following(); boolean following();

View File

@ -17,8 +17,8 @@ import androidx.viewpager2.widget.ViewPager2;
import org.nuclearfog.twidda.R; import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback; import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback;
import org.nuclearfog.twidda.backend.async.HashtagAction; import org.nuclearfog.twidda.backend.async.HashtagAction;
import org.nuclearfog.twidda.backend.async.HashtagAction.HashtagParam; import org.nuclearfog.twidda.backend.async.HashtagAction.HashtagActionParam;
import org.nuclearfog.twidda.backend.async.HashtagAction.HashtagResult; import org.nuclearfog.twidda.backend.async.HashtagAction.HashtagActionResult;
import org.nuclearfog.twidda.backend.utils.AppStyles; import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.config.GlobalSettings; import org.nuclearfog.twidda.config.GlobalSettings;
import org.nuclearfog.twidda.ui.adapter.viewpager.HashtagAdapter; import org.nuclearfog.twidda.ui.adapter.viewpager.HashtagAdapter;
@ -30,7 +30,7 @@ import org.nuclearfog.twidda.ui.views.TabSelector.OnTabSelectedListener;
* *
* @author nuclearfog * @author nuclearfog
*/ */
public class HashtagActivity extends AppCompatActivity implements SearchView.OnQueryTextListener, OnTabSelectedListener, AsyncCallback<HashtagResult> { public class HashtagActivity extends AppCompatActivity implements SearchView.OnQueryTextListener, OnTabSelectedListener, AsyncCallback<HashtagActionResult> {
private GlobalSettings settings; private GlobalSettings settings;
private HashtagAction hashtagAction; private HashtagAction hashtagAction;
@ -90,11 +90,11 @@ public class HashtagActivity extends AppCompatActivity implements SearchView.OnQ
public boolean onQueryTextSubmit(String query) { public boolean onQueryTextSubmit(String query) {
if (hashtagAction.isIdle()) { if (hashtagAction.isIdle()) {
if (viewPager.getCurrentItem() == 0) { if (viewPager.getCurrentItem() == 0) {
HashtagParam param = new HashtagParam(HashtagParam.FOLLOW, query); HashtagActionParam param = new HashtagActionParam(HashtagActionParam.FOLLOW, query);
hashtagAction.execute(param, this); hashtagAction.execute(param, this);
return true; return true;
} else if (viewPager.getCurrentItem() == 1) { } else if (viewPager.getCurrentItem() == 1) {
HashtagParam param = new HashtagParam(HashtagParam.FEATURE, query); HashtagActionParam param = new HashtagActionParam(HashtagActionParam.FEATURE, query);
hashtagAction.execute(param, this); hashtagAction.execute(param, this);
return true; return true;
} }
@ -110,21 +110,21 @@ public class HashtagActivity extends AppCompatActivity implements SearchView.OnQ
@Override @Override
public void onResult(@NonNull HashtagResult result) { public void onResult(@NonNull HashtagActionResult result) {
switch (result.mode) { switch (result.mode) {
case HashtagResult.FEATURE: case HashtagActionResult.FEATURE:
Toast.makeText(getApplicationContext(), R.string.info_hashtag_featured, Toast.LENGTH_SHORT).show(); Toast.makeText(getApplicationContext(), R.string.info_hashtag_featured, Toast.LENGTH_SHORT).show();
adapter.notifySettingsChanged(); adapter.notifySettingsChanged();
invalidateOptionsMenu(); invalidateOptionsMenu();
break; break;
case HashtagResult.FOLLOW: case HashtagActionResult.FOLLOW:
Toast.makeText(getApplicationContext(), R.string.info_hashtag_followed, Toast.LENGTH_SHORT).show(); Toast.makeText(getApplicationContext(), R.string.info_hashtag_followed, Toast.LENGTH_SHORT).show();
adapter.notifySettingsChanged(); adapter.notifySettingsChanged();
invalidateOptionsMenu(); invalidateOptionsMenu();
break; break;
case HashtagResult.ERROR: case HashtagActionResult.ERROR:
break; break;
} }
} }

View File

@ -22,8 +22,8 @@ import androidx.viewpager2.widget.ViewPager2;
import org.nuclearfog.twidda.R; import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback; import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback;
import org.nuclearfog.twidda.backend.async.HashtagAction; import org.nuclearfog.twidda.backend.async.HashtagAction;
import org.nuclearfog.twidda.backend.async.HashtagAction.HashtagParam; import org.nuclearfog.twidda.backend.async.HashtagAction.HashtagActionParam;
import org.nuclearfog.twidda.backend.async.HashtagAction.HashtagResult; import org.nuclearfog.twidda.backend.async.HashtagAction.HashtagActionResult;
import org.nuclearfog.twidda.backend.utils.AppStyles; import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.backend.utils.ErrorUtils; import org.nuclearfog.twidda.backend.utils.ErrorUtils;
import org.nuclearfog.twidda.config.GlobalSettings; import org.nuclearfog.twidda.config.GlobalSettings;
@ -39,7 +39,7 @@ import java.io.Serializable;
* *
* @author nuclearfog * @author nuclearfog
*/ */
public class SearchActivity extends AppCompatActivity implements OnClickListener, OnTabSelectedListener, OnQueryTextListener, AsyncCallback<HashtagResult> { public class SearchActivity extends AppCompatActivity implements OnClickListener, OnTabSelectedListener, OnQueryTextListener, AsyncCallback<HashtagActionResult> {
/** /**
* Key for the search query, required * Key for the search query, required
@ -96,7 +96,7 @@ public class SearchActivity extends AppCompatActivity implements OnClickListener
} else if (query != null) { } else if (query != null) {
search = query; search = query;
if (search.startsWith("#") && search.matches("\\S+")) { if (search.startsWith("#") && search.matches("\\S+")) {
HashtagParam param = new HashtagParam(HashtagParam.LOAD, search); HashtagActionParam param = new HashtagActionParam(HashtagActionParam.LOAD, search);
hashtagAction.execute(param, this); hashtagAction.execute(param, this);
} }
} }
@ -207,11 +207,11 @@ public class SearchActivity extends AppCompatActivity implements OnClickListener
// follow/unfollow hashtag // follow/unfollow hashtag
else if (item.getItemId() == R.id.search_hashtag) { else if (item.getItemId() == R.id.search_hashtag) {
if (hashtag != null && hashtagAction.isIdle()) { if (hashtag != null && hashtagAction.isIdle()) {
HashtagParam param; HashtagActionParam param;
if (hashtag.following()) if (hashtag.following())
param = new HashtagParam(HashtagParam.UNFOLLOW, hashtag.getName()); param = new HashtagActionParam(HashtagActionParam.UNFOLLOW, hashtag.getName());
else else
param = new HashtagParam(HashtagParam.FOLLOW, hashtag.getName()); param = new HashtagActionParam(HashtagActionParam.FOLLOW, hashtag.getName());
hashtagAction.execute(param, this); hashtagAction.execute(param, this);
} }
} }
@ -258,21 +258,21 @@ public class SearchActivity extends AppCompatActivity implements OnClickListener
@Override @Override
public void onResult(@NonNull HashtagResult result) { public void onResult(@NonNull HashtagActionResult result) {
if (result.hashtag != null) { if (result.hashtag != null) {
this.hashtag = result.hashtag; this.hashtag = result.hashtag;
invalidateMenu(); invalidateMenu();
} }
switch (result.mode) { switch (result.mode) {
case HashtagResult.FOLLOW: case HashtagActionResult.FOLLOW:
Toast.makeText(getApplicationContext(), R.string.info_hashtag_followed, Toast.LENGTH_SHORT).show(); Toast.makeText(getApplicationContext(), R.string.info_hashtag_followed, Toast.LENGTH_SHORT).show();
break; break;
case HashtagResult.UNFOLLOW: case HashtagActionResult.UNFOLLOW:
Toast.makeText(getApplicationContext(), R.string.info_hashtag_unfollowed, Toast.LENGTH_SHORT).show(); Toast.makeText(getApplicationContext(), R.string.info_hashtag_unfollowed, Toast.LENGTH_SHORT).show();
break; break;
case HashtagResult.ERROR: case HashtagActionResult.ERROR:
ErrorUtils.showErrorMessage(this, result.exception); ErrorUtils.showErrorMessage(this, result.exception);
break; break;
} }

View File

@ -10,7 +10,7 @@ import org.nuclearfog.twidda.model.Hashtag;
import org.nuclearfog.twidda.model.lists.Trends; import org.nuclearfog.twidda.model.lists.Trends;
import org.nuclearfog.twidda.ui.adapter.recyclerview.holder.OnHolderClickListener; import org.nuclearfog.twidda.ui.adapter.recyclerview.holder.OnHolderClickListener;
import org.nuclearfog.twidda.ui.adapter.recyclerview.holder.PlaceHolder; import org.nuclearfog.twidda.ui.adapter.recyclerview.holder.PlaceHolder;
import org.nuclearfog.twidda.ui.adapter.recyclerview.holder.TrendHolder; import org.nuclearfog.twidda.ui.adapter.recyclerview.holder.HashtagHolder;
/** /**
* custom {@link androidx.recyclerview.widget.RecyclerView} adapter implementation to show trends * custom {@link androidx.recyclerview.widget.RecyclerView} adapter implementation to show trends
@ -18,7 +18,7 @@ import org.nuclearfog.twidda.ui.adapter.recyclerview.holder.TrendHolder;
* @author nuclearfog * @author nuclearfog
* @see org.nuclearfog.twidda.ui.fragments.HashtagFragment * @see org.nuclearfog.twidda.ui.fragments.HashtagFragment
*/ */
public class TrendAdapter extends Adapter<ViewHolder> implements OnHolderClickListener { public class HashtagAdapter extends Adapter<ViewHolder> implements OnHolderClickListener {
/** /**
* "index" used to replace the whole list with new items * "index" used to replace the whole list with new items
@ -31,15 +31,16 @@ public class TrendAdapter extends Adapter<ViewHolder> implements OnHolderClickLi
private static final int NO_LOADING = -1; private static final int NO_LOADING = -1;
private TrendClickListener itemClickListener; private OnHashtagClickListener itemClickListener;
private Trends items = new Trends(); private Trends items = new Trends();
private int loadingIndex = NO_LOADING; private int loadingIndex = NO_LOADING;
private boolean enableDelete = false;
/** /**
* @param itemClickListener Listener for item click * @param itemClickListener Listener for item click
*/ */
public TrendAdapter(TrendClickListener itemClickListener) { public HashtagAdapter(OnHashtagClickListener itemClickListener) {
this.itemClickListener = itemClickListener; this.itemClickListener = itemClickListener;
} }
@ -62,7 +63,7 @@ public class TrendAdapter extends Adapter<ViewHolder> implements OnHolderClickLi
@Override @Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) { public ViewHolder onCreateViewHolder(@NonNull ViewGroup parent, int viewType) {
if (viewType == TYPE_TREND) { if (viewType == TYPE_TREND) {
return new TrendHolder(parent, this); return new HashtagHolder(parent, this, enableDelete);
} else { } else {
return new PlaceHolder(parent, this, false); return new PlaceHolder(parent, this, false);
} }
@ -71,8 +72,8 @@ public class TrendAdapter extends Adapter<ViewHolder> implements OnHolderClickLi
@Override @Override
public void onBindViewHolder(@NonNull ViewHolder vh, int index) { public void onBindViewHolder(@NonNull ViewHolder vh, int index) {
if (vh instanceof TrendHolder) { if (vh instanceof HashtagHolder) {
TrendHolder holder = (TrendHolder) vh; HashtagHolder holder = (HashtagHolder) vh;
Hashtag hashtag = items.get(index); Hashtag hashtag = items.get(index);
if (hashtag != null) { if (hashtag != null) {
holder.setContent(hashtag, index); holder.setContent(hashtag, index);
@ -86,7 +87,11 @@ public class TrendAdapter extends Adapter<ViewHolder> implements OnHolderClickLi
@Override @Override
public void onItemClick(int position, int type, int... extras) { public void onItemClick(int position, int type, int... extras) {
itemClickListener.onTrendClick(items.get(position)); if (type == HASHTAG_CLICK) {
itemClickListener.onHashtagClick(items.get(position), OnHashtagClickListener.SELECT);
} else if (type == HASHTAG_REMOVE) {
itemClickListener.onHashtagClick(items.get(position), OnHashtagClickListener.REMOVE);
}
} }
@ -177,18 +182,32 @@ public class TrendAdapter extends Adapter<ViewHolder> implements OnHolderClickLi
} }
/** /**
* Listener for trend list *
*/ */
public interface TrendClickListener { public void enableDelete() {
enableDelete = true;
}
/**
* Listener for hashtag list
*/
public interface OnHashtagClickListener {
int SELECT = 1;
int REMOVE = 2;
/** /**
* called when a trend item is clicked * called when a trend item is clicked
* *
* @param hashtag trend name * @param hashtag trend name
* @param action action to take {@link #SELECT,#REMOVE}
*/ */
void onTrendClick(Hashtag hashtag); void onHashtagClick(Hashtag hashtag, int action);
/**
*
*/
boolean onPlaceholderClick(long cursor, int index); boolean onPlaceholderClick(long cursor, int index);
} }
} }

View File

@ -17,26 +17,28 @@ import org.nuclearfog.twidda.backend.utils.AppStyles;
import org.nuclearfog.twidda.backend.utils.StringUtils; import org.nuclearfog.twidda.backend.utils.StringUtils;
import org.nuclearfog.twidda.config.GlobalSettings; import org.nuclearfog.twidda.config.GlobalSettings;
import org.nuclearfog.twidda.model.Hashtag; import org.nuclearfog.twidda.model.Hashtag;
import org.nuclearfog.twidda.ui.adapter.recyclerview.HashtagAdapter;
/** /**
* ViewHolder for a trend item * ViewHolder for a trend item
* *
* @author nuclearfog * @author nuclearfog
* @see org.nuclearfog.twidda.ui.adapter.recyclerview.TrendAdapter * @see HashtagAdapter
*/ */
public class TrendHolder extends ViewHolder implements OnClickListener { public class HashtagHolder extends ViewHolder implements OnClickListener {
private TextView name, rank, vol; private TextView name, rank, vol;
private OnHolderClickListener listener; private OnHolderClickListener listener;
public TrendHolder(ViewGroup parent, OnHolderClickListener listener) { public HashtagHolder(ViewGroup parent, OnHolderClickListener listener, boolean enableRemove) {
super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_trend, parent, false)); super(LayoutInflater.from(parent.getContext()).inflate(R.layout.item_hashtag, parent, false));
this.listener = listener; this.listener = listener;
CardView background = (CardView) itemView; CardView background = (CardView) itemView;
ViewGroup container = itemView.findViewById(R.id.item_trend_container); ViewGroup container = itemView.findViewById(R.id.item_trend_container);
View btnRemove = itemView.findViewById(R.id.item_trend_delete_button);
rank = itemView.findViewById(R.id.item_trend_rank); rank = itemView.findViewById(R.id.item_trend_rank);
name = itemView.findViewById(R.id.item_trend_name); name = itemView.findViewById(R.id.item_trend_name);
vol = itemView.findViewById(R.id.item_trend_vol); vol = itemView.findViewById(R.id.item_trend_vol);
@ -44,16 +46,26 @@ public class TrendHolder extends ViewHolder implements OnClickListener {
GlobalSettings settings = GlobalSettings.get(parent.getContext()); GlobalSettings settings = GlobalSettings.get(parent.getContext());
AppStyles.setTheme(container, Color.TRANSPARENT); AppStyles.setTheme(container, Color.TRANSPARENT);
background.setCardBackgroundColor(settings.getCardColor()); background.setCardBackgroundColor(settings.getCardColor());
if (enableRemove) {
btnRemove.setVisibility(View.VISIBLE);
} else {
btnRemove.setVisibility(View.GONE);
}
itemView.setOnClickListener(this); itemView.setOnClickListener(this);
btnRemove.setOnClickListener(this);
} }
@Override @Override
public void onClick(View v) { public void onClick(View v) {
if (v == itemView) { int position = getLayoutPosition();
int position = getLayoutPosition(); if (v.getId() == R.id.item_trend_delete_button) {
if (position != RecyclerView.NO_POSITION) { if (position != RecyclerView.NO_POSITION) {
listener.onItemClick(position, OnHolderClickListener.NO_TYPE); listener.onItemClick(position, OnHolderClickListener.HASHTAG_REMOVE);
}
} else if (v == itemView) {
if (position != RecyclerView.NO_POSITION) {
listener.onItemClick(position, OnHolderClickListener.HASHTAG_CLICK);
} }
} }
} }

View File

@ -45,6 +45,10 @@ public interface OnHolderClickListener {
int FILTER_REMOVE = 23; int FILTER_REMOVE = 23;
int HASHTAG_CLICK = 24;
int HASHTAG_REMOVE = 25;
/** /**
* called when an item was clicked * called when an item was clicked
* *

View File

@ -84,7 +84,7 @@ public class UserHolder extends ViewHolder implements OnClickListener, AsyncCall
profileImg = itemView.findViewById(R.id.item_user_profile); profileImg = itemView.findViewById(R.id.item_user_profile);
verifyIcon = itemView.findViewById(R.id.item_user_verified); verifyIcon = itemView.findViewById(R.id.item_user_verified);
lockedIcon = itemView.findViewById(R.id.item_user_private); lockedIcon = itemView.findViewById(R.id.item_user_private);
delete = itemView.findViewById(R.id.item_user_delete_buton); delete = itemView.findViewById(R.id.item_user_delete_button);
placeholder = new ColorDrawable(EMPTY_COLOR); placeholder = new ColorDrawable(EMPTY_COLOR);
AppStyles.setTheme(container, Color.TRANSPARENT); AppStyles.setTheme(container, Color.TRANSPARENT);

View File

@ -134,6 +134,16 @@ public class ConfirmDialog extends Dialog implements OnClickListener {
*/ */
public static final int CONTINUE_BROWSER = 627; public static final int CONTINUE_BROWSER = 627;
/**
*
*/
public static final int UNFOLLOW_HASHTAG = 628;
/**
*
*/
public static final int UNFEATURE_HASHTAG = 629;
private TextView title, message, remember_label; private TextView title, message, remember_label;
private Button confirm, cancel; private Button confirm, cancel;
@ -275,6 +285,14 @@ public class ConfirmDialog extends Dialog implements OnClickListener {
titleRes = R.string.confirm_warning; titleRes = R.string.confirm_warning;
messageRes = R.string.confirm_proxy_bypass; messageRes = R.string.confirm_proxy_bypass;
break; break;
case UNFOLLOW_HASHTAG:
messageRes = R.string.confirm_hashtag_unfollow;
break;
case UNFEATURE_HASHTAG:
messageRes = R.string.confirm_hashtag_unfeature;
break;
} }
// setup title // setup title
title.setVisibility(titleVis); title.setVisibility(titleVis);

View File

@ -3,6 +3,7 @@ package org.nuclearfog.twidda.ui.fragments;
import android.content.Intent; import android.content.Intent;
import android.os.Bundle; import android.os.Bundle;
import android.view.View; import android.view.View;
import android.widget.Toast;
import androidx.activity.result.ActivityResult; import androidx.activity.result.ActivityResult;
import androidx.activity.result.ActivityResultCallback; import androidx.activity.result.ActivityResultCallback;
@ -11,16 +12,21 @@ import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import org.nuclearfog.twidda.R;
import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback; import org.nuclearfog.twidda.backend.async.AsyncExecutor.AsyncCallback;
import org.nuclearfog.twidda.backend.async.TrendLoader; import org.nuclearfog.twidda.backend.async.HashtagAction;
import org.nuclearfog.twidda.backend.async.TrendLoader.TrendParameter; import org.nuclearfog.twidda.backend.async.HashtagAction.HashtagActionParam;
import org.nuclearfog.twidda.backend.async.TrendLoader.TrendResult; import org.nuclearfog.twidda.backend.async.HashtagAction.HashtagActionResult;
import org.nuclearfog.twidda.backend.async.HashtagLoader;
import org.nuclearfog.twidda.backend.async.HashtagLoader.HashtagLoaderParam;
import org.nuclearfog.twidda.backend.async.HashtagLoader.HashtagLoaderResult;
import org.nuclearfog.twidda.backend.utils.ErrorUtils; import org.nuclearfog.twidda.backend.utils.ErrorUtils;
import org.nuclearfog.twidda.model.Hashtag; import org.nuclearfog.twidda.model.Hashtag;
import org.nuclearfog.twidda.model.lists.Trends; import org.nuclearfog.twidda.model.lists.Trends;
import org.nuclearfog.twidda.ui.activities.SearchActivity; import org.nuclearfog.twidda.ui.activities.SearchActivity;
import org.nuclearfog.twidda.ui.adapter.recyclerview.TrendAdapter; import org.nuclearfog.twidda.ui.adapter.recyclerview.HashtagAdapter;
import org.nuclearfog.twidda.ui.adapter.recyclerview.TrendAdapter.TrendClickListener; import org.nuclearfog.twidda.ui.adapter.recyclerview.HashtagAdapter.OnHashtagClickListener;
import org.nuclearfog.twidda.ui.dialogs.ConfirmDialog;
import java.io.Serializable; import java.io.Serializable;
@ -29,7 +35,7 @@ import java.io.Serializable;
* *
* @author nuclearfog * @author nuclearfog
*/ */
public class HashtagFragment extends ListFragment implements TrendClickListener, AsyncCallback<TrendResult>, ActivityResultCallback<ActivityResult> { public class HashtagFragment extends ListFragment implements OnHashtagClickListener, ActivityResultCallback<ActivityResult>, ConfirmDialog.OnConfirmListener {
/** /**
* setup fragment to show popular trends of an instance/location * setup fragment to show popular trends of an instance/location
@ -72,18 +78,26 @@ public class HashtagFragment extends ListFragment implements TrendClickListener,
private ActivityResultLauncher<Intent> activityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), this); private ActivityResultLauncher<Intent> activityResultLauncher = registerForActivityResult(new ActivityResultContracts.StartActivityForResult(), this);
private TrendLoader trendLoader; private AsyncCallback<HashtagActionResult> hashtagActionCallback = this::onHashtagActionResult;
private TrendAdapter adapter; private AsyncCallback<HashtagLoaderResult> hashtagLoaderCallback = this::onHashtagLoaderResult;
private HashtagLoader hashtagLoader;
private HashtagAction hashtagAction;
private HashtagAdapter adapter;
private ConfirmDialog confirmDialog;
private int mode = 0; private int mode = 0;
private String search = ""; private String search = "";
private Hashtag selection;
@Override @Override
public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) { public void onViewCreated(@NonNull View view, @Nullable Bundle savedInstanceState) {
super.onViewCreated(view, savedInstanceState); super.onViewCreated(view, savedInstanceState);
adapter = new TrendAdapter(this); adapter = new HashtagAdapter(this);
trendLoader = new TrendLoader(requireContext()); hashtagLoader = new HashtagLoader(requireContext());
hashtagAction = new HashtagAction(requireContext());
confirmDialog = new ConfirmDialog(requireActivity(), this);
setAdapter(adapter); setAdapter(adapter);
Bundle args = getArguments(); Bundle args = getArguments();
@ -91,15 +105,18 @@ public class HashtagFragment extends ListFragment implements TrendClickListener,
search = args.getString(KEY_SEARCH, ""); search = args.getString(KEY_SEARCH, "");
mode = args.getInt(KEY_MODE, 0); mode = args.getInt(KEY_MODE, 0);
} }
if (mode == MODE_FOLLOW || mode == MODE_FEATURE) {
adapter.enableDelete();
}
if (savedInstanceState != null) { if (savedInstanceState != null) {
Serializable data = savedInstanceState.getSerializable(KEY_DATA); Serializable data = savedInstanceState.getSerializable(KEY_DATA);
if (data instanceof Trends) { if (data instanceof Trends) {
adapter.addItems((Trends) data, TrendAdapter.CLEAR_LIST); adapter.addItems((Trends) data, HashtagAdapter.CLEAR_LIST);
return; return;
} }
} }
setRefresh(true); setRefresh(true);
load(TrendParameter.NO_CURSOR, TrendAdapter.CLEAR_LIST); load(HashtagLoaderParam.NO_CURSOR, HashtagAdapter.CLEAR_LIST);
} }
@ -112,7 +129,7 @@ public class HashtagFragment extends ListFragment implements TrendClickListener,
@Override @Override
public void onDestroy() { public void onDestroy() {
trendLoader.cancel(); hashtagLoader.cancel();
super.onDestroy(); super.onDestroy();
} }
@ -120,15 +137,15 @@ public class HashtagFragment extends ListFragment implements TrendClickListener,
@Override @Override
protected void onReset() { protected void onReset() {
adapter.clear(); adapter.clear();
trendLoader = new TrendLoader(requireContext()); hashtagLoader = new HashtagLoader(requireContext());
load(TrendParameter.NO_CURSOR, TrendAdapter.CLEAR_LIST); load(HashtagLoaderParam.NO_CURSOR, HashtagAdapter.CLEAR_LIST);
setRefresh(true); setRefresh(true);
} }
@Override @Override
protected void onReload() { protected void onReload() {
load(TrendParameter.NO_CURSOR, TrendAdapter.CLEAR_LIST); load(HashtagLoaderParam.NO_CURSOR, HashtagAdapter.CLEAR_LIST);
} }
@ -150,24 +167,35 @@ public class HashtagFragment extends ListFragment implements TrendClickListener,
@Override @Override
public void onTrendClick(Hashtag hashtag) { public void onHashtagClick(Hashtag hashtag, int action) {
if (!isRefreshing()) { if (action == OnHashtagClickListener.SELECT) {
Intent intent = new Intent(requireContext(), SearchActivity.class); if (!isRefreshing()) {
String name = hashtag.getName(); Intent intent = new Intent(requireContext(), SearchActivity.class);
if (!name.startsWith("#") && !name.startsWith("\"") && !name.endsWith("\"")) { String name = hashtag.getName();
name = "\"" + name + "\""; if (!name.startsWith("#") && !name.startsWith("\"") && !name.endsWith("\"")) {
intent.putExtra(SearchActivity.KEY_QUERY, name); name = "\"" + name + "\"";
} else { intent.putExtra(SearchActivity.KEY_QUERY, name);
intent.putExtra(SearchActivity.KEY_DATA, hashtag); } else {
intent.putExtra(SearchActivity.KEY_DATA, hashtag);
}
activityResultLauncher.launch(intent);
}
} else if (action == OnHashtagClickListener.REMOVE) {
if (!confirmDialog.isShowing()) {
selection = hashtag;
if (mode == MODE_FEATURE) {
confirmDialog.show(ConfirmDialog.UNFEATURE_HASHTAG);
} else if (mode == MODE_FOLLOW) {
confirmDialog.show(ConfirmDialog.UNFOLLOW_HASHTAG);
}
} }
activityResultLauncher.launch(intent);
} }
} }
@Override @Override
public boolean onPlaceholderClick(long cursor, int index) { public boolean onPlaceholderClick(long cursor, int index) {
if (trendLoader.isIdle()) { if (hashtagLoader.isIdle()) {
load(cursor, index); load(cursor, index);
return true; return true;
} }
@ -176,8 +204,36 @@ public class HashtagFragment extends ListFragment implements TrendClickListener,
@Override @Override
public void onResult(@NonNull TrendResult result) { public void onConfirm(int type, boolean remember) {
if (result.mode == TrendResult.ERROR) { if (selection != null) {
if (type == ConfirmDialog.UNFOLLOW_HASHTAG) {
HashtagActionParam param = new HashtagActionParam(HashtagActionParam.UNFOLLOW, selection.getName(), selection.getId());
hashtagAction.execute(param, hashtagActionCallback);
} else if (type == ConfirmDialog.UNFEATURE_HASHTAG) {
HashtagActionParam param = new HashtagActionParam(HashtagActionParam.UNFEATURE, selection.getName(), selection.getId());
hashtagAction.execute(param, hashtagActionCallback);
}
}
}
/**
* callback for {@link HashtagAction}
*/
private void onHashtagActionResult(@NonNull HashtagActionResult result) {
if (result.mode == HashtagActionResult.UNFEATURE) {
Toast.makeText(requireContext(), R.string.info_hashtag_unfeatured, Toast.LENGTH_SHORT).show();
adapter.removeItem(result.hashtag);
} else if (result.mode == HashtagActionResult.UNFOLLOW) {
Toast.makeText(requireContext(), R.string.info_hashtag_unfollowed, Toast.LENGTH_SHORT).show();
adapter.removeItem(result.hashtag);
}
}
/**
* callback for {@link HashtagLoader}
*/
private void onHashtagLoaderResult(@NonNull HashtagLoaderResult result) {
if (result.mode == HashtagLoaderResult.ERROR) {
if (getContext() != null) { if (getContext() != null) {
ErrorUtils.showErrorMessage(getContext(), result.exception); ErrorUtils.showErrorMessage(getContext(), result.exception);
} }
@ -192,30 +248,30 @@ public class HashtagFragment extends ListFragment implements TrendClickListener,
* load content into the list * load content into the list
*/ */
private void load(long cursor, int index) { private void load(long cursor, int index) {
TrendParameter param; HashtagLoaderParam param;
switch (mode) { switch (mode) {
case MODE_POPULAR: case MODE_POPULAR:
if (adapter.isEmpty()) { if (adapter.isEmpty()) {
param = new TrendParameter(TrendParameter.POPULAR_OFFLINE, index, search, cursor); param = new HashtagLoaderParam(HashtagLoaderParam.POPULAR_OFFLINE, index, search, cursor);
} else { } else {
param = new TrendParameter(TrendParameter.POPULAR_ONLINE, index, search, cursor); param = new HashtagLoaderParam(HashtagLoaderParam.POPULAR_ONLINE, index, search, cursor);
} }
trendLoader.execute(param, this); hashtagLoader.execute(param, hashtagLoaderCallback);
break; break;
case MODE_FOLLOW: case MODE_FOLLOW:
param = new TrendParameter(TrendParameter.FOLLOWING, index, search, cursor); param = new HashtagLoaderParam(HashtagLoaderParam.FOLLOWING, index, search, cursor);
trendLoader.execute(param, this); hashtagLoader.execute(param, hashtagLoaderCallback);
break; break;
case MODE_FEATURE: case MODE_FEATURE:
param = new TrendParameter(TrendParameter.FEATURING, index, search, cursor); param = new HashtagLoaderParam(HashtagLoaderParam.FEATURING, index, search, cursor);
trendLoader.execute(param, this); hashtagLoader.execute(param, hashtagLoaderCallback);
break; break;
case MODE_SEARCH: case MODE_SEARCH:
param = new TrendParameter(TrendParameter.SEARCH, index, search, cursor); param = new HashtagLoaderParam(HashtagLoaderParam.SEARCH, index, search, cursor);
trendLoader.execute(param, this); hashtagLoader.execute(param, hashtagLoaderCallback);
break; break;
} }
} }

View File

@ -9,16 +9,16 @@
android:id="@+id/item_trend_container" android:id="@+id/item_trend_container"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent" android:layout_height="match_parent"
android:padding="@dimen/trenditem_layout_padding"> android:padding="@dimen/item_hashtag_layout_padding">
<TextView <TextView
android:id="@+id/item_trend_rank" android:id="@+id/item_trend_rank"
android:layout_width="@dimen/trenditem_textsize_trendindex_width" android:layout_width="@dimen/item_hashtag_textsize_trendindex_width"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:gravity="center_vertical|end" android:gravity="center_vertical|end"
android:lines="1" android:lines="1"
android:textAlignment="gravity" android:textAlignment="gravity"
android:textSize="@dimen/trenditem_textsize_trendindex" android:textSize="@dimen/item_hashtag_textsize_trendindex"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toStartOf="@id/item_trend_name" app:layout_constraintEnd_toStartOf="@id/item_trend_name"
@ -28,28 +28,42 @@
android:id="@+id/item_trend_name" android:id="@+id/item_trend_name"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/trenditem_text_margin" android:layout_marginStart="@dimen/item_hashtag_layout_margin"
android:layout_marginEnd="@dimen/item_hashtag_layout_margin"
android:lines="1" android:lines="1"
android:textSize="@dimen/trenditem_textsize_trendname" android:textSize="@dimen/item_hashtag_textsize_trendname"
app:layout_constraintStart_toEndOf="@id/item_trend_rank" app:layout_constraintStart_toEndOf="@id/item_trend_rank"
app:layout_constraintTop_toTopOf="parent" app:layout_constraintTop_toTopOf="parent"
app:layout_constraintBottom_toTopOf="@id/item_trend_vol" app:layout_constraintBottom_toTopOf="@id/item_trend_vol"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toStartOf="@id/item_trend_delete_button"
app:layout_constrainedWidth="true" /> app:layout_constrainedWidth="true" />
<TextView <TextView
android:id="@+id/item_trend_vol" android:id="@+id/item_trend_vol"
android:layout_width="0dp" android:layout_width="0dp"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:layout_marginStart="@dimen/trenditem_text_margin" android:layout_marginStart="@dimen/item_hashtag_layout_margin"
android:lines="1" android:lines="1"
android:textSize="@dimen/trenditem_textsize_trendvol" android:textSize="@dimen/item_hashtag_textsize_trendvol"
app:layout_constraintStart_toEndOf="@id/item_trend_rank" app:layout_constraintStart_toEndOf="@id/item_trend_rank"
app:layout_constraintTop_toBottomOf="@id/item_trend_name" app:layout_constraintTop_toBottomOf="@id/item_trend_name"
app:layout_constraintBottom_toBottomOf="parent" app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constrainedWidth="false" /> app:layout_constrainedWidth="false" />
<ImageButton
android:id="@+id/item_trend_delete_button"
android:layout_width="@dimen/item_hashtag_button_size"
android:layout_height="@dimen/item_hashtag_button_size"
android:visibility="invisible"
android:padding="@dimen/item_hashtag_button_padding"
android:contentDescription="@string/descr_remove_hashtag"
android:scaleType="fitCenter"
android:src="@drawable/cross"
app:layout_constraintTop_toTopOf="parent"
app:layout_constraintEnd_toEndOf="parent"
style="@style/RoundButton" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>
</androidx.cardview.widget.CardView> </androidx.cardview.widget.CardView>

View File

@ -80,7 +80,7 @@
app:layout_constraintStart_toEndOf="@id/item_user_verified" app:layout_constraintStart_toEndOf="@id/item_user_verified"
app:layout_constraintTop_toTopOf="@id/item_user_profile" app:layout_constraintTop_toTopOf="@id/item_user_profile"
app:layout_constraintBottom_toTopOf="@id/item_user_screenname" app:layout_constraintBottom_toTopOf="@id/item_user_screenname"
app:layout_constraintEnd_toStartOf="@id/item_user_delete_buton" /> app:layout_constraintEnd_toStartOf="@id/item_user_delete_button" />
<ImageView <ImageView
android:id="@+id/item_user_private" android:id="@+id/item_user_private"
@ -106,7 +106,7 @@
app:layout_constraintStart_toEndOf="@id/item_user_private" app:layout_constraintStart_toEndOf="@id/item_user_private"
app:layout_constraintTop_toBottomOf="@id/item_user_username" app:layout_constraintTop_toBottomOf="@id/item_user_username"
app:layout_constraintBottom_toTopOf="@id/item_user_following_icon" app:layout_constraintBottom_toTopOf="@id/item_user_following_icon"
app:layout_constraintEnd_toStartOf="@id/item_user_delete_buton" /> app:layout_constraintEnd_toStartOf="@id/item_user_delete_button" />
<ImageView <ImageView
android:id="@+id/item_user_following_icon" android:id="@+id/item_user_following_icon"
@ -157,10 +157,10 @@
app:layout_constraintStart_toEndOf="@id/item_user_follower_icon" app:layout_constraintStart_toEndOf="@id/item_user_follower_icon"
app:layout_constraintTop_toTopOf="@id/item_user_follower_icon" app:layout_constraintTop_toTopOf="@id/item_user_follower_icon"
app:layout_constraintBottom_toBottomOf="@id/item_user_follower_icon" app:layout_constraintBottom_toBottomOf="@id/item_user_follower_icon"
app:layout_constraintEnd_toStartOf="@id/item_user_delete_buton" /> app:layout_constraintEnd_toStartOf="@id/item_user_delete_button" />
<ImageButton <ImageButton
android:id="@+id/item_user_delete_buton" android:id="@+id/item_user_delete_button"
android:layout_width="@dimen/item_user_button_size" android:layout_width="@dimen/item_user_button_size"
android:layout_height="@dimen/item_user_button_size" android:layout_height="@dimen/item_user_button_size"
android:visibility="invisible" android:visibility="invisible"

View File

@ -209,6 +209,7 @@
<string name="menu_hint_mute_user">\@name stummschalten</string> <string name="menu_hint_mute_user">\@name stummschalten</string>
<string name="menu_hint_block_user">\@name blockieren</string> <string name="menu_hint_block_user">\@name blockieren</string>
<string name="menu_exclude_user">Nutzer ausschließen</string> <string name="menu_exclude_user">Nutzer ausschließen</string>
<string name="menu_hashtag_add">Hashtag hinzufügen</string>
<string name="menu_search_filter">Ergebnisse filtern</string> <string name="menu_search_filter">Ergebnisse filtern</string>
<string name="menu_hint_block_domain">domain.namen eingeben</string> <string name="menu_hint_block_domain">domain.namen eingeben</string>
<string name="menu_licenses">Lizenzen</string> <string name="menu_licenses">Lizenzen</string>
@ -296,11 +297,13 @@
<string name="error_duration_time_low">Dauer der Umfrage ist zu kurz!</string> <string name="error_duration_time_low">Dauer der Umfrage ist zu kurz!</string>
<string name="error_duration_time_high">Dauer der Umfrage ist zu lang!</string> <string name="error_duration_time_high">Dauer der Umfrage ist zu lang!</string>
<string name="error_poll_option_missing">Umfrageoption darf nicht leer sein!</string> <string name="error_poll_option_missing">Umfrageoption darf nicht leer sein!</string>
<string name="menu_hashtags">Hashtags</string>
<string name="menu_hashtag_follow">folge Hashtag</string> <string name="menu_hashtag_follow">folge Hashtag</string>
<string name="menu_hashtag_unfollow">entfolge Hashtag</string> <string name="menu_hashtag_unfollow">entfolge Hashtag</string>
<string name="info_hashtag_unfollowed">Hashtag entfolgt</string> <string name="info_hashtag_unfollowed">Hashtag entfolgt</string>
<string name="info_domain_removed">Domain von der Liste entfernt</string> <string name="info_domain_removed">Domain von der Liste entfernt</string>
<string name="info_hashtag_followed">Hashtag gefolgt</string> <string name="info_hashtag_followed">Hashtag gefolgt</string>
<string name="dialog_status_schedule_set">Veröffentlichung planen</string>
<string name="dialog_push_repost">bei Reposts</string> <string name="dialog_push_repost">bei Reposts</string>
<string name="dialog_push_favorite">bei Favorisierung</string> <string name="dialog_push_favorite">bei Favorisierung</string>
<string name="dialog_push_poll_finished">bei beendeten Umfragen</string> <string name="dialog_push_poll_finished">bei beendeten Umfragen</string>
@ -354,4 +357,6 @@
<string name="error_empty_filter_title">Filterbezeichnung darf nicht leer sein</string> <string name="error_empty_filter_title">Filterbezeichnung darf nicht leer sein</string>
<string name="error_empty_filter_selection">es muss mindestens ein Filter aktiviert sein</string> <string name="error_empty_filter_selection">es muss mindestens ein Filter aktiviert sein</string>
<string name="dialog_description_title">Medienbeschreibung</string> <string name="dialog_description_title">Medienbeschreibung</string>
<string name="descr_remove_hashtag">Hashtag von der Liste entfernen</string>
<string name="confirm_hashtag_unfollow">Hashtag entfolgen?</string>
</resources> </resources>

View File

@ -92,13 +92,15 @@
<dimen name="item_status_indicator_padding">6sp</dimen> <dimen name="item_status_indicator_padding">6sp</dimen>
<dimen name="item_status_indicator_size">26sp</dimen> <dimen name="item_status_indicator_size">26sp</dimen>
<!--dimens of item_trend.xml--> <!--dimens of item_hashtag.xml-->
<dimen name="trenditem_layout_padding">5dp</dimen> <dimen name="item_hashtag_layout_padding">5dp</dimen>
<dimen name="trenditem_text_margin">5dp</dimen> <dimen name="item_hashtag_layout_margin">5dp</dimen>
<dimen name="trenditem_textsize_trendindex">20sp</dimen> <dimen name="item_hashtag_button_size">20sp</dimen>
<dimen name="trenditem_textsize_trendname">20sp</dimen> <dimen name="item_hashtag_button_padding">1dp</dimen>
<dimen name="trenditem_textsize_trendvol">14sp</dimen> <dimen name="item_hashtag_textsize_trendindex">20sp</dimen>
<dimen name="trenditem_textsize_trendindex_width">40sp</dimen> <dimen name="item_hashtag_textsize_trendname">20sp</dimen>
<dimen name="item_hashtag_textsize_trendvol">14sp</dimen>
<dimen name="item_hashtag_textsize_trendindex_width">40sp</dimen>
<!--dimens of item_user.xml--> <!--dimens of item_user.xml-->
<dimen name="item_user_image_size">56sp</dimen> <dimen name="item_user_image_size">56sp</dimen>

View File

@ -78,6 +78,7 @@
<string name="info_hashtag_featured">Hashtag featured</string> <string name="info_hashtag_featured">Hashtag featured</string>
<string name="info_hashtag_followed">Hashtag followed</string> <string name="info_hashtag_followed">Hashtag followed</string>
<string name="info_hashtag_unfollowed">Hashtag unfollowed</string> <string name="info_hashtag_unfollowed">Hashtag unfollowed</string>
<string name="info_hashtag_unfeatured">Hashtag unfeatured</string>
<string name="info_status_reported">Status reported</string> <string name="info_status_reported">Status reported</string>
<string name="info_error">Error</string> <string name="info_error">Error</string>
@ -289,6 +290,7 @@
<string name="update_list">update list</string> <string name="update_list">update list</string>
<string name="confirm_remove_user_from_list">remove user from list?</string> <string name="confirm_remove_user_from_list">remove user from list?</string>
<string name="descr_remove_user">remove user from list</string> <string name="descr_remove_user">remove user from list</string>
<string name="descr_remove_hashtag">remove hashtag from list</string>
<string name="descr_remove_domain">remove domain from list</string> <string name="descr_remove_domain">remove domain from list</string>
<string name="confirm_unknown_error">unknown error!</string> <string name="confirm_unknown_error">unknown error!</string>
<string name="list_following_indicator">following list</string> <string name="list_following_indicator">following list</string>
@ -307,6 +309,8 @@
<string name="confirm_remember">remember choice</string> <string name="confirm_remember">remember choice</string>
<string name="confirm_warning">Warning</string> <string name="confirm_warning">Warning</string>
<string name="confirm_proxy_bypass">Opening an external link would bypass proxy connection. Proceed?</string> <string name="confirm_proxy_bypass">Opening an external link would bypass proxy connection. Proceed?</string>
<string name="confirm_hashtag_unfollow">unfollow hashtag?</string>
<string name="confirm_hashtag_unfeature">unfeature hashtag?</string>
<string name="confirm_remove_account">remove account from list?</string> <string name="confirm_remove_account">remove account from list?</string>
<string name="confirm_remove_filter">delete filter?</string> <string name="confirm_remove_filter">delete filter?</string>
<string name="account_user_unnamed">\'unnamed\'</string> <string name="account_user_unnamed">\'unnamed\'</string>