Sort modes

This commit is contained in:
pegasko 2025-01-08 21:25:40 +03:00
parent 72b00399bf
commit b52a0b4b34
19 changed files with 589 additions and 157 deletions

View file

@ -0,0 +1,67 @@
/**
* Copyright 2024 pegasko
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package art.pegasko.yeeemp.base;
import androidx.annotation.Nullable;
public class EventOrder {
public static String orderToString(@Nullable Order order) {
if (order == null) {
return null;
}
switch (order) {
case ID_ASC:
return "id_asc";
case ID_DESC:
return "id_desc";
case TIMESTAMP_ASC:
return "timestamp_asc";
case TIMESTAMP_DESC:
return "timestamp_desc";
default:
throw new RuntimeException("Not implemented for " + order);
}
}
public static Order orderFromString(@Nullable String order) {
if (order == null) {
return null;
}
switch (order) {
case "id_asc":
return Order.ID_ASC;
case "id_desc":
return Order.ID_DESC;
case "timestamp_asc":
return Order.TIMESTAMP_ASC;
case "timestamp_desc":
return Order.TIMESTAMP_DESC;
default:
return Order.ID_ASC;
// throw new RuntimeException("Not implemented for " + order);
}
}
public enum Order {
ID_ASC,
ID_DESC,
TIMESTAMP_ASC,
TIMESTAMP_DESC,
}
}

View file

@ -23,7 +23,7 @@ public interface Queue {
void setName(String name); void setName(String name);
Event[] getEvents(); Event[] getEvents(EventOrder.Order order);
int getEventCount(); int getEventCount();

View file

@ -23,5 +23,5 @@ public interface QueueMaker {
void delete(Queue queue); void delete(Queue queue);
Queue[] list(); Queue[] list(QueueOrder.Order order);
} }

View file

@ -0,0 +1,56 @@
/**
* Copyright 2024 pegasko
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package art.pegasko.yeeemp.base;
import androidx.annotation.Nullable;
public class QueueOrder {
public static String orderToString(@Nullable Order order) {
if (order == null) {
return null;
}
switch (order) {
case ID:
return "id";
case NAME:
return "name";
default:
throw new RuntimeException("Not implemented for " + order);
}
}
public static Order orderFromString(@Nullable String order) {
if (order == null) {
return null;
}
switch (order) {
case "id":
return Order.ID;
case "name":
return Order.NAME;
default:
throw new RuntimeException("Not implemented for " + order);
}
}
public enum Order {
ID,
NAME,
}
}

View file

@ -57,11 +57,7 @@ public class EventImpl implements Event {
null null
); );
if (Utils.findResult(cursor)) { return Utils.getLongAndClose(cursor, 0);
return cursor.getLong(0);
}
return 0;
} }
} }
@ -70,7 +66,13 @@ public class EventImpl implements Event {
synchronized (this.db) { synchronized (this.db) {
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.put("timestamp", timestamp); cv.put("timestamp", timestamp);
db.update("event", cv, "id = ?", new String[] { Integer.toString(this.getId()) });
db.update(
"event",
cv,
"id = ?",
new String[] { Integer.toString(this.getId()) }
);
} }
} }
@ -87,11 +89,7 @@ public class EventImpl implements Event {
null null
); );
if (Utils.findResult(cursor)) { return Utils.getStringAndClose(cursor, null);
return cursor.getString(0);
}
return null;
} }
} }
@ -100,7 +98,13 @@ public class EventImpl implements Event {
synchronized (this.db) { synchronized (this.db) {
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.put("comment", comment); cv.put("comment", comment);
db.update("event", cv, "id = ?", new String[] { Integer.toString(this.getId()) });
db.update(
"event",
cv,
"id = ?",
new String[] { Integer.toString(this.getId()) }
);
} }
} }
@ -109,11 +113,19 @@ public class EventImpl implements Event {
*/ */
protected boolean hasTag(Tag tag) { protected boolean hasTag(Tag tag) {
synchronized (this.db) { synchronized (this.db) {
Cursor cursor = db.query("event_tag", new String[] { "1" }, "event_id = ? AND tag_id = ?", new String[] { Cursor cursor = db.query(
"event_tag",
new String[] { "1" },
"event_id = ? AND tag_id = ?",
new String[] {
Integer.toString(this.getId()), Integer.toString(tag.getId()) Integer.toString(this.getId()), Integer.toString(tag.getId())
}, null, null, null); },
null,
null,
null
);
return Utils.findResult(cursor); return Utils.findResultAndClose(cursor);
} }
} }
@ -195,6 +207,7 @@ public class EventImpl implements Event {
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
tags[index++] = new TagImpl(this.db, cursor.getInt(0)); tags[index++] = new TagImpl(this.db, cursor.getInt(0));
} }
cursor.close();
return this.cachedTags = tags; return this.cachedTags = tags;
} }

View file

@ -49,7 +49,9 @@ public class EventMakerImpl implements EventMaker {
null null
); );
if (Utils.findResult(cursor)) return new EventImpl(this.db, id); if (Utils.findResultAndClose(cursor)) {
return new EventImpl(this.db, id);
}
} catch (SQLiteException e) { } catch (SQLiteException e) {
Log.wtf(TAG, e); Log.wtf(TAG, e);

View file

@ -25,10 +25,9 @@ import android.util.Log;
import androidx.annotation.NonNull; import androidx.annotation.NonNull;
import art.pegasko.yeeemp.base.Event; import art.pegasko.yeeemp.base.Event;
import art.pegasko.yeeemp.base.EventOrder;
import art.pegasko.yeeemp.base.Queue; import art.pegasko.yeeemp.base.Queue;
import art.pegasko.yeeemp.base.Tag;
import art.pegasko.yeeemp.base.TagStat; import art.pegasko.yeeemp.base.TagStat;
import kotlin.NotImplementedError;
public class QueueImpl implements Queue { public class QueueImpl implements Queue {
public static final String TAG = QueueImpl.class.getSimpleName(); public static final String TAG = QueueImpl.class.getSimpleName();
@ -59,11 +58,7 @@ public class QueueImpl implements Queue {
null null
); );
if (Utils.findResult(cursor)) { return Utils.getStringAndClose(cursor, null);
return cursor.getString(0);
}
return null;
} }
} }
@ -72,22 +67,63 @@ public class QueueImpl implements Queue {
synchronized (this.db) { synchronized (this.db) {
ContentValues cv = new ContentValues(); ContentValues cv = new ContentValues();
cv.put("name", name); cv.put("name", name);
db.update("queue", cv, "id = ?", new String[] { Integer.toString(this.getId()) });
db.update(
"queue",
cv,
"id = ?",
new String[] { Integer.toString(this.getId()) }
);
} }
} }
@Override @Override
public Event[] getEvents() { public Event[] getEvents(EventOrder.Order order) {
synchronized (this.db) { synchronized (this.db) {
Cursor cursor = db.query( Cursor cursor;
// Requires JOIN
if ((order == EventOrder.Order.TIMESTAMP_ASC) || (order == EventOrder.Order.TIMESTAMP_DESC)) {
String query = (
"select\n" +
" queue_event.event_id\n" +
"from\n" +
" queue_event\n" +
"left join\n" +
" event\n" +
"on\n" +
" queue_event.event_id = event.id\n" +
"where\n" +
" queue_event.queue_id = ?\n" +
"order by\n" +
" timestamp "
);
if (order == EventOrder.Order.TIMESTAMP_ASC) {
query += "asc";
} else {
query += "desc";
}
cursor = db.rawQuery(
query,
new String[] { Integer.toString(this.getId()) }
);
} else {
cursor = db.query(
"queue_event", "queue_event",
new String[] { "event_id" }, new String[] { "event_id" },
"queue_id = ?", "queue_id = ?",
new String[] { Integer.toString(this.getId()) }, new String[] { Integer.toString(this.getId()) },
null, null,
null, null,
"event_id desc" ( order == EventOrder.Order.ID_ASC ?
"event_id asc" :
( order == EventOrder.Order.ID_DESC ?
"event_id desc" :
null
))
); );
}
if (cursor == null) { if (cursor == null) {
return new Event[0]; return new Event[0];
@ -99,6 +135,7 @@ public class QueueImpl implements Queue {
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
events[index++] = new EventImpl(this.db, cursor.getInt(0)); events[index++] = new EventImpl(this.db, cursor.getInt(0));
} }
cursor.close();
return events; return events;
} }
@ -117,9 +154,7 @@ public class QueueImpl implements Queue {
null null
); );
if (!Utils.findResult(cursor)) return 0; return Utils.getIntAndClose(cursor, 0);
return cursor.getInt(0);
} }
} }
@ -138,7 +173,7 @@ public class QueueImpl implements Queue {
null null
); );
return Utils.findResult(cursor); return Utils.findResultAndClose(cursor);
} }
} }
@ -183,7 +218,38 @@ public class QueueImpl implements Queue {
public TagStat[] getGlobalTags() { public TagStat[] getGlobalTags() {
synchronized (this.db) { synchronized (this.db) {
Cursor cursor = db.rawQuery( Cursor cursor = db.rawQuery(
"select" + " tag_id,\n" + " count(*) as cnt\n" + "from (\n" + " select\n" + " event_id,\n" + " tag_id\n" + " from (\n" + " select\n" + " event_tag.event_id as event_id,\n" + " event_tag.tag_id as tag_id\n" + " from (\n" + " select\n" + " event_id\n" + " from\n" + " queue_event\n" + " where\n" + " queue_id = ?\n" + " ) as queue_event_temp\n" + " inner join\n" + " event_tag\n" + " on\n" + " (event_tag.event_id = queue_event_temp.event_id)\n" + " )\n" + " group by\n" + " event_id,\n" + " tag_id\n" + ")\n" + "group by\n" + " tag_id\n" + "order by\n" + " cnt desc", "select" +
" tag_id,\n" +
" count(*) as cnt\n" +
"from (\n" +
" select\n" +
" event_id,\n" +
" tag_id\n" +
" from (\n" +
" select\n" +
" event_tag.event_id as event_id,\n" +
" event_tag.tag_id as tag_id\n" +
" from (\n" +
" select\n" +
" event_id\n" +
" from\n" +
" queue_event\n" +
" where\n" +
" queue_id = ?\n" +
" ) as queue_event_temp\n" +
" inner join\n" +
" event_tag\n" +
" on\n" +
" (event_tag.event_id = queue_event_temp.event_id)\n" +
" )\n" +
" group by\n" +
" event_id,\n" +
" tag_id\n" +
")\n" +
"group by\n" +
" tag_id\n" +
"order by\n" +
" cnt desc",
new String[] { Integer.toString(this.getId()) } new String[] { Integer.toString(this.getId()) }
); );

View file

@ -23,9 +23,10 @@ import android.database.sqlite.SQLiteException;
import android.util.Log; import android.util.Log;
import art.pegasko.yeeemp.base.Event; import art.pegasko.yeeemp.base.Event;
import art.pegasko.yeeemp.base.EventMaker; import art.pegasko.yeeemp.base.EventOrder;
import art.pegasko.yeeemp.base.Queue; import art.pegasko.yeeemp.base.Queue;
import art.pegasko.yeeemp.base.QueueMaker; import art.pegasko.yeeemp.base.QueueMaker;
import art.pegasko.yeeemp.base.QueueOrder;
public class QueueMakerImpl implements QueueMaker { public class QueueMakerImpl implements QueueMaker {
public static final String TAG = EventMakerImpl.class.getSimpleName(); public static final String TAG = EventMakerImpl.class.getSimpleName();
@ -40,7 +41,8 @@ public class QueueMakerImpl implements QueueMaker {
public Queue getById(int id) { public Queue getById(int id) {
synchronized (this.db) { synchronized (this.db) {
try { try {
Cursor cursor = db.query("queue", Cursor cursor = db.query(
"queue",
new String[] { "1" }, new String[] { "1" },
"id = ?", "id = ?",
new String[] { Integer.toString(id) }, new String[] { Integer.toString(id) },
@ -49,7 +51,9 @@ public class QueueMakerImpl implements QueueMaker {
null null
); );
if (Utils.findResult(cursor)) return new QueueImpl(this.db, id); if (Utils.findResultAndClose(cursor)) {
return new QueueImpl(this.db, id);
}
} catch (SQLiteException e) { } catch (SQLiteException e) {
Log.wtf(TAG, e); Log.wtf(TAG, e);
@ -76,9 +80,22 @@ public class QueueMakerImpl implements QueueMaker {
} }
@Override @Override
public Queue[] list() { public Queue[] list(QueueOrder.Order order) {
synchronized (this.db) { synchronized (this.db) {
Cursor cursor = db.query("queue", new String[] { "id" }, null, null, null, null, null); Cursor cursor = db.query(
"queue",
new String[] { "id" },
null,
null,
null,
null,
( order == QueueOrder.Order.ID ?
"rowid" :
( order == QueueOrder.Order.NAME ?
"name" :
null
))
);
if (cursor == null) { if (cursor == null) {
return new Queue[0]; return new Queue[0];
@ -90,6 +107,7 @@ public class QueueMakerImpl implements QueueMaker {
while (cursor.moveToNext()) { while (cursor.moveToNext()) {
queues[index++] = new QueueImpl(this.db, cursor.getInt(0)); queues[index++] = new QueueImpl(this.db, cursor.getInt(0));
} }
cursor.close();
return queues; return queues;
} }
@ -101,7 +119,8 @@ public class QueueMakerImpl implements QueueMaker {
// Drop events // Drop events
try { try {
for (Event event : queue.getEvents()) { // TODO: Foreign key for cascade delete
for (Event event : queue.getEvents(EventOrder.Order.ID_DESC)) {
db.delete("event", "id = ?", new String[] { Integer.toString(event.getId()) }); db.delete("event", "id = ?", new String[] { Integer.toString(event.getId()) });
} }
} catch (SQLiteException e) { } catch (SQLiteException e) {

View file

@ -57,8 +57,9 @@ public class TagImpl implements Tag {
null null
); );
if (Utils.findResult(cursor)) { String result = Utils.getStringAndClose(cursor, null);
return this._cached_name = cursor.getString(0); if (result != null) {
return this._cached_name = result;
} }
return null; return null;

View file

@ -47,10 +47,12 @@ public class TagMakerImpl implements TagMaker {
null, null,
null null
); );
if (Utils.findResult(cursor)) {
int id = Utils.getIntAndClose(cursor, -1);
if (id != -1) {
return new TagImpl( return new TagImpl(
db, db,
cursor.getInt(0) id
); );
} }
} catch (SQLiteException e) { } catch (SQLiteException e) {

View file

@ -20,9 +20,85 @@ import android.database.Cursor;
import android.database.sqlite.SQLiteDatabase; import android.database.sqlite.SQLiteDatabase;
public class Utils { public class Utils {
public static boolean findResult(Cursor cursor) { public static boolean findResultAndClose(Cursor cursor) {
if (cursor == null) return false; if (cursor == null)
cursor.moveToFirst(); return false;
return cursor.getCount() != 0;
boolean hasResult = cursor.moveToFirst();
if (!hasResult) {
cursor.close();
return false;
}
boolean result = cursor.getCount() != 0;
cursor.close();
return result;
}
public static int getIntAndClose(Cursor cursor, int def) {
if (cursor == null)
return def;
boolean hasResult = cursor.moveToFirst();
if (!hasResult) {
cursor.close();
return def;
}
if (cursor.getCount() == 0) {
cursor.close();
return def;
}
int result = cursor.getInt(0);
cursor.close();
return result;
}
public static long getLongAndClose(Cursor cursor, long def) {
if (cursor == null)
return def;
boolean hasResult = cursor.moveToFirst();
if (!hasResult) {
cursor.close();
return def;
}
if (cursor.getCount() == 0) {
cursor.close();
return def;
}
long result = cursor.getLong(0);
cursor.close();
return result;
}
public static String getStringAndClose(Cursor cursor, String def) {
if (cursor == null)
return def;
boolean hasResult = cursor.moveToFirst();
if (!hasResult) {
cursor.close();
return def;
}
if (cursor.getCount() == 0) {
cursor.close();
return def;
}
String result = cursor.getString(0);
cursor.close();
return result;
} }
} }

View file

@ -0,0 +1,5 @@
package art.pegasko.yeeemp.ui.activity;
public class Common {
public static final String PREFS_NAME = "preferences";
}

View file

@ -16,25 +16,27 @@
package art.pegasko.yeeemp.ui.activity; package art.pegasko.yeeemp.ui.activity;
import android.app.AlertDialog;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.SharedPreferences;
import android.os.Bundle; import android.os.Bundle;
import android.util.Log; import android.util.Log;
import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import androidx.annotation.NonNull; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import androidx.recyclerview.widget.ItemTouchHelper;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import java.time.Duration; import java.util.Objects;
import java.util.concurrent.ScheduledFuture;
import art.pegasko.yeeemp.R; import art.pegasko.yeeemp.R;
import art.pegasko.yeeemp.base.Event; import art.pegasko.yeeemp.base.EventOrder;
import art.pegasko.yeeemp.base.Queue; import art.pegasko.yeeemp.base.Queue;
import art.pegasko.yeeemp.base.Wrapper; import art.pegasko.yeeemp.base.Wrapper;
import art.pegasko.yeeemp.databinding.ActivityEventListBinding; import art.pegasko.yeeemp.databinding.ActivityEventListBinding;
@ -43,6 +45,9 @@ import art.pegasko.yeeemp.impl.Init;
public class EventListActivity extends AppCompatActivity { public class EventListActivity extends AppCompatActivity {
public static final String TAG = EventListActivity.class.getSimpleName(); public static final String TAG = EventListActivity.class.getSimpleName();
private static final String PREFS_UI_EVENT_ORDER = "ui-event-order";
private static final String PREFS_UI_EVENT_ORDER_DEFAULT = "id_desc";
private ActivityEventListBinding binding; private ActivityEventListBinding binding;
private RecyclerView eventList; private RecyclerView eventList;
private EventRecyclerViewAdapter eventListAdapter; private EventRecyclerViewAdapter eventListAdapter;
@ -54,6 +59,25 @@ public class EventListActivity extends AppCompatActivity {
}); });
} }
private CharSequence getEventOrderDisplayString(@Nullable EventOrder.Order order) {
if (order == null) {
throw new RuntimeException("Order is null");
}
switch (order) {
case ID_ASC:
return "created ascending";
case ID_DESC:
return "created descending";
case TIMESTAMP_ASC:
return "date ascending";
case TIMESTAMP_DESC:
return "date descending";
default:
throw new RuntimeException("Not implemented for " + order);
}
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -82,6 +106,7 @@ public class EventListActivity extends AppCompatActivity {
return; return;
} }
/* Bindings */
binding = ActivityEventListBinding.inflate(getLayoutInflater()); binding = ActivityEventListBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot()); setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar); setSupportActionBar(binding.toolbar);
@ -89,31 +114,73 @@ public class EventListActivity extends AppCompatActivity {
getSupportActionBar().setDisplayHomeAsUpEnabled(true); getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true); getSupportActionBar().setDisplayShowHomeEnabled(true);
/* Menu handlers */
binding.toolbar.setOnMenuItemClickListener((MenuItem item) -> {
if (item.getItemId() == R.id.event_list_toolbar_menu_order) {
/* Get some options */
SharedPreferences prefs = this.getApplicationContext().getSharedPreferences(Common.PREFS_NAME, MODE_PRIVATE);
EventOrder.Order order = EventOrder.orderFromString(prefs.getString(PREFS_UI_EVENT_ORDER, PREFS_UI_EVENT_ORDER_DEFAULT));
/* Construct options */
EventOrder.Order[] values = EventOrder.Order.class.getEnumConstants();
if (values == null) {
// How did we get here?
throw new RuntimeException("EventOrder.Order.class.getEnumConstants() is Empty");
}
CharSequence[] choices = new CharSequence[values.length];
int currentSelection = -1;
for (int i = 0; i < values.length; ++i) {
choices[i] = this.getEventOrderDisplayString(values[i]);
if (values[i] == order) {
currentSelection = i;
}
}
/* Build dialog */
AlertDialog.Builder builder = new AlertDialog.Builder(EventListActivity.this);
builder.setTitle("Order by ..");
builder.setSingleChoiceItems(
choices,
currentSelection,
(dialog, which) -> {
EventOrder.Order newOrder = values[which];
// Save option
prefs.edit().putString(PREFS_UI_EVENT_ORDER, EventOrder.orderToString(newOrder)).apply();
// Apply
EventListActivity.this.invalidateOptionsMenu();
eventListAdapter.setOrder(newOrder);
runOnUiThread(() -> {
eventListAdapter.reloadItems();
});
// Close dialog
dialog.dismiss();
}
);
builder.show();
return true;
}
return false;
});
/* Get some options */
SharedPreferences prefs = this.getApplicationContext().getSharedPreferences(Common.PREFS_NAME, MODE_PRIVATE);
EventOrder.Order order = EventOrder.orderFromString(prefs.getString(PREFS_UI_EVENT_ORDER, PREFS_UI_EVENT_ORDER_DEFAULT));
/* Queue list */ /* Queue list */
eventListAdapter = new EventRecyclerViewAdapter(queue); eventListAdapter = new EventRecyclerViewAdapter(queue);
eventListAdapter.setOrder(order);
eventList = findViewById(R.id.content_event_list__list); eventList = findViewById(R.id.content_event_list__list);
eventList.setLayoutManager(new LinearLayoutManager(this)); eventList.setLayoutManager(new LinearLayoutManager(this));
eventList.setAdapter(eventListAdapter); eventList.setAdapter(eventListAdapter);
// /* Swipe delete */
// ItemTouchHelper.SimpleCallback swipeCallback = new ItemTouchHelper.SimpleCallback(0, ItemTouchHelper.LEFT | ItemTouchHelper.RIGHT) {
// @Override
// public boolean onMove(@NonNull RecyclerView recyclerView, @NonNull RecyclerView.ViewHolder viewHolder, @NonNull RecyclerView.ViewHolder target) {
// return false;
// }
//
// @Override
// public void onSwiped(@NonNull RecyclerView.ViewHolder viewHolder, int direction) {
// Snackbar.make(viewHolder.itemView, "undo delete event", 5000)
// .setAnchorView(R.id.fab)
// .setAction("Action", (View view) -> {
//
// }).show();
//
// ScheduledFuture<?> future = eventListAdapter.scheduleDeleteAt(viewHolder.getAdapterPosition());
// }
// };
/* FAB Listeners */ /* FAB Listeners */
binding.fab.setOnLongClickListener((View view) -> { binding.fab.setOnLongClickListener((View view) -> {
Snackbar.make(view, "Create Event", Snackbar.LENGTH_LONG).setAnchorView(R.id.fab).setAction( Snackbar.make(view, "Create Event", Snackbar.LENGTH_LONG).setAnchorView(R.id.fab).setAction(
@ -139,6 +206,21 @@ public class EventListActivity extends AppCompatActivity {
updateList(); updateList();
} }
@Override
public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.event_list_toolbar_menu, menu);
/* Get some options */
SharedPreferences prefs = this.getApplicationContext().getSharedPreferences(Common.PREFS_NAME, MODE_PRIVATE);
EventOrder.Order order = EventOrder.orderFromString(prefs.getString(PREFS_UI_EVENT_ORDER, PREFS_UI_EVENT_ORDER_DEFAULT));
// Set item text based on sort mode
MenuItem item = menu.findItem(R.id.event_list_toolbar_menu_order);
item.setTitle(Objects.requireNonNull(item.getTitle()).toString() + ": " + getEventOrderDisplayString(order).toString());
return true;
}
@Override @Override
protected void onResume() { protected void onResume() {
super.onResume(); super.onResume();

View file

@ -18,11 +18,9 @@ package art.pegasko.yeeemp.ui.activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Intent; import android.content.Intent;
import android.os.AsyncTask;
import android.os.Bundle; import android.os.Bundle;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.util.Log;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -34,12 +32,10 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import java.util.concurrent.Executors; import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.ScheduledFuture;
import java.util.concurrent.TimeUnit;
import art.pegasko.yeeemp.R; import art.pegasko.yeeemp.R;
import art.pegasko.yeeemp.base.Event; import art.pegasko.yeeemp.base.Event;
import art.pegasko.yeeemp.base.EventOrder;
import art.pegasko.yeeemp.base.Queue; import art.pegasko.yeeemp.base.Queue;
import art.pegasko.yeeemp.base.Tag; import art.pegasko.yeeemp.base.Tag;
import art.pegasko.yeeemp.base.Wrapper; import art.pegasko.yeeemp.base.Wrapper;
@ -51,6 +47,8 @@ class EventRecyclerViewAdapter extends RecyclerView.Adapter<EventRecyclerViewAda
private final Queue queue; private final Queue queue;
private Event[] events; private Event[] events;
private EventOrder.Order order;
public EventRecyclerViewAdapter(Queue queue) { public EventRecyclerViewAdapter(Queue queue) {
super(); super();
this.queue = queue; this.queue = queue;
@ -155,10 +153,14 @@ class EventRecyclerViewAdapter extends RecyclerView.Adapter<EventRecyclerViewAda
public void reloadItems() { public void reloadItems() {
Handler handler = new Handler(Looper.getMainLooper()); Handler handler = new Handler(Looper.getMainLooper());
Executors.newSingleThreadExecutor().execute(() -> { Executors.newSingleThreadExecutor().execute(() -> {
this.events = this.queue.getEvents(); this.events = this.queue.getEvents(this.order);
handler.post(() -> { handler.post(() -> {
this.notifyDataSetChanged(); this.notifyDataSetChanged();
}); });
}); });
} }
public void setOrder(EventOrder.Order order) {
this.order = order;
}
} }

View file

@ -16,44 +16,37 @@
package art.pegasko.yeeemp.ui.activity; package art.pegasko.yeeemp.ui.activity;
import android.Manifest;
import android.app.Activity; import android.app.Activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.DialogInterface; import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.content.pm.PackageManager; import android.content.SharedPreferences;
import android.net.Uri; import android.net.Uri;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import com.google.android.material.snackbar.Snackbar; import com.google.android.material.snackbar.Snackbar;
import androidx.activity.result.ActivityResultLauncher; import androidx.annotation.Nullable;
import androidx.appcompat.app.AppCompatActivity; import androidx.appcompat.app.AppCompatActivity;
import android.os.Environment;
import android.util.Log; import android.util.Log;
import android.view.Menu; import android.view.Menu;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
import android.widget.Toast; import android.widget.Toast;
import androidx.core.app.ActivityCompat;
import androidx.core.content.ContextCompat;
import androidx.core.content.PermissionChecker;
import androidx.navigation.ui.AppBarConfiguration; import androidx.navigation.ui.AppBarConfiguration;
import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import java.io.File; import java.util.Objects;
import java.security.Permission;
import art.pegasko.yeeemp.base.Queue; import art.pegasko.yeeemp.base.Queue;
import art.pegasko.yeeemp.base.QueueOrder;
import art.pegasko.yeeemp.base.Wrapper; import art.pegasko.yeeemp.base.Wrapper;
import art.pegasko.yeeemp.databinding.ActivityQueueListBinding; import art.pegasko.yeeemp.databinding.ActivityQueueListBinding;
import art.pegasko.yeeemp.R; import art.pegasko.yeeemp.R;
import art.pegasko.yeeemp.impl.DBWrapper;
import art.pegasko.yeeemp.impl.DataUtils; import art.pegasko.yeeemp.impl.DataUtils;
import art.pegasko.yeeemp.impl.Init; import art.pegasko.yeeemp.impl.Init;
@ -63,6 +56,9 @@ public class QueueListActivity extends AppCompatActivity {
private static final int REQUEST_CODE_CREATE_FILE = 37; private static final int REQUEST_CODE_CREATE_FILE = 37;
private static final int REQUEST_CODE_OPEN_FILE = 19; private static final int REQUEST_CODE_OPEN_FILE = 19;
private static String PREFS_UI_QUEUE_ORDER = "ui-queue-order";
private static String PREFS_UI_QUEUE_ORDER_DEFAULT = "id";
private AppBarConfiguration appBarConfiguration; private AppBarConfiguration appBarConfiguration;
private ActivityQueueListBinding binding; private ActivityQueueListBinding binding;
private RecyclerView queueList; private RecyclerView queueList;
@ -74,6 +70,21 @@ public class QueueListActivity extends AppCompatActivity {
}); });
} }
private CharSequence getQueueOrderDisplayString(@Nullable QueueOrder.Order order) {
if (order == null) {
throw new RuntimeException("Order is null");
}
switch (order) {
case ID:
return "created";
case NAME:
return "name";
default:
throw new RuntimeException("Not implemented for " + order);
}
}
@Override @Override
protected void onCreate(Bundle savedInstanceState) { protected void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState); super.onCreate(savedInstanceState);
@ -86,9 +97,59 @@ public class QueueListActivity extends AppCompatActivity {
/* Toolbar menu */ /* Toolbar menu */
binding.toolbar.inflateMenu(R.menu.queue_list_toolbar_menu); binding.toolbar.inflateMenu(R.menu.queue_list_toolbar_menu);
// TODO: Better import / export / delete logic // TODO: Better import / export / delete logic
binding.toolbar.setOnMenuItemClickListener((MenuItem item) -> { binding.toolbar.setOnMenuItemClickListener((MenuItem item) -> {
if (item.getItemId() == R.id.queue_list_toolbar_menu_export) { if (item.getItemId() == R.id.queue_list_toolbar_menu_order) {
/* Get some options */
SharedPreferences prefs = this.getApplicationContext().getSharedPreferences(Common.PREFS_NAME, MODE_PRIVATE);
QueueOrder.Order order = QueueOrder.orderFromString(prefs.getString(PREFS_UI_QUEUE_ORDER, PREFS_UI_QUEUE_ORDER_DEFAULT));
/* Construct options */
QueueOrder.Order[] values = QueueOrder.Order.class.getEnumConstants();
if (values == null) {
// How did we get here?
throw new RuntimeException("QueueOrder.Order.class.getEnumConstants() is Empty");
}
CharSequence[] choices = new CharSequence[values.length];
int currentSelection = -1;
for (int i = 0; i < values.length; ++i) {
choices[i] = this.getQueueOrderDisplayString(values[i]);
if (values[i] == order) {
currentSelection = i;
}
}
/* Build dialog */
AlertDialog.Builder builder = new AlertDialog.Builder(QueueListActivity.this);
builder.setTitle("Order by ..");
builder.setSingleChoiceItems(
choices,
currentSelection,
(dialog, which) -> {
QueueOrder.Order newOrder = values[which];
// Save option
prefs.edit().putString(PREFS_UI_QUEUE_ORDER, QueueOrder.orderToString(newOrder)).apply();
// Apply
QueueListActivity.this.invalidateOptionsMenu();
queueListAdapter.setOrder(newOrder);
runOnUiThread(() -> {
queueListAdapter.reloadItems();
});
// Close
dialog.dismiss();
}
);
builder.show();
return true;
} if (item.getItemId() == R.id.queue_list_toolbar_menu_export) {
Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT); Intent intent = new Intent(Intent.ACTION_CREATE_DOCUMENT);
intent.setType("*/*"); intent.setType("*/*");
intent.putExtra(Intent.EXTRA_TITLE, "export_" + DataUtils.formatTs(System.currentTimeMillis()) + ".db"); intent.putExtra(Intent.EXTRA_TITLE, "export_" + DataUtils.formatTs(System.currentTimeMillis()) + ".db");
@ -140,8 +201,13 @@ public class QueueListActivity extends AppCompatActivity {
return false; return false;
}); });
/* Get some options */
SharedPreferences prefs = this.getApplicationContext().getSharedPreferences(Common.PREFS_NAME, MODE_PRIVATE);
QueueOrder.Order order = QueueOrder.orderFromString(prefs.getString(PREFS_UI_QUEUE_ORDER, PREFS_UI_QUEUE_ORDER_DEFAULT));
/* Queue list */ /* Queue list */
queueListAdapter = new QueueRecyclerViewAdapter(); queueListAdapter = new QueueRecyclerViewAdapter();
queueListAdapter.setOrder(order);
queueList = findViewById(R.id.content_queue_list__list); queueList = findViewById(R.id.content_queue_list__list);
queueList.setLayoutManager(new LinearLayoutManager(this)); queueList.setLayoutManager(new LinearLayoutManager(this));
queueList.setAdapter(queueListAdapter); queueList.setAdapter(queueListAdapter);
@ -275,6 +341,16 @@ public class QueueListActivity extends AppCompatActivity {
@Override @Override
public boolean onCreateOptionsMenu(Menu menu) { public boolean onCreateOptionsMenu(Menu menu) {
getMenuInflater().inflate(R.menu.queue_list_toolbar_menu, menu); getMenuInflater().inflate(R.menu.queue_list_toolbar_menu, menu);
/* Get some options */
SharedPreferences prefs = this.getApplicationContext().getSharedPreferences(Common.PREFS_NAME, MODE_PRIVATE);
QueueOrder.Order order = QueueOrder.orderFromString(prefs.getString(PREFS_UI_QUEUE_ORDER, PREFS_UI_QUEUE_ORDER_DEFAULT));
// Set item text based on sort mode
MenuItem item = menu.findItem(R.id.queue_list_toolbar_menu_order);
item.setTitle(Objects.requireNonNull(item.getTitle()).toString() + ": " + getQueueOrderDisplayString(order).toString());
return true; return true;
} }

View file

@ -17,16 +17,9 @@
package art.pegasko.yeeemp.ui.activity; package art.pegasko.yeeemp.ui.activity;
import android.app.AlertDialog; import android.app.AlertDialog;
import android.content.Context;
import android.content.DialogInterface;
import android.content.Intent; import android.content.Intent;
import android.os.Build;
import android.os.Bundle; import android.os.Bundle;
import android.os.VibrationEffect;
import android.os.Vibrator;
import android.text.InputType; import android.text.InputType;
import android.util.Log;
import android.view.HapticFeedbackConstants;
import android.view.LayoutInflater; import android.view.LayoutInflater;
import android.view.MenuItem; import android.view.MenuItem;
import android.view.View; import android.view.View;
@ -38,9 +31,8 @@ import androidx.annotation.NonNull;
import androidx.recyclerview.widget.RecyclerView; import androidx.recyclerview.widget.RecyclerView;
import art.pegasko.yeeemp.R; import art.pegasko.yeeemp.R;
import art.pegasko.yeeemp.base.Event;
import art.pegasko.yeeemp.base.Queue; import art.pegasko.yeeemp.base.Queue;
import art.pegasko.yeeemp.base.Tag; import art.pegasko.yeeemp.base.QueueOrder;
import art.pegasko.yeeemp.base.Wrapper; import art.pegasko.yeeemp.base.Wrapper;
import art.pegasko.yeeemp.databinding.QueueListItemBinding; import art.pegasko.yeeemp.databinding.QueueListItemBinding;
@ -49,6 +41,8 @@ class QueueRecyclerViewAdapter extends RecyclerView.Adapter<QueueRecyclerViewAda
private Queue[] queues; private Queue[] queues;
private QueueOrder.Order order;
@NonNull @NonNull
@Override @Override
public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) { public ViewHolder onCreateViewHolder(@NonNull ViewGroup viewGroup, int viewType) {
@ -150,7 +144,11 @@ class QueueRecyclerViewAdapter extends RecyclerView.Adapter<QueueRecyclerViewAda
} }
public void reloadItems() { public void reloadItems() {
this.queues = Wrapper.getQueueMaker().list(); this.queues = Wrapper.getQueueMaker().list(this.order);
this.notifyDataSetChanged(); this.notifyDataSetChanged();
} }
public void setOrder(QueueOrder.Order order) {
this.order = order;
}
} }

View file

@ -0,0 +1,6 @@
<?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/event_list_toolbar_menu_order"
android:title="Order by" />
</menu>

View file

@ -1,5 +1,9 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<menu xmlns:android="http://schemas.android.com/apk/res/android"> <menu xmlns:android="http://schemas.android.com/apk/res/android">
<item
android:id="@+id/queue_list_toolbar_menu_order"
android:title="Order by" />
<item <item
android:id="@+id/queue_list_toolbar_menu_export" android:id="@+id/queue_list_toolbar_menu_export"
android:title="Export" /> android:title="Export" />

View file

@ -1,46 +1,3 @@
<resources> <resources>
<string name="app_name">Yeeemp</string> <string name="app_name">Yeeemp</string>
<!-- Strings used for fragments for navigation -->
<string name="first_fragment_label">First Fragment</string>
<string name="second_fragment_label">Second Fragment</string>
<string name="next">Next</string>
<string name="previous">Previous</string>
<string name="lorem_ipsum">
Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam in scelerisque sem. Mauris
volutpat, dolor id interdum ullamcorper, risus dolor egestas lectus, sit amet mattis purus
dui nec risus. Maecenas non sodales nisi, vel dictum dolor. Class aptent taciti sociosqu ad
litora torquent per conubia nostra, per inceptos himenaeos. Suspendisse blandit eleifend
diam, vel rutrum tellus vulputate quis. Aliquam eget libero aliquet, imperdiet nisl a,
ornare ex. Sed rhoncus est ut libero porta lobortis. Fusce in dictum tellus.\n\n
Suspendisse interdum ornare ante. Aliquam nec cursus lorem. Morbi id magna felis. Vivamus
egestas, est a condimentum egestas, turpis nisl iaculis ipsum, in dictum tellus dolor sed
neque. Morbi tellus erat, dapibus ut sem a, iaculis tincidunt dui. Interdum et malesuada
fames ac ante ipsum primis in faucibus. Curabitur et eros porttitor, ultricies urna vitae,
molestie nibh. Phasellus at commodo eros, non aliquet metus. Sed maximus nisl nec dolor
bibendum, vel congue leo egestas.\n\n
Sed interdum tortor nibh, in sagittis risus mollis quis. Curabitur mi odio, condimentum sit
amet auctor at, mollis non turpis. Nullam pretium libero vestibulum, finibus orci vel,
molestie quam. Fusce blandit tincidunt nulla, quis sollicitudin libero facilisis et. Integer
interdum nunc ligula, et fermentum metus hendrerit id. Vestibulum lectus felis, dictum at
lacinia sit amet, tristique id quam. Cras eu consequat dui. Suspendisse sodales nunc ligula,
in lobortis sem porta sed. Integer id ultrices magna, in luctus elit. Sed a pellentesque
est.\n\n
Aenean nunc velit, lacinia sed dolor sed, ultrices viverra nulla. Etiam a venenatis nibh.
Morbi laoreet, tortor sed facilisis varius, nibh orci rhoncus nulla, id elementum leo dui
non lorem. Nam mollis ipsum quis auctor varius. Quisque elementum eu libero sed commodo. In
eros nisl, imperdiet vel imperdiet et, scelerisque a mauris. Pellentesque varius ex nunc,
quis imperdiet eros placerat ac. Duis finibus orci et est auctor tincidunt. Sed non viverra
ipsum. Nunc quis augue egestas, cursus lorem at, molestie sem. Morbi a consectetur ipsum, a
placerat diam. Etiam vulputate dignissim convallis. Integer faucibus mauris sit amet finibus
convallis.\n\n
Phasellus in aliquet mi. Pellentesque habitant morbi tristique senectus et netus et
malesuada fames ac turpis egestas. In volutpat arcu ut felis sagittis, in finibus massa
gravida. Pellentesque id tellus orci. Integer dictum, lorem sed efficitur ullamcorper,
libero justo consectetur ipsum, in mollis nisl ex sed nisl. Donec maximus ullamcorper
sodales. Praesent bibendum rhoncus tellus nec feugiat. In a ornare nulla. Donec rhoncus
libero vel nunc consequat, quis tincidunt nisl eleifend. Cras bibendum enim a justo luctus
vestibulum. Fusce dictum libero quis erat maximus, vitae volutpat diam dignissim.
</string>
<string name="title_activity_main">MainActivity</string>
</resources> </resources>