Minimal working prototype

This commit is contained in:
pegasko 2024-06-11 01:03:10 +03:00
parent de2bdf4b91
commit 5f31ce23d0
29 changed files with 668 additions and 451 deletions

View file

@ -1,82 +0,0 @@
/**
* 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;
import android.database.sqlite.SQLiteDatabase;
import android.provider.CalendarContract;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import art.pegasko.yeeemp.base.Event;
import art.pegasko.yeeemp.base.Queue;
import art.pegasko.yeeemp.base.Tag;
import art.pegasko.yeeemp.impl.QueueMakerImpl;
import art.pegasko.yeeemp.impl.TagMakerImpl;
@RunWith(AndroidJUnit4.class)
public class DBImplTest {
private SQLiteDatabase db;
@Before
public void setUp() {
db = SQLiteDatabase.create(null);
// db = SQLiteDatabase.openDatabase("test.db", SQLiteDatabase.OpenParams);
}
@After
public void tearDown() {
db.close();
}
@Test
public void testQueueMakerImpl() {
SQLiteDatabase db = SQLiteDatabase.create(null);
QueueMakerImpl queueMaker = new QueueMakerImpl(db);
Queue q = queueMaker.create();
// check initial ID
assert q.getId() == 0;
// check null name
assert q.getName() == null;
// check no events
Event[] events = q.getEvents();
assert events != null;
assert events.length == 0;
// check no tags
Tag[] tags = q.getGlobalTags();
assert tags != null;
assert tags.length == 0;
// Check name set
q.setName("aboba");
assert q.getName() == "aboba";
}
@Test
public void testTagMakerImpl() {
}
}

View file

@ -1,42 +0,0 @@
/**
* 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;
import android.content.Context;
import androidx.test.platform.app.InstrumentationRegistry;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
import static org.junit.Assert.*;
/**
* Instrumented test, which will execute on an Android device.
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
@RunWith(AndroidJUnit4.class)
public class ExampleInstrumentedTest {
@Test
public void useAppContext() {
// Context of the app under test.
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
assertEquals("art.pegasko.yeeemp", appContext.getPackageName());
}
}

View file

@ -18,12 +18,20 @@ package art.pegasko.yeeemp.base;
public interface Event {
int getId();
long getTimestamp();
void setTimestamp(long timestamp);
String getComment();
void setComment(String comment);
void addTag(Tag tag);
void removeTag(Tag tag);
void removeTags();
Tag[] getTags();
}

View file

@ -18,6 +18,8 @@ package art.pegasko.yeeemp.base;
public interface EventMaker {
Event create();
void delete(Event event);
Event getById(int id);
}

View file

@ -18,11 +18,18 @@ package art.pegasko.yeeemp.base;
public interface Queue {
int getId();
String getName();
void setName(String name);
Event[] getEvents();
int getEventCount();
void addEvent(Event event);
void removeEvent(Event event);
Tag[] getGlobalTags();
TagStat[] getGlobalTags();
}

View file

@ -18,7 +18,10 @@ package art.pegasko.yeeemp.base;
public interface QueueMaker {
Queue getById(int id);
Queue create();
void delete(Queue queue);
Queue[] list();
}

View file

@ -18,5 +18,6 @@ package art.pegasko.yeeemp.base;
public interface Tag {
int getId();
String getName();
}

View file

@ -17,6 +17,5 @@
package art.pegasko.yeeemp.base;
public interface TagMaker {
/** Get or Create distinct name Tag in Queue */
Tag getOrCreateInQueue(Queue queue, String name);
}

View file

@ -40,6 +40,8 @@ public abstract class Wrapper {
}
public abstract QueueMaker queueMaker();
public abstract EventMaker eventMaker();
public abstract TagMaker tagMaker();
}

View file

@ -129,7 +129,7 @@ public class DBWrapper extends Wrapper {
try {
new File(context.getFilesDir(), DB_PATH).delete();
} catch (Exception e) {
Log.wtf(DBWrapper.TAG, e);
Log.wtf(TAG, e);
}
}

View file

@ -46,14 +46,8 @@ public class EventImpl implements Event {
@Override
public long getTimestamp() {
synchronized (this.db) {
Cursor cursor = db.query(
"event",
new String[] { "timestamp" },
"id = ?",
new String[] { Integer.toString(this.getId()) },
null,
null,
null
Cursor cursor = db.query("event", new String[] { "timestamp" }, "id = ?",
new String[] { Integer.toString(this.getId()) }, null, null, null
);
if (Utils.findResult(cursor)) {
@ -69,26 +63,15 @@ 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()) });
}
}
@Override
public String getComment() {
synchronized (this.db) {
Cursor cursor = db.query(
"event",
new String[] { "comment" },
"id = ?",
new String[] { Integer.toString(this.getId()) },
null,
null,
null
Cursor cursor = db.query("event", new String[] { "comment" }, "id = ?",
new String[] { Integer.toString(this.getId()) }, null, null, null
);
if (Utils.findResult(cursor)) {
@ -104,27 +87,18 @@ 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()) });
}
}
/** !synchronized */
/**
* !synchronized
*/
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);
}
@ -133,11 +107,9 @@ public class EventImpl implements Event {
@Override
public void addTag(Tag tag) {
synchronized (this.db) {
if (tag == null)
return;
if (tag == null) return;
if (this.hasTag(tag))
return;
if (this.hasTag(tag)) return;
try {
ContentValues cv = new ContentValues();
@ -153,18 +125,14 @@ public class EventImpl implements Event {
@Override
public void removeTag(Tag tag) {
synchronized (this.db) {
if (tag == null)
return;
if (tag == null) return;
if (!this.hasTag(tag))
return;
if (!this.hasTag(tag)) return;
try {
db.delete(
"event_tag",
"event_id = ? AND tag_id = ?",
new String[] { Integer.toString(this.getId()), Integer.toString(tag.getId()) }
);
db.delete("event_tag", "event_id = ? AND tag_id = ?", new String[] {
Integer.toString(this.getId()), Integer.toString(tag.getId())
});
} catch (SQLiteException e) {
Log.wtf(TAG, e);
}
@ -175,11 +143,7 @@ public class EventImpl implements Event {
public void removeTags() {
synchronized (this.db) {
try {
db.delete(
"event_tag",
"event_id = ?",
new String[] { Integer.toString(this.getId()) }
);
db.delete("event_tag", "event_id = ?", new String[] { Integer.toString(this.getId()) });
} catch (SQLiteException e) {
Log.wtf(TAG, e);
}
@ -189,14 +153,8 @@ public class EventImpl implements Event {
@Override
public Tag[] getTags() {
synchronized (this.db) {
Cursor cursor = db.query(
"event_tag",
new String[] { "tag_id" },
"event_id = ?",
new String[] { Integer.toString(this.getId()) },
null,
null,
null
Cursor cursor = db.query("event_tag", new String[] { "tag_id" }, "event_id = ?",
new String[] { Integer.toString(this.getId()) }, null, null, "tag_id desc"
);
if (cursor == null) {
@ -207,10 +165,7 @@ public class EventImpl implements Event {
int index = 0;
while (cursor.moveToNext()) {
tags[index++] = new TagImpl(
this.db,
cursor.getInt(0)
);
tags[index++] = new TagImpl(this.db, cursor.getInt(0));
}
return tags;

View file

@ -49,8 +49,7 @@ public class EventMakerImpl implements EventMaker {
null
);
if (Utils.findResult(cursor))
return new EventImpl(this.db, id);
if (Utils.findResult(cursor)) return new EventImpl(this.db, id);
} catch (SQLiteException e) {
Log.wtf(TAG, e);
@ -67,10 +66,7 @@ public class EventMakerImpl implements EventMaker {
ContentValues cv = new ContentValues();
cv.put("id", (Integer) null);
long rowId = db.insertOrThrow("event", null, cv);
return new EventImpl(
this.db,
(int) rowId
);
return new EventImpl(this.db, (int) rowId);
} catch (SQLiteException e) {
Log.wtf(TAG, e);
}
@ -84,25 +80,13 @@ public class EventMakerImpl implements EventMaker {
synchronized (this.db) {
try {
// Delete queue <-> event
db.delete(
"queue_event",
"event_id = ?",
new String[] { Integer.toString(event.getId()) }
);
db.delete("queue_event", "event_id = ?", new String[] { Integer.toString(event.getId()) });
// Delete event <-> tag
db.delete(
"event_tag",
"event_id = ?",
new String[] { Integer.toString(event.getId()) }
);
db.delete("event_tag", "event_id = ?", new String[] { Integer.toString(event.getId()) });
// Delete event
db.delete(
"event",
"id = ?",
new String[] { Integer.toString(event.getId()) }
);
db.delete("event", "id = ?", new String[] { Integer.toString(event.getId()) });
} catch (SQLiteException e) {
Log.wtf(TAG, e);
}

View file

@ -27,6 +27,7 @@ import androidx.annotation.NonNull;
import art.pegasko.yeeemp.base.Event;
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 {
@ -131,7 +132,9 @@ public class QueueImpl implements Queue {
}
}
/** !synchronized */
/**
* !synchronized
*/
protected boolean hasEvent(Event event) {
synchronized (this.db) {
Cursor cursor = db.query(
@ -190,30 +193,60 @@ public class QueueImpl implements Queue {
}
@Override
public Tag[] getGlobalTags() {
public TagStat[] getGlobalTags() {
synchronized (this.db) {
Cursor cursor = db.query(
"tag",
new String[] { "id" },
"queue_id = ?",
new String[] { Integer.toString(this.getId()) },
null,
null,
null
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",
new String[] { Integer.toString(this.getId()) }
);
if (cursor == null) {
return new Tag[0];
return new TagStat[0];
}
Tag[] tags = new Tag[cursor.getCount()];
TagStat[] tags = new TagStat[cursor.getCount()];
int index = 0;
while (cursor.moveToNext()) {
tags[index++] = new TagImpl(
TagStat tagStat = new TagStat();
tags[index++] = tagStat;
tagStat.tag = new TagImpl(
this.db,
cursor.getInt(0)
);
tagStat.count = cursor.getInt(1);
}
return tags;

View file

@ -50,11 +50,16 @@ public class QueueMakerImpl implements QueueMaker {
null
);
if (Utils.findResult(cursor))
return new QueueImpl(this.db, id);
if (Utils.findResult(cursor)) return new QueueImpl(
this.db,
id
);
} catch (SQLiteException e) {
Log.wtf(TAG, e);
Log.wtf(
TAG,
e
);
}
return null;
@ -66,14 +71,24 @@ public class QueueMakerImpl implements QueueMaker {
synchronized (this.db) {
try {
ContentValues cv = new ContentValues();
cv.put("id", (Integer) null);
long rowId = db.insertOrThrow("queue", null, cv);
cv.put(
"id",
(Integer) null
);
long rowId = db.insertOrThrow(
"queue",
null,
cv
);
return new QueueImpl(
this.db,
(int) rowId
);
} catch (SQLiteException e) {
Log.wtf(TAG, e);
Log.wtf(
TAG,
e
);
}
return null;
@ -117,7 +132,7 @@ public class QueueMakerImpl implements QueueMaker {
// Drop events
try {
for (Event event: queue.getEvents()) {
for (Event event : queue.getEvents()) {
db.delete(
"event",
"id = ?",
@ -125,7 +140,10 @@ public class QueueMakerImpl implements QueueMaker {
);
}
} catch (SQLiteException e) {
Log.wtf(TAG, e);
Log.wtf(
TAG,
e
);
return;
}
@ -137,7 +155,10 @@ public class QueueMakerImpl implements QueueMaker {
new String[] { Integer.toString(queue.getId()) }
);
} catch (SQLiteException e) {
Log.wtf(TAG, e);
Log.wtf(
TAG,
e
);
}
// Drop queue
@ -148,7 +169,10 @@ public class QueueMakerImpl implements QueueMaker {
new String[] { Integer.toString(queue.getId()) }
);
} catch (SQLiteException e) {
Log.wtf(TAG, e);
Log.wtf(
TAG,
e
);
}
}
}

View file

@ -21,8 +21,7 @@ import android.database.sqlite.SQLiteDatabase;
public class Utils {
public static boolean findResult(Cursor cursor) {
if (cursor == null)
return false;
if (cursor == null) return false;
cursor.moveToFirst();
return cursor.getCount() != 0;
}

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package art.pegasko.yeeemp.okand.base;
package art.pegasko.yeeemp.okand.base;
public abstract class Potato {
private static Potato instance;
@ -28,6 +28,8 @@ public abstract class Potato {
}
public abstract Event getById(int id);
public abstract void save(Event container);
public abstract void delete(Event container);
}

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package art.pegasko.yeeemp.okand.impl;
package art.pegasko.yeeemp.okand.impl;
import art.pegasko.yeeemp.okand.base.Event;
import art.pegasko.yeeemp.okand.base.Potato;

View file

@ -14,7 +14,7 @@
* limitations under the License.
*/
package art.pegasko.yeeemp.ui.activity;
package art.pegasko.yeeemp.ui.activity;
import androidx.appcompat.app.AppCompatActivity;
@ -24,9 +24,14 @@ import android.content.Intent;
import android.os.Bundle;
import android.util.Log;
import android.util.TimeUtils;
import android.view.KeyEvent;
import android.view.LayoutInflater;
import android.view.MenuItem;
import android.view.View;
import android.widget.AdapterView;
import android.widget.ArrayAdapter;
import android.widget.DatePicker;
import android.widget.MultiAutoCompleteTextView;
import android.widget.TextView;
import android.widget.TimePicker;
@ -39,12 +44,16 @@ import java.time.LocalDateTime;
import java.time.ZoneId;
import java.time.temporal.ChronoUnit;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
import java.util.concurrent.TimeUnit;
import java.util.function.Function;
import art.pegasko.yeeemp.base.Queue;
import art.pegasko.yeeemp.base.Tag;
import art.pegasko.yeeemp.base.TagStat;
import art.pegasko.yeeemp.databinding.ActivityEventEditBinding;
import art.pegasko.yeeemp.R;
@ -59,8 +68,11 @@ public class EventEditActivity extends AppCompatActivity {
private Event event;
private EventContainer eventContainer;
private ActivityEventEditBinding binding;
private TagStat[] tagStat;
/** Store values for edited or new event */
/**
* Store values for edited or new event
*/
private static class EventContainer {
public String comment;
public long timestamp;
@ -82,21 +94,33 @@ public class EventEditActivity extends AppCompatActivity {
// Get Queue ID from Intent
Bundle extras = getIntent().getExtras();
if (extras == null) {
Log.e(TAG, "Missing Intent arguments (queue_id)");
Log.e(
TAG,
"Missing Intent arguments (queue_id)"
);
finish();
return;
}
int queue_id = extras.getInt("queue_id", -1);
int queue_id = extras.getInt(
"queue_id",
-1
);
if (queue_id == -1) {
Log.e(TAG, "Missing Intent arguments (queue_id)");
Log.e(
TAG,
"Missing Intent arguments (queue_id)"
);
finish();
return;
}
queue = Wrapper.getQueueMaker().getById(queue_id);
if (queue == null) {
Log.e(TAG, "Missing Intent arguments (queue_id)");
Log.e(
TAG,
"Missing Intent arguments (queue_id)"
);
finish();
return;
}
@ -107,12 +131,15 @@ public class EventEditActivity extends AppCompatActivity {
// Get Event ID from Intent (optional)
extras = getIntent().getExtras();
if (extras != null) {
int event_id = extras.getInt("event_id", -1);
int event_id = extras.getInt(
"event_id",
-1
);
if (event_id != -1) {
this.event = Wrapper.getEventMaker().getById(event_id);
this.eventContainer.timestamp = this.event.getTimestamp();
this.eventContainer.comment = this.event.getComment();
for (Tag tag: this.event.getTags()) {
for (Tag tag : this.event.getTags()) {
this.eventContainer.tags.add(tag.getName());
}
}
@ -121,7 +148,8 @@ public class EventEditActivity extends AppCompatActivity {
binding = ActivityEventEditBinding.inflate(getLayoutInflater());
setContentView(binding.getRoot());
setSupportActionBar(binding.toolbar);
getSupportActionBar().setTitle(getSupportActionBar().getTitle() + " / " + (event == null ? "Create" : "Edit") + " Event");
getSupportActionBar().setTitle(
getSupportActionBar().getTitle() + " / " + (event == null ? "Create" : "Edit") + " Event");
getSupportActionBar().setDisplayHomeAsUpEnabled(true);
getSupportActionBar().setDisplayShowHomeEnabled(true);
@ -131,6 +159,17 @@ public class EventEditActivity extends AppCompatActivity {
if (this.eventContainer.comment != null)
binding.eventEditContent.eventEditComment.setText(this.eventContainer.comment);
/* Comment Listeners */
// Request focus on click
this.binding.eventEditContent.eventEditContainerComment.setOnClickListener((View view) -> {
this.binding.eventEditContent.eventEditComment.requestFocus();
this.binding.eventEditContent.eventEditComment.setSelection(
EventEditActivity.this.binding.eventEditContent.eventEditComment.getText().length());
});
/* Timestamp Listeners */
binding.eventEditContent.eventEditContainerTimestamp.setOnClickListener((View view) -> {
Date date;
if (this.eventContainer.timestamp != 0)
@ -153,21 +192,34 @@ public class EventEditActivity extends AppCompatActivity {
newDate.setSeconds(0);
this.eventContainer.timestamp = newDate.getTime();
binding.eventEditContent.eventEditTimestamp.setText(Utils.formatTs(this.eventContainer.timestamp));
binding.eventEditContent.eventEditTimestamp.setText(
Utils.formatTs(this.eventContainer.timestamp));
},
date.getHours(), date.getMinutes(), true
date.getHours(),
date.getMinutes(),
true
);
timePickerDialog.show();
}, date.getYear() + 1900, date.getMonth(), date.getDate()
},
date.getYear() + 1900,
date.getMonth(),
date.getDate()
);
datePickerDialog.show();
});
/* FAB Listeners */
binding.fab.setOnLongClickListener((View view) -> {
Snackbar.make(view, "Save Event", Snackbar.LENGTH_LONG)
Snackbar.make(
view,
"Save Event",
Snackbar.LENGTH_LONG
)
.setAnchorView(R.id.fab)
.setAction("Action", null).show();
.setAction(
"Action",
null
).show();
return true;
});
@ -175,20 +227,75 @@ public class EventEditActivity extends AppCompatActivity {
// Finalize values
this.eventContainer.comment = this.binding.eventEditContent.eventEditComment.getText().toString().trim();
String[] tags = EventEditActivity.this.binding.eventEditContent.eventEditTags.getText().toString().split(
",");
tags = Utils.orderedDeduplicateIgnoreCaseAndTrim(tags);
this.eventContainer.tags.clear();
for (String tag : tags) {
this.eventContainer.tags.add(tag);
}
// Fill event
boolean hasEvent = this.event != null;
if (this.event == null)
this.event = Wrapper.getEventMaker().create();
this.event.setTimestamp(this.eventContainer.timestamp);
this.event.removeTags();
for (String tag: this.eventContainer.tags) {
this.event.addTag(Wrapper.getTagMaker().getOrCreateInQueue(this.queue, tag));
for (String tag : this.eventContainer.tags) {
this.event.addTag(Wrapper.getTagMaker().getOrCreateInQueue(
this.queue,
tag
));
}
this.event.setComment(this.eventContainer.comment);
if (!hasEvent) {
this.queue.addEvent(event);
}
finish();
});
/* Tags list + input */
this.tagStat = this.queue.getGlobalTags();
ArrayAdapter<TagStat> adapter = new EventEditTagsAdapter(
this,
android.R.layout.simple_dropdown_item_1line,
0,
new ArrayList<>(Arrays.asList(this.tagStat))
);
this.binding.eventEditContent.eventEditTags.setAdapter(adapter);
this.binding.eventEditContent.eventEditTags.setThreshold(1);
this.binding.eventEditContent.eventEditTags.setTokenizer(new MultiAutoCompleteTextView.CommaTokenizer());
this.binding.eventEditContent.eventEditTags.setOnItemClickListener((parent, view, position, id) -> {
String[] tags = EventEditActivity.this.binding.eventEditContent.eventEditTags.getText().toString().split(
",");
tags = Utils.orderedDeduplicateIgnoreCaseAndTrim(tags);
EventEditActivity.this.binding.eventEditContent.eventEditTags.setText(String.join(
", ",
tags
));
EventEditActivity.this.binding.eventEditContent.eventEditTags.setSelection(
EventEditActivity.this.binding.eventEditContent.eventEditTags.getText().length());
});
// Request focus on click
this.binding.eventEditContent.eventEditContainerTags.setOnClickListener((View view) -> {
this.binding.eventEditContent.eventEditTags.requestFocus();
this.binding.eventEditContent.eventEditTags.setSelection(
EventEditActivity.this.binding.eventEditContent.eventEditTags.getText().length());
});
// Fill
this.binding.eventEditContent.eventEditTags.setText(String.join(
", ",
this.eventContainer.tags
));
this.binding.eventEditContent.eventEditTags.setSelection(
EventEditActivity.this.binding.eventEditContent.eventEditTags.getText().length());
}
@Override

View file

@ -0,0 +1,120 @@
/**
* 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.ui.activity;
import android.content.Context;
import android.util.Log;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.ArrayAdapter;
import android.widget.Filter;
import android.widget.TextView;
import java.util.ArrayList;
import java.util.List;
import art.pegasko.yeeemp.base.TagStat;
public class EventEditTagsAdapter extends ArrayAdapter<TagStat> {
public static final String TAG = EventEditTagsAdapter.class.getSimpleName();
private ArrayList<TagStat> items;
private ArrayList<TagStat> itemsAll;
private ArrayList<TagStat> suggestions;
private int viewResourceId;
private int viewFieldId;
private LayoutInflater inflater;
@SuppressWarnings("unchecked")
public EventEditTagsAdapter(Context context, int viewResourceId, int viewFieldId, ArrayList<TagStat> items) {
super(context, viewResourceId, items);
this.items = items;
this.itemsAll = (ArrayList<TagStat>) items.clone();
this.suggestions = new ArrayList<TagStat>();
this.viewResourceId = viewResourceId;
this.viewFieldId = viewFieldId;
this.inflater = LayoutInflater.from(context);
}
public View getView(int position, View convertView, ViewGroup parent) {
if (convertView == null) convertView = inflater.inflate(this.viewResourceId, parent, false);
TextView text;
try {
if (this.viewFieldId == 0) {
text = (TextView) convertView;
} else {
text = convertView.findViewById(this.viewFieldId);
}
if (text == null) {
Log.e(TAG, "Failed attach text to view");
throw new RuntimeException("Failed attach text to view");
}
} catch (ClassCastException e) {
Log.e(TAG, "You must supply a resource ID for a TextView");
throw new RuntimeException("You must supply a resource ID for a TextView");
}
text.setText(items.get(position).tag.getName() + " (" + Integer.toString(items.get(position).count) + ")");
return convertView;
}
@Override
public Filter getFilter() {
return nameFilter;
}
Filter nameFilter = new Filter() {
public String convertResultToString(Object resultValue) {
String str = ((TagStat) (resultValue)).tag.getName();
return str;
}
@Override
protected FilterResults performFiltering(CharSequence constraint) {
if (constraint != null) {
suggestions.clear();
for (TagStat product : itemsAll) {
if (product.tag.getName().contains(constraint.toString().toLowerCase())) {
suggestions.add(product);
}
}
FilterResults filterResults = new FilterResults();
filterResults.values = suggestions;
filterResults.count = suggestions.size();
return filterResults;
} else {
return new FilterResults();
}
}
@Override
protected void publishResults(CharSequence constraint, FilterResults results) {
@SuppressWarnings("unchecked") ArrayList<TagStat> filteredList = (ArrayList<TagStat>) results.values;
if (results != null && results.count > 0) {
clear();
for (TagStat c : filteredList) {
add(c);
}
notifyDataSetChanged();
}
}
};
}

View file

@ -63,21 +63,33 @@ public class EventListActivity extends AppCompatActivity {
// Get Queue ID from Intent
Bundle extras = getIntent().getExtras();
if (extras == null) {
Log.e(TAG, "Missing Intent arguments (queue_id)");
Log.e(
TAG,
"Missing Intent arguments (queue_id)"
);
finish();
return;
}
int queue_id = extras.getInt("queue_id", -1);
int queue_id = extras.getInt(
"queue_id",
-1
);
if (queue_id == -1) {
Log.e(TAG, "Missing Intent arguments (queue_id)");
Log.e(
TAG,
"Missing Intent arguments (queue_id)"
);
finish();
return;
}
queue = Wrapper.getQueueMaker().getById(queue_id);
if (queue == null) {
Log.e(TAG, "Missing Intent arguments (queue_id)");
Log.e(
TAG,
"Missing Intent arguments (queue_id)"
);
finish();
return;
}
@ -116,31 +128,33 @@ public class EventListActivity extends AppCompatActivity {
/* FAB Listeners */
binding.fab.setOnLongClickListener((View view) -> {
Snackbar.make(view, "Create Event", Snackbar.LENGTH_LONG)
Snackbar.make(
view,
"Create Event",
Snackbar.LENGTH_LONG
)
.setAnchorView(R.id.fab)
.setAction("Action", null).show();
.setAction(
"Action",
null
).show();
return true;
});
binding.fab.setOnClickListener(view -> {
Bundle extra = new Bundle();
extra.putInt("queue_id", this.queue.getId());
extra.putInt(
"queue_id",
this.queue.getId()
);
Intent intent = new Intent(view.getContext(), EventEditActivity.class);
Intent intent = new Intent(
view.getContext(),
EventEditActivity.class
);
intent.putExtras(extra);
view.getContext().startActivity(intent);
// Log.w(TAG, "TODO: Open editor");
//
// Event event = Wrapper.getEventMaker().create();
// event.setTimestamp(System.currentTimeMillis());
// event.setComment("Lobster number " + System.currentTimeMillis());
// queue.addEvent(event);
//
// Log.w(TAG, "Create: " + event.toString() + " in " + queue);
// updateList();
});
/* Fill lists */

View file

@ -59,7 +59,11 @@ class EventRecyclerViewAdapter extends RecyclerView.Adapter<EventRecyclerViewAda
View view = (
LayoutInflater
.from(viewGroup.getContext())
.inflate(R.layout.event_list_item, viewGroup, false)
.inflate(
R.layout.event_list_item,
viewGroup,
false
)
);
return new ViewHolder(view);
@ -69,12 +73,15 @@ class EventRecyclerViewAdapter extends RecyclerView.Adapter<EventRecyclerViewAda
public void onBindViewHolder(@NonNull ViewHolder viewHolder, int position) {
viewHolder.getBinding().eventListItemTags.removeAllViews();
Tag[] tags = this.events[position].getTags();
Log.w(TAG, "Tags: " + tags.length);
for (Tag tag: tags) {
for (Tag tag : tags) {
TextView tagView = (TextView) (
LayoutInflater
.from(viewHolder.getBinding().getRoot().getContext())
.inflate(R.layout.event_list_item_tag, null, false)
.inflate(
R.layout.event_list_item_tag,
null,
false
)
);
tagView.setText(tag.getName());
@ -87,22 +94,37 @@ class EventRecyclerViewAdapter extends RecyclerView.Adapter<EventRecyclerViewAda
if (comment != null && !comment.isEmpty()) {
viewHolder.getBinding().eventListItemComment.setVisibility(View.VISIBLE);
viewHolder.getBinding().eventListItemComment.setText(comment);
} else {
viewHolder.getBinding().eventListItemComment.setVisibility(View.GONE);
viewHolder.getBinding().eventListItemComment.setText("");
}
viewHolder.getBinding().eventListItemItem.setOnLongClickListener((View view) -> {
PopupMenu popupMenu = new PopupMenu(view.getContext(), viewHolder.getBinding().eventListItemItem);
popupMenu.getMenuInflater().inflate(R.menu.event_list_item_action_menu, popupMenu.getMenu());
PopupMenu popupMenu = new PopupMenu(
view.getContext(),
viewHolder.getBinding().eventListItemItem
);
popupMenu.getMenuInflater().inflate(
R.menu.event_list_item_action_menu,
popupMenu.getMenu()
);
popupMenu.setOnMenuItemClickListener((MenuItem menuItem) -> {
if (menuItem.getItemId() == R.id.event_list_item_action_menu_delete) {
new AlertDialog.Builder(view.getContext())
.setTitle("Delete event")
.setMessage("Are you sure you want to delete this event?")
.setPositiveButton(android.R.string.yes, (dialog, which) -> {
.setPositiveButton(
android.R.string.yes,
(dialog, which) -> {
Wrapper.getEventMaker().delete(events[position]);
reloadItems();
})
.setNegativeButton(android.R.string.no, null)
}
)
.setNegativeButton(
android.R.string.no,
null
)
.setIcon(android.R.drawable.ic_dialog_alert)
.show();
}
@ -115,10 +137,19 @@ class EventRecyclerViewAdapter extends RecyclerView.Adapter<EventRecyclerViewAda
});
viewHolder.getBinding().eventListItemItem.setOnClickListener((View view) -> {
Bundle extra = new Bundle();
extra.putInt("event_id", this.events[position].getId());
extra.putInt("queue_id", this.queue.getId());
extra.putInt(
"event_id",
this.events[position].getId()
);
extra.putInt(
"queue_id",
this.queue.getId()
);
Intent intent = new Intent(view.getContext(), EventEditActivity.class);
Intent intent = new Intent(
view.getContext(),
EventEditActivity.class
);
intent.putExtras(extra);
view.getContext().startActivity(intent);
@ -147,19 +178,4 @@ class EventRecyclerViewAdapter extends RecyclerView.Adapter<EventRecyclerViewAda
this.events = this.queue.getEvents();
this.notifyDataSetChanged();
}
// public ScheduledFuture<?> scheduleDeleteAt(int position) {
// ScheduledExecutorService scheduledExecutorService = Executors.newScheduledThreadPool(1);
// ScheduledFuture<?> future = scheduledExecutorService.schedule(() -> {
// try {
// Wrapper.getEventMaker().delete(this.events[position]);
// } catch (Exception e) {
// Log.wtf(TAG, e);
// }
// }, 5, TimeUnit.SECONDS);
//
// // Hide item from list
//
// return future;
// }
}

View file

@ -17,7 +17,9 @@
package art.pegasko.yeeemp.ui.activity;
import android.os.Bundle;
import com.google.android.material.snackbar.Snackbar;
import androidx.appcompat.app.AppCompatActivity;
import android.util.Log;
@ -66,16 +68,20 @@ public class QueueListActivity extends AppCompatActivity {
/* FAB Listeners */
binding.fab.setOnLongClickListener((View view) -> {
Snackbar.make(view, "Create Queue", Snackbar.LENGTH_LONG)
.setAnchorView(R.id.fab)
.setAction("Action", null).show();
Snackbar.make(
view,
"Create Queue",
Snackbar.LENGTH_LONG
).setAnchorView(R.id.fab).setAction(
"Action",
null
).show();
return true;
});
binding.fab.setOnClickListener(view -> {
Queue q = Wrapper.getQueueMaker().create();
q.setName("New Queue");
Log.w(TAG, "Create: " + q.toString());
updateList();
});

View file

@ -50,7 +50,11 @@ class QueueRecyclerViewAdapter extends RecyclerView.Adapter<QueueRecyclerViewAda
View view = (
LayoutInflater
.from(viewGroup.getContext())
.inflate(R.layout.queue_list_item, viewGroup, false)
.inflate(
R.layout.queue_list_item,
viewGroup,
false
)
);
return new ViewHolder(view);
@ -61,19 +65,31 @@ class QueueRecyclerViewAdapter extends RecyclerView.Adapter<QueueRecyclerViewAda
viewHolder.getBinding().queueListItemTitle.setText(queues[position].getName());
viewHolder.getBinding().queueListItemItem.setOnLongClickListener((View view) -> {
PopupMenu popupMenu = new PopupMenu(view.getContext(), viewHolder.getBinding().queueListItemItem);
popupMenu.getMenuInflater().inflate(R.menu.queue_list_item_action_menu, popupMenu.getMenu());
PopupMenu popupMenu = new PopupMenu(
view.getContext(),
viewHolder.getBinding().queueListItemItem
);
popupMenu.getMenuInflater().inflate(
R.menu.queue_list_item_action_menu,
popupMenu.getMenu()
);
popupMenu.setOnMenuItemClickListener((MenuItem menuItem) -> {
if (menuItem.getItemId() == R.id.queue_list_item_action_menu_delete) {
new AlertDialog.Builder(view.getContext())
.setTitle("Delete queue")
.setMessage("Are you sure you want to delete this queue?")
.setPositiveButton(android.R.string.yes, (dialog, which) -> {
.setPositiveButton(
android.R.string.yes,
(dialog, which) -> {
Wrapper.getQueueMaker().delete(queues[position]);
reloadItems();
})
.setNegativeButton(android.R.string.no, null)
}
)
.setNegativeButton(
android.R.string.no,
null
)
.setIcon(android.R.drawable.ic_dialog_alert)
.show();
} else if (menuItem.getItemId() == R.id.queue_list_item_action_menu_rename) {
@ -85,13 +101,19 @@ class QueueRecyclerViewAdapter extends RecyclerView.Adapter<QueueRecyclerViewAda
input.setText(queues[position].getName());
builder.setView(input);
builder.setPositiveButton("OK", (dialog, which) -> {
builder.setPositiveButton(
"OK",
(dialog, which) -> {
String name = input.getText().toString().trim();
queues[position].setName(name);
reloadItems();
});
builder.setNegativeButton("Cancel", (dialog, which) -> dialog.cancel());
}
);
builder.setNegativeButton(
"Cancel",
(dialog, which) -> dialog.cancel()
);
builder.show();
}
@ -104,9 +126,15 @@ class QueueRecyclerViewAdapter extends RecyclerView.Adapter<QueueRecyclerViewAda
});
viewHolder.getBinding().queueListItemItem.setOnClickListener((View view) -> {
Bundle extra = new Bundle();
extra.putInt("queue_id", queues[position].getId());
extra.putInt(
"queue_id",
queues[position].getId()
);
Intent intent = new Intent(view.getContext(), EventListActivity.class);
Intent intent = new Intent(
view.getContext(),
EventListActivity.class
);
intent.putExtras(extra);
view.getContext().startActivity(intent);
@ -115,35 +143,11 @@ class QueueRecyclerViewAdapter extends RecyclerView.Adapter<QueueRecyclerViewAda
viewHolder.getBinding().queueListItemStats.setText(Integer.toString(queues[position].getEventCount()));
viewHolder.getBinding().queueListItemPlus.setOnClickListener((View view) -> {
Intent intent = new Intent(view.getContext(), EventEditActivity.class);
Intent intent = new Intent(
view.getContext(),
EventEditActivity.class
);
view.getContext().startActivity(intent);
// Log.w(TAG, "TODO: Open editor");
//
// Event event = Wrapper.getEventMaker().create();
// event.setTimestamp(System.currentTimeMillis());
// event.setComment("Lobster number " + System.currentTimeMillis() + "\n" + "Lorem ipsum dolor sit amet, consectetur adipiscing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua. Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.");
// queues[position].addEvent(event);
//
// Tag[] tags = new Tag[] {
// Wrapper.getTagMaker().getOrCreateInQueue(queues[position], "tag_" + position + "_cum"),
// Wrapper.getTagMaker().getOrCreateInQueue(queues[position], "tag_" + position + "_poo"),
// Wrapper.getTagMaker().getOrCreateInQueue(queues[position], "tag_" + position + "_fart"),
// Wrapper.getTagMaker().getOrCreateInQueue(queues[position], "tag_" + position + "_penis"),
// Wrapper.getTagMaker().getOrCreateInQueue(queues[position], "tag_" + position + "_dick"),
// Wrapper.getTagMaker().getOrCreateInQueue(queues[position], "tag_" + position + "_cock"),
// Wrapper.getTagMaker().getOrCreateInQueue(queues[position], "tag_" + position + "_zalupa"),
// Wrapper.getTagMaker().getOrCreateInQueue(queues[position], "tag_" + position + "_ololo"),
// Wrapper.getTagMaker().getOrCreateInQueue(queues[position], "tag_" + position + "_owo"),
// Wrapper.getTagMaker().getOrCreateInQueue(queues[position], "tag_" + position + "_uwu"),
// };
// for (Tag tag: tags) {
// event.addTag(tag);
// }
//
// Log.w(TAG, "Create: " + event.toString() + " in " + queues[position]);
//
// reloadItems();
});
}

View file

@ -17,7 +17,10 @@
package art.pegasko.yeeemp.ui.activity;
import java.text.SimpleDateFormat;
import java.util.ArrayList;
import java.util.Date;
import java.util.HashSet;
import java.util.Set;
public class Utils {
public static final String DATE_FORMAT = "yyyy-MM-dd HH:mm:ss";
@ -26,4 +29,24 @@ public class Utils {
SimpleDateFormat sdf = new SimpleDateFormat(Utils.DATE_FORMAT);
return sdf.format(timestamp);
}
public static String[] orderedDeduplicateIgnoreCaseAndTrim(String[] items) {
Set<String> duplicates = new HashSet<String>();
ArrayList<String> reverseOrderedItems = new ArrayList<String>();
for (int index = items.length - 1; index >= 0; --index) {
String item = items[index].toLowerCase().trim();
if (!duplicates.contains(item) && !item.isEmpty()) {
duplicates.add(item);
reverseOrderedItems.add(item);
}
}
String[] finalItems = new String[reverseOrderedItems.size()];
for (int index = 0; index < finalItems.length; ++index) {
finalItems[finalItems.length - index - 1] = reverseOrderedItems.get(index);
}
return finalItems;
}
}

View file

@ -5,16 +5,27 @@
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/pegasko_black"
android:orientation="vertical"
app:layout_behavior="@string/appbar_scrolling_view_behavior">
android:orientation="vertical">
<ScrollView
android:layout_width="match_parent"
android:layout_height="match_parent">
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
xmlns:tools="http://schemas.android.com/tools"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/pegasko_black"
android:orientation="vertical">
<LinearLayout
android:id="@+id/event_edit_container_timestamp"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="@dimen/event_edit_box_margin"
android:id="@+id/event_edit_container_timestamp"
android:background="@drawable/box_red" >
android:background="@drawable/box_red"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
@ -22,8 +33,8 @@
android:layout_margin="@dimen/event_edit_hint_margin"
android:paddingLeft="@dimen/event_edit_hint_padding_left"
android:text="Time"
android:textSize="@dimen/event_edit_hint_text"
android:textColor="@color/pegasko_black"
android:textSize="@dimen/event_edit_hint_text"
android:textStyle="bold" />
<TextView
@ -36,26 +47,26 @@
android:padding="@dimen/event_edit_timestamp_padding"
android:text="Timestamp"
android:textColor="@color/pegasko_white"
android:textSize="@dimen/event_edit_timestamp_text"/>
android:textSize="@dimen/event_edit_timestamp_text" />
</LinearLayout>
<LinearLayout
android:id="@+id/event_edit_container_comment"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:layout_margin="@dimen/event_edit_box_margin"
android:background="@drawable/box_red" >
android:background="@drawable/box_red"
android:orientation="vertical">
<TextView
android:id="@+id/textView2"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/event_edit_hint_margin"
android:paddingLeft="@dimen/event_edit_hint_padding_left"
android:text="Comment"
android:textSize="@dimen/event_edit_hint_text"
android:textColor="@color/pegasko_black"
android:textSize="@dimen/event_edit_hint_text"
android:textStyle="bold" />
<EditText
@ -66,11 +77,11 @@
android:background="@drawable/box_black"
android:cursorVisible="true"
android:gravity="start|left"
android:hint="comment"
android:inputType="textMultiLine"
android:padding="@dimen/event_edit_comment_padding"
android:scrollbars="vertical"
android:singleLine="false"
android:text="lonk message (very very very long, trust me, i'm sure that is is very loooooooooooooooonk so will fit many lines and still be very very loooooooooooooooooooooooooooooooooonk"
android:textColor="@color/pegasko_white"
android:textSize="@dimen/event_edit_comment_text">
@ -78,4 +89,40 @@
</LinearLayout>
<LinearLayout
android:id="@+id/event_edit_container_tags"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/event_edit_box_margin"
android:background="@drawable/box_red"
android:orientation="vertical">
<TextView
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/event_edit_hint_margin"
android:paddingLeft="@dimen/event_edit_hint_padding_left"
android:text="Tags"
android:textColor="@color/pegasko_black"
android:textSize="@dimen/event_edit_hint_text"
android:textStyle="bold" />
<MultiAutoCompleteTextView
android:id="@+id/event_edit_tags"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_margin="@dimen/event_edit_tags_suggest_margin"
android:background="@drawable/box_black"
android:hint="tags"
android:padding="@dimen/event_edit_tags_suggest_padding"
android:textSize="@dimen/event_edit_tags_suggest_text">
</MultiAutoCompleteTextView>
</LinearLayout>
</LinearLayout>
</ScrollView>
</LinearLayout>

View file

@ -0,0 +1,11 @@
<?xml version="1.0" encoding="utf-8"?>
<TextView xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="@dimen/event_list_item_tag_padding"
android:layout_margin="@dimen/event_list_item_tag_margin"
android:background="@drawable/box_red"
android:textColor="@color/pegasko_black"
android:text="sample_tag" >
</TextView>

View file

@ -40,7 +40,6 @@
android:layout_margin="@dimen/event_list_item_comment_margin"
android:fontFamily="monospace"
android:text="TextView"
android:textAlignment="center"
android:visibility="gone"
android:padding="@dimen/event_list_item_comment_padding"
android:textColor="@color/pegasko_white"

View file

@ -53,5 +53,13 @@
<dimen name="event_edit_comment_margin">8dp</dimen>
<dimen name="event_edit_comment_padding">4dp</dimen>
<dimen name="event_edit_comment_text">12sp</dimen>
<dimen name="event_edit_comment_text">24sp</dimen>
<dimen name="event_edit_tags_margin">8dp</dimen>
<dimen name="event_edit_tags_padding">4dp</dimen>
<dimen name="event_edit_tags_text">24sp</dimen>
<dimen name="event_edit_tags_suggest_padding">4dp</dimen>
<dimen name="event_edit_tags_suggest_margin">8dp</dimen>
<dimen name="event_edit_tags_suggest_text">24sp</dimen>
</resources>

View file

@ -1,33 +0,0 @@
/**
* 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;
import org.junit.Test;
import static org.junit.Assert.*;
/**
* Example local unit test, which will execute on the development machine (host).
*
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
*/
public class ExampleUnitTest {
@Test
public void addition_isCorrect() {
assertEquals(4, 2 + 2);
}
}