diff --git a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/base/EventOrder.java b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/base/EventOrder.java new file mode 100644 index 0000000..c888423 --- /dev/null +++ b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/base/EventOrder.java @@ -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, + } +} diff --git a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/base/Queue.java b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/base/Queue.java index f7af19e..7200f58 100644 --- a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/base/Queue.java +++ b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/base/Queue.java @@ -23,7 +23,7 @@ public interface Queue { void setName(String name); - Event[] getEvents(); + Event[] getEvents(EventOrder.Order order); int getEventCount(); diff --git a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/base/QueueMaker.java b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/base/QueueMaker.java index 0c826cb..0068d6f 100644 --- a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/base/QueueMaker.java +++ b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/base/QueueMaker.java @@ -23,5 +23,5 @@ public interface QueueMaker { void delete(Queue queue); - Queue[] list(); + Queue[] list(QueueOrder.Order order); } diff --git a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/base/QueueOrder.java b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/base/QueueOrder.java new file mode 100644 index 0000000..99b7917 --- /dev/null +++ b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/base/QueueOrder.java @@ -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, + } +} diff --git a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/EventImpl.java b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/EventImpl.java index 400dbb0..ee45133 100644 --- a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/EventImpl.java +++ b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/EventImpl.java @@ -57,11 +57,7 @@ public class EventImpl implements Event { null ); - if (Utils.findResult(cursor)) { - return cursor.getLong(0); - } - - return 0; + return Utils.getLongAndClose(cursor, 0); } } @@ -70,7 +66,13 @@ public class EventImpl implements Event { synchronized (this.db) { ContentValues cv = new ContentValues(); 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 ); - if (Utils.findResult(cursor)) { - return cursor.getString(0); - } - - return null; + return Utils.getStringAndClose(cursor, null); } } @@ -100,7 +98,13 @@ public class EventImpl implements Event { synchronized (this.db) { ContentValues cv = new ContentValues(); 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) { synchronized (this.db) { - Cursor cursor = db.query("event_tag", new String[] { "1" }, "event_id = ? AND tag_id = ?", new String[] { - Integer.toString(this.getId()), Integer.toString(tag.getId()) - }, null, null, null); + Cursor cursor = db.query( + "event_tag", + new String[] { "1" }, + "event_id = ? AND tag_id = ?", + new String[] { + Integer.toString(this.getId()), Integer.toString(tag.getId()) + }, + null, + null, + null + ); - return Utils.findResult(cursor); + return Utils.findResultAndClose(cursor); } } @@ -195,6 +207,7 @@ public class EventImpl implements Event { while (cursor.moveToNext()) { tags[index++] = new TagImpl(this.db, cursor.getInt(0)); } + cursor.close(); return this.cachedTags = tags; } diff --git a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/EventMakerImpl.java b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/EventMakerImpl.java index c609d7f..c831087 100644 --- a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/EventMakerImpl.java +++ b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/EventMakerImpl.java @@ -49,7 +49,9 @@ public class EventMakerImpl implements EventMaker { null ); - if (Utils.findResult(cursor)) return new EventImpl(this.db, id); + if (Utils.findResultAndClose(cursor)) { + return new EventImpl(this.db, id); + } } catch (SQLiteException e) { Log.wtf(TAG, e); diff --git a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/QueueImpl.java b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/QueueImpl.java index 058b1c3..ac26970 100644 --- a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/QueueImpl.java +++ b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/QueueImpl.java @@ -25,10 +25,9 @@ import android.util.Log; import androidx.annotation.NonNull; import art.pegasko.yeeemp.base.Event; +import art.pegasko.yeeemp.base.EventOrder; import art.pegasko.yeeemp.base.Queue; -import art.pegasko.yeeemp.base.Tag; import art.pegasko.yeeemp.base.TagStat; -import kotlin.NotImplementedError; public class QueueImpl implements Queue { public static final String TAG = QueueImpl.class.getSimpleName(); @@ -59,11 +58,7 @@ public class QueueImpl implements Queue { null ); - if (Utils.findResult(cursor)) { - return cursor.getString(0); - } - - return null; + return Utils.getStringAndClose(cursor, null); } } @@ -72,22 +67,63 @@ public class QueueImpl implements Queue { synchronized (this.db) { ContentValues cv = new ContentValues(); 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 - public Event[] getEvents() { + public Event[] getEvents(EventOrder.Order order) { synchronized (this.db) { - Cursor cursor = db.query( - "queue_event", - new String[] { "event_id" }, - "queue_id = ?", - new String[] { Integer.toString(this.getId()) }, - null, - null, - "event_id desc" - ); + 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", + new String[] { "event_id" }, + "queue_id = ?", + new String[] { Integer.toString(this.getId()) }, + null, + null, + ( order == EventOrder.Order.ID_ASC ? + "event_id asc" : + ( order == EventOrder.Order.ID_DESC ? + "event_id desc" : + null + )) + ); + } if (cursor == null) { return new Event[0]; @@ -99,6 +135,7 @@ public class QueueImpl implements Queue { while (cursor.moveToNext()) { events[index++] = new EventImpl(this.db, cursor.getInt(0)); } + cursor.close(); return events; } @@ -117,9 +154,7 @@ public class QueueImpl implements Queue { null ); - if (!Utils.findResult(cursor)) return 0; - - return cursor.getInt(0); + return Utils.getIntAndClose(cursor, 0); } } @@ -138,7 +173,7 @@ public class QueueImpl implements Queue { null ); - return Utils.findResult(cursor); + return Utils.findResultAndClose(cursor); } } @@ -183,7 +218,38 @@ public class QueueImpl implements Queue { public TagStat[] getGlobalTags() { synchronized (this.db) { 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()) } ); diff --git a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/QueueMakerImpl.java b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/QueueMakerImpl.java index 8d4c5e4..858997f 100644 --- a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/QueueMakerImpl.java +++ b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/QueueMakerImpl.java @@ -23,9 +23,10 @@ import android.database.sqlite.SQLiteException; import android.util.Log; 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.QueueMaker; +import art.pegasko.yeeemp.base.QueueOrder; public class QueueMakerImpl implements QueueMaker { public static final String TAG = EventMakerImpl.class.getSimpleName(); @@ -40,16 +41,19 @@ public class QueueMakerImpl implements QueueMaker { public Queue getById(int id) { synchronized (this.db) { try { - Cursor cursor = db.query("queue", - new String[] { "1" }, - "id = ?", - new String[] { Integer.toString(id) }, - null, - null, - null + Cursor cursor = db.query( + "queue", + new String[] { "1" }, + "id = ?", + new String[] { Integer.toString(id) }, + null, + 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) { Log.wtf(TAG, e); @@ -76,9 +80,22 @@ public class QueueMakerImpl implements QueueMaker { } @Override - public Queue[] list() { + public Queue[] list(QueueOrder.Order order) { 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) { return new Queue[0]; @@ -90,6 +107,7 @@ public class QueueMakerImpl implements QueueMaker { while (cursor.moveToNext()) { queues[index++] = new QueueImpl(this.db, cursor.getInt(0)); } + cursor.close(); return queues; } @@ -101,7 +119,8 @@ public class QueueMakerImpl implements QueueMaker { // Drop events 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()) }); } } catch (SQLiteException e) { diff --git a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/TagImpl.java b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/TagImpl.java index d85efbc..4a2f043 100644 --- a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/TagImpl.java +++ b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/TagImpl.java @@ -57,8 +57,9 @@ public class TagImpl implements Tag { null ); - if (Utils.findResult(cursor)) { - return this._cached_name = cursor.getString(0); + String result = Utils.getStringAndClose(cursor, null); + if (result != null) { + return this._cached_name = result; } return null; diff --git a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/TagMakerImpl.java b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/TagMakerImpl.java index ce1da8a..34bb5ba 100644 --- a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/TagMakerImpl.java +++ b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/TagMakerImpl.java @@ -47,10 +47,12 @@ public class TagMakerImpl implements TagMaker { null, null ); - if (Utils.findResult(cursor)) { + + int id = Utils.getIntAndClose(cursor, -1); + if (id != -1) { return new TagImpl( db, - cursor.getInt(0) + id ); } } catch (SQLiteException e) { diff --git a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/Utils.java b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/Utils.java index be2244c..a897b9e 100644 --- a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/Utils.java +++ b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/impl/Utils.java @@ -20,9 +20,85 @@ import android.database.Cursor; import android.database.sqlite.SQLiteDatabase; public class Utils { - public static boolean findResult(Cursor cursor) { - if (cursor == null) return false; - cursor.moveToFirst(); - return cursor.getCount() != 0; + public static boolean findResultAndClose(Cursor cursor) { + if (cursor == null) + return false; + + 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; } } diff --git a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/Common.java b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/Common.java new file mode 100644 index 0000000..55025c3 --- /dev/null +++ b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/Common.java @@ -0,0 +1,5 @@ +package art.pegasko.yeeemp.ui.activity; + +public class Common { + public static final String PREFS_NAME = "preferences"; +} diff --git a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/EventListActivity.java b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/EventListActivity.java index 1ad678c..68f6df8 100644 --- a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/EventListActivity.java +++ b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/EventListActivity.java @@ -16,25 +16,27 @@ package art.pegasko.yeeemp.ui.activity; +import android.app.AlertDialog; +import android.content.DialogInterface; import android.content.Intent; +import android.content.SharedPreferences; import android.os.Bundle; import android.util.Log; +import android.view.Menu; import android.view.MenuItem; import android.view.View; -import androidx.annotation.NonNull; +import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import androidx.recyclerview.widget.ItemTouchHelper; import androidx.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; import com.google.android.material.snackbar.Snackbar; -import java.time.Duration; -import java.util.concurrent.ScheduledFuture; +import java.util.Objects; 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.Wrapper; import art.pegasko.yeeemp.databinding.ActivityEventListBinding; @@ -43,6 +45,9 @@ import art.pegasko.yeeemp.impl.Init; public class EventListActivity extends AppCompatActivity { 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 RecyclerView eventList; 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 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -82,6 +106,7 @@ public class EventListActivity extends AppCompatActivity { return; } + /* Bindings */ binding = ActivityEventListBinding.inflate(getLayoutInflater()); setContentView(binding.getRoot()); setSupportActionBar(binding.toolbar); @@ -89,31 +114,73 @@ public class EventListActivity extends AppCompatActivity { getSupportActionBar().setDisplayHomeAsUpEnabled(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 */ eventListAdapter = new EventRecyclerViewAdapter(queue); + eventListAdapter.setOrder(order); eventList = findViewById(R.id.content_event_list__list); eventList.setLayoutManager(new LinearLayoutManager(this)); 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 */ binding.fab.setOnLongClickListener((View view) -> { Snackbar.make(view, "Create Event", Snackbar.LENGTH_LONG).setAnchorView(R.id.fab).setAction( @@ -139,6 +206,21 @@ public class EventListActivity extends AppCompatActivity { 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 protected void onResume() { super.onResume(); diff --git a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/EventRecyclerViewAdapter.java b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/EventRecyclerViewAdapter.java index 1868f51..3808cf2 100644 --- a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/EventRecyclerViewAdapter.java +++ b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/EventRecyclerViewAdapter.java @@ -18,11 +18,9 @@ package art.pegasko.yeeemp.ui.activity; import android.app.AlertDialog; import android.content.Intent; -import android.os.AsyncTask; import android.os.Bundle; import android.os.Handler; import android.os.Looper; -import android.util.Log; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -34,12 +32,10 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; 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.base.Event; +import art.pegasko.yeeemp.base.EventOrder; import art.pegasko.yeeemp.base.Queue; import art.pegasko.yeeemp.base.Tag; import art.pegasko.yeeemp.base.Wrapper; @@ -51,6 +47,8 @@ class EventRecyclerViewAdapter extends RecyclerView.Adapter { - this.events = this.queue.getEvents(); + this.events = this.queue.getEvents(this.order); handler.post(() -> { this.notifyDataSetChanged(); }); }); } + + public void setOrder(EventOrder.Order order) { + this.order = order; + } } diff --git a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/QueueListActivity.java b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/QueueListActivity.java index 004851d..3d516cd 100644 --- a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/QueueListActivity.java +++ b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/QueueListActivity.java @@ -16,44 +16,37 @@ package art.pegasko.yeeemp.ui.activity; -import android.Manifest; import android.app.Activity; import android.app.AlertDialog; import android.content.DialogInterface; import android.content.Intent; -import android.content.pm.PackageManager; +import android.content.SharedPreferences; import android.net.Uri; -import android.os.Build; import android.os.Bundle; import com.google.android.material.snackbar.Snackbar; -import androidx.activity.result.ActivityResultLauncher; +import androidx.annotation.Nullable; import androidx.appcompat.app.AppCompatActivity; -import android.os.Environment; import android.util.Log; import android.view.Menu; import android.view.MenuItem; import android.view.View; 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.recyclerview.widget.LinearLayoutManager; import androidx.recyclerview.widget.RecyclerView; -import java.io.File; -import java.security.Permission; +import java.util.Objects; import art.pegasko.yeeemp.base.Queue; +import art.pegasko.yeeemp.base.QueueOrder; import art.pegasko.yeeemp.base.Wrapper; import art.pegasko.yeeemp.databinding.ActivityQueueListBinding; import art.pegasko.yeeemp.R; -import art.pegasko.yeeemp.impl.DBWrapper; import art.pegasko.yeeemp.impl.DataUtils; 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_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 ActivityQueueListBinding binding; 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 protected void onCreate(Bundle savedInstanceState) { super.onCreate(savedInstanceState); @@ -86,9 +97,59 @@ public class QueueListActivity extends AppCompatActivity { /* Toolbar menu */ binding.toolbar.inflateMenu(R.menu.queue_list_toolbar_menu); + // TODO: Better import / export / delete logic 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.setType("*/*"); intent.putExtra(Intent.EXTRA_TITLE, "export_" + DataUtils.formatTs(System.currentTimeMillis()) + ".db"); @@ -140,8 +201,13 @@ public class QueueListActivity extends AppCompatActivity { 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 */ queueListAdapter = new QueueRecyclerViewAdapter(); + queueListAdapter.setOrder(order); queueList = findViewById(R.id.content_queue_list__list); queueList.setLayoutManager(new LinearLayoutManager(this)); queueList.setAdapter(queueListAdapter); @@ -275,6 +341,16 @@ public class QueueListActivity extends AppCompatActivity { @Override public boolean onCreateOptionsMenu(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; } diff --git a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/QueueRecyclerViewAdapter.java b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/QueueRecyclerViewAdapter.java index 352ec74..b581e1e 100644 --- a/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/QueueRecyclerViewAdapter.java +++ b/Yeeemp/app/src/main/java/art/pegasko/yeeemp/ui/activity/QueueRecyclerViewAdapter.java @@ -17,16 +17,9 @@ package art.pegasko.yeeemp.ui.activity; import android.app.AlertDialog; -import android.content.Context; -import android.content.DialogInterface; import android.content.Intent; -import android.os.Build; import android.os.Bundle; -import android.os.VibrationEffect; -import android.os.Vibrator; import android.text.InputType; -import android.util.Log; -import android.view.HapticFeedbackConstants; import android.view.LayoutInflater; import android.view.MenuItem; import android.view.View; @@ -38,9 +31,8 @@ import androidx.annotation.NonNull; import androidx.recyclerview.widget.RecyclerView; import art.pegasko.yeeemp.R; -import art.pegasko.yeeemp.base.Event; 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.databinding.QueueListItemBinding; @@ -49,6 +41,8 @@ class QueueRecyclerViewAdapter extends RecyclerView.Adapter + + + \ No newline at end of file diff --git a/Yeeemp/app/src/main/res/menu/queue_list_toolbar_menu.xml b/Yeeemp/app/src/main/res/menu/queue_list_toolbar_menu.xml index d6daecc..715cdf2 100644 --- a/Yeeemp/app/src/main/res/menu/queue_list_toolbar_menu.xml +++ b/Yeeemp/app/src/main/res/menu/queue_list_toolbar_menu.xml @@ -1,5 +1,9 @@ + + diff --git a/Yeeemp/app/src/main/res/values/strings.xml b/Yeeemp/app/src/main/res/values/strings.xml index 2183e6a..615f5a6 100644 --- a/Yeeemp/app/src/main/res/values/strings.xml +++ b/Yeeemp/app/src/main/res/values/strings.xml @@ -1,46 +1,3 @@ Yeeemp - - First Fragment - Second Fragment - Next - Previous - - - 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. - - MainActivity \ No newline at end of file