diff --git a/README.md b/README.md
index 5c068c96..4facf1a3 100644
--- a/README.md
+++ b/README.md
@@ -21,6 +21,10 @@ deflated prices and a lot more!
- [Add untracked trades easily](#add-untracked-trades-easily)
- [Setup offers quickly](#offer-editors)
- [Favorites lookup](#favorites-lookup)
+- [Development](#development)
+ + [General Structure of Codebase](#general-structure-of-codebase)
+ + [Example Flow](#example-flow)
+
# Features
The plugin is divided into three tabs: the slots tab, flipping tab and statistics tab.
@@ -122,6 +126,108 @@ Quickly lookup your favorited items just by typing "1" in the ge search!
+# Development
+
+### General Structure of Codebase
+
+This section will talk about the purpose of various parts of the codebase, specifically the folders.
+
+**controller/**
+- This folder contains the components that handle some specific responsibility of the plugin, mainly by handling runelite
+ events (such as new GE offers) or presenting APIs to alter/view user data. Each class in this folder is instantiated
+ by the FlippingPlugin in its constructor, which is run on client startup. Each of these classes handles a specific
+ responsibility of the plugin. For example, the NewOfferEventPipelineHandler is responsible for consuming new offer
+ events and adding it to the data structures that model user trade history. The classes are used via the FlippingPlugin
+ calling their methods, for example, when the FlippingPlugin gets an offer event in the onGrandExchangeOfferChanged
+ method it calls `newOfferEventPipelineHandler.onGrandExchangeOfferChanged(newOffer);`.
+ Much of the logic in these controller classes used to live in the FlippingPlugin class but was moved out as the
+ FlippingPlugin class had become huge and was doing too many things.
+
+
+**model/**
+- This folder contains the classes that represent the data that will be stored on disk. These classes are turned into
+ JSON and saved to disk and are also created from JSON that was previously saved to disk. To see what each of these
+ classes actually model, check out the [Data model](#Data-model) section.
+
+
+**db/**
+- This folder contains the class responsible for taking the models and saving them
+to disk as JSON. It also loads JSON from disk (previously saved) and turns them into objects.
+
+**ui/**
+- This folder contains all of the UI code for the plugin which is the code that draws the "plugin" you see, such as the slots
+tab, the flipping tab, the statistics tab, and all of the content within them.
+
+**jobs/**
+- This folder contains code that is running in background threads to periodically performing some action, such as the
+ code that queries the wiki to get the new prices of items, or the code that sends premium users' slots to our api.
+
+**utilities/**
+- Just random code that doesn't fit neatly into any of the categories above.
+
+### Example Flow
+
+This section will give an example of how the plugin actually runs, what happens during its lifetime, how the different
+components interact with each other, and how the data flows.
+
+The starting point to this plugin is the FlippingPlugin class. On Runelite startup, the Runelite code will create an
+instance of the FlippingPlugin class and then call its `startUp()` method.
+
+#### On Runelite Client Startup
+The FlippingPlugin does three main things on startup (all in the `startUp()` method):
+1. It creates instances of all the controller classes, described in the controller section of [General Structure of Codebase](#General-structure-of-codebase)
+2. It initializes the various UI classes such as the FlippingPanel and StatsPanel. This will result in the UI being
+drawn eventually.
+3. It loads the user's previously saved trading history from the disk via one of the controller classes, the DataHandler
+
+Now things have been setup properly and the UI has been drawn and populated with the user's previously saved data from
+disk.
+
+#### During Runelite Client's Lifetime
+During the lifetime of the Runelite Client (between startup and shutdown), there are primarily three ways the plugin does work
+1. **Handling Runelite events that reflect some change of state in the game**. There are many different Runelite events. For example,
+ events can trigger in various scenarios: when an account logs in, a GE offer gets placed, an account logs out,
+ an account opens the GE interace, and many more. The Runelite code will feed the plugin these events if the plugin implements certain methods. For example,
+ when the user places an offer in the GE, Runelite code gives the plugin details of the GE offer event via calling the
+ `onGrandExchangeOfferChanged` on the FlippingPlugin class and passing that method a `GrandExchangeOfferChanged` object.
+ It knows to do this because of method naming conventions. You don't have to define a method to handle every possible
+ type of Runelite event, just the ones your plugin should care about.
+2. **Handling UI interactions that the user initiates**. For example, the user may want to see data for another account, so
+ he clicks on the account dropdown selector and selects another account. The plugin then needs to re-render its UI with the
+ selected account's trades.
+3. **Via background threads that are performing some action periodically**. See the jobs section of [General Structure of Codebase](#general-structure-of-codebase).
+
+
+#### On Runelite Client Shutdown
+The runelite client will tell the plugin when its shutting down and will allow it to execute some code before
+that happens. This occurs in `onClientShutdown` in the FlippingPlugin class. Not much happens other than saving
+user data to disk and cancelling any background jobs that were running.
+
+
+### Data model
+
+This section describes how the plugin models users' trade history. The main model classes used to do this are:
+AccountData, FlippingItem, HistoryManager, and OfferEvent.
+
+When you go to `.runelite/flipping/.json` and open it, you will see the JSON version of an AccountData
+object. Each of the user's osrs accounts get their own AccountData object, each of which is stored in a file
+of the format `.runelite/flipping/.json`.
+
+AccountData objects are created from the JSON in those files on client startup (or created on account login if they have
+no previously saved data for that account). As the user makes trades, deletes trades via the UI, and so on, the
+AccountData object for the correct account is mutated. Then, on account logout or client shutdown, it is turned back
+into JSON and saved into the same file it was loaded from (or creates a new file if there was no previously saved data).
+
+The AccountData object's most important field is `List trades`. This field contains FlippingItem objects.
+FlippingItem objects have an itemId (every runescape item has a unique number to identify it) and represents the trade history
+for that item on that account. So, for example, if you flipped dragon claws on an account, you would have one and only one
+FlippingItem in that account's AccountData object that holds all the buy and sell offers you have ever made for dragon
+claws on that account.
+
+The FlippingItem stores all the offers for that item in the `HistoryManager history` field. Put simply, a HistoryManager
+is just a list of OfferEvent. An OfferEvent represents a buy and sell offer in which some amount of an
+item bought or sold. It has various fields like price, quantity, etc.
+
## Icon Attributions
All icons were either made by Belieal or downloaded from the creators on www.flaticon.com below.
diff --git a/build.gradle b/build.gradle
index 73e2c20f..3deace85 100644
--- a/build.gradle
+++ b/build.gradle
@@ -10,7 +10,7 @@ repositories {
mavenCentral()
}
-def runeLiteVersion = '1.8.32'
+def runeLiteVersion = '1.9.5'
dependencies {
compileOnly group: 'net.runelite', name:'client', version: runeLiteVersion
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 5c2d1cf0..e708b1c0 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradlew b/gradlew
index 83f2acfd..4f906e0c 100755
--- a/gradlew
+++ b/gradlew
@@ -82,6 +82,7 @@ esac
CLASSPATH=$APP_HOME/gradle/wrapper/gradle-wrapper.jar
+
# Determine the Java command to use to start the JVM.
if [ -n "$JAVA_HOME" ] ; then
if [ -x "$JAVA_HOME/jre/sh/java" ] ; then
@@ -129,6 +130,7 @@ fi
if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
APP_HOME=`cygpath --path --mixed "$APP_HOME"`
CLASSPATH=`cygpath --path --mixed "$CLASSPATH"`
+
JAVACMD=`cygpath --unix "$JAVACMD"`
# We build the pattern for arguments to be converted via cygpath
@@ -154,19 +156,19 @@ if [ "$cygwin" = "true" -o "$msys" = "true" ] ; then
else
eval `echo args$i`="\"$arg\""
fi
- i=$((i+1))
+ i=`expr $i + 1`
done
case $i in
- (0) set -- ;;
- (1) set -- "$args0" ;;
- (2) set -- "$args0" "$args1" ;;
- (3) set -- "$args0" "$args1" "$args2" ;;
- (4) set -- "$args0" "$args1" "$args2" "$args3" ;;
- (5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
- (6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
- (7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
- (8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
- (9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
+ 0) set -- ;;
+ 1) set -- "$args0" ;;
+ 2) set -- "$args0" "$args1" ;;
+ 3) set -- "$args0" "$args1" "$args2" ;;
+ 4) set -- "$args0" "$args1" "$args2" "$args3" ;;
+ 5) set -- "$args0" "$args1" "$args2" "$args3" "$args4" ;;
+ 6) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" ;;
+ 7) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" ;;
+ 8) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" ;;
+ 9) set -- "$args0" "$args1" "$args2" "$args3" "$args4" "$args5" "$args6" "$args7" "$args8" ;;
esac
fi
@@ -175,14 +177,9 @@ save () {
for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
echo " "
}
-APP_ARGS=$(save "$@")
+APP_ARGS=`save "$@"`
# Collect all arguments for the java command, following the shell quoting and substitution rules
eval set -- $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS "\"-Dorg.gradle.appname=$APP_BASE_NAME\"" -classpath "\"$CLASSPATH\"" org.gradle.wrapper.GradleWrapperMain "$APP_ARGS"
-# by default we should be in the correct project dir, but when run from Finder on Mac, the cwd is wrong
-if [ "$(uname)" = "Darwin" ] && [ "$HOME" = "$PWD" ]; then
- cd "$(dirname "$0")"
-fi
-
exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index 24467a14..ac1b06f9 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -29,6 +29,9 @@ if "%DIRNAME%" == "" set DIRNAME=.
set APP_BASE_NAME=%~n0
set APP_HOME=%DIRNAME%
+@rem Resolve any "." and ".." in APP_HOME to make it shorter.
+for %%i in ("%APP_HOME%") do set APP_HOME=%%~fi
+
@rem Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
set DEFAULT_JVM_OPTS="-Xmx64m" "-Xms64m"
@@ -37,7 +40,7 @@ if defined JAVA_HOME goto findJavaFromJavaHome
set JAVA_EXE=java.exe
%JAVA_EXE% -version >NUL 2>&1
-if "%ERRORLEVEL%" == "0" goto init
+if "%ERRORLEVEL%" == "0" goto execute
echo.
echo ERROR: JAVA_HOME is not set and no 'java' command could be found in your PATH.
@@ -51,7 +54,7 @@ goto fail
set JAVA_HOME=%JAVA_HOME:"=%
set JAVA_EXE=%JAVA_HOME%/bin/java.exe
-if exist "%JAVA_EXE%" goto init
+if exist "%JAVA_EXE%" goto execute
echo.
echo ERROR: JAVA_HOME is set to an invalid directory: %JAVA_HOME%
@@ -61,28 +64,14 @@ echo location of your Java installation.
goto fail
-:init
-@rem Get command-line arguments, handling Windows variants
-
-if not "%OS%" == "Windows_NT" goto win9xME_args
-
-:win9xME_args
-@rem Slurp the command line arguments.
-set CMD_LINE_ARGS=
-set _SKIP=2
-
-:win9xME_args_slurp
-if "x%~1" == "x" goto execute
-
-set CMD_LINE_ARGS=%*
-
:execute
@rem Setup the command line
set CLASSPATH=%APP_HOME%\gradle\wrapper\gradle-wrapper.jar
+
@rem Execute Gradle
-"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %CMD_LINE_ARGS%
+"%JAVA_EXE%" %DEFAULT_JVM_OPTS% %JAVA_OPTS% %GRADLE_OPTS% "-Dorg.gradle.appname=%APP_BASE_NAME%" -classpath "%CLASSPATH%" org.gradle.wrapper.GradleWrapperMain %*
:end
@rem End local scope for the variables with windows NT shell
diff --git a/images/list.png b/images/list.png
new file mode 100644
index 00000000..a4960a0d
Binary files /dev/null and b/images/list.png differ
diff --git a/src/main/java/com/flippingutilities/model/AccountWideData.java b/src/main/java/com/flippingutilities/model/AccountWideData.java
index 43dd53dd..24f6262e 100644
--- a/src/main/java/com/flippingutilities/model/AccountWideData.java
+++ b/src/main/java/com/flippingutilities/model/AccountWideData.java
@@ -3,14 +3,14 @@
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
-import java.util.ArrayList;
-import java.util.List;
+import java.util.*;
@Data
@Slf4j
public class AccountWideData {
List