diff --git a/forge-ai/src/main/java/forge/ai/ComputerUtil.java b/forge-ai/src/main/java/forge/ai/ComputerUtil.java index 3af26c3358f..0d83d91aefe 100644 --- a/forge-ai/src/main/java/forge/ai/ComputerUtil.java +++ b/forge-ai/src/main/java/forge/ai/ComputerUtil.java @@ -1626,7 +1626,7 @@ public static boolean hasAFogEffect(final Player defender, final Player ai, bool for (final Card c : all) { // check if card is at least available to be played // further improvements might consider if AI has options to steal the spell by making it playable first - if (c.getZone().getPlayer() != null && c.getZone().getPlayer() != defender && c.mayPlay(defender).isEmpty()) { + if (c.getZone() != null && c.getZone().getPlayer() != null && c.getZone().getPlayer() != defender && c.mayPlay(defender).isEmpty()) { continue; } for (final SpellAbility sa : c.getSpellAbilities()) { diff --git a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java index f35c873f368..2f507a41dda 100644 --- a/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java +++ b/forge-ai/src/main/java/forge/ai/CreatureEvaluator.java @@ -26,6 +26,9 @@ public int evaluateCreature(final Card c) { return evaluateCreature(c, true, true); } public int evaluateCreature(final Card c, final boolean considerPT, final boolean considerCMC) { + //Card shouldn't be null and AI shouldn't crash since this is just score + if (c == null) + return 0; int value = 80; if (!c.isToken()) { value += addValue(20, "non-token"); // tokens should be worth less than actual cards diff --git a/forge-core/pom.xml b/forge-core/pom.xml index d96a8c7ead6..24740c37a5c 100644 --- a/forge-core/pom.xml +++ b/forge-core/pom.xml @@ -23,6 +23,22 @@ commons-lang3 3.17.0 + + org.apache.commons + commons-text + 1.12.0 + + + org.apache.commons + commons-lang3 + + + + + com.apptasticsoftware + rssreader + 3.8.2 + diff --git a/forge-core/src/main/java/forge/util/RSSReader.java b/forge-core/src/main/java/forge/util/RSSReader.java new file mode 100644 index 00000000000..2c60a1685d6 --- /dev/null +++ b/forge-core/src/main/java/forge/util/RSSReader.java @@ -0,0 +1,76 @@ +package forge.util; + +import com.apptasticsoftware.rssreader.Item; +import com.apptasticsoftware.rssreader.RssReader; +import org.apache.commons.text.StringEscapeUtils; + +import java.io.InputStream; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.time.ZonedDateTime; +import java.util.Date; +import java.util.List; + +public class RSSReader { + public static String getCommitLog(String commitsAtom, Date buildDateOriginal, Date maxDate) { + String message = ""; + SimpleDateFormat simpleDate = TextUtil.getSimpleDate(); + try { + RssReader reader = new RssReader(); + URL url = new URL(commitsAtom); + InputStream inputStream = url.openStream(); + List items = reader.read(inputStream).toList(); + StringBuilder logs = new StringBuilder(); + int c = 0; + for (Item i : items) { + if (i.getTitle().isEmpty()) + continue; + String title = TextUtil.stripNonValidXMLCharacters(i.getTitle().get()); + if (title.contains("Merge")) + continue; + ZonedDateTime zonedDateTime = i.getPubDateZonedDateTime().isPresent() ? i.getPubDateZonedDateTime().get() : null; + if (zonedDateTime == null) + continue; + Date feedDate = Date.from(zonedDateTime.toInstant()); + if (buildDateOriginal != null && feedDate.before(buildDateOriginal)) + continue; + if (maxDate != null && feedDate.after(maxDate)) + continue; + logs.append(simpleDate.format(feedDate)).append(" | ").append(StringEscapeUtils.unescapeXml(title).replace("\n", "").replace(" ", "")).append("\n\n"); + if (c >= 15) + break; + c++; + } + if (logs.length() > 0) + message += ("\n\nLatest Changes:\n\n" + logs); + inputStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return message; + } + public static String getLatestReleaseTag(String releaseAtom) { + String tag = ""; + try { + RssReader reader = new RssReader(); + URL url = new URL(releaseAtom); + InputStream inputStream = url.openStream(); + List items = reader.read(inputStream).toList(); + for (Item i : items) { + if (i.getLink().isPresent()) { + try { + String val = i.getLink().get(); + tag = val.substring(val.lastIndexOf("forge")); + break; + } catch (Exception e) { + e.printStackTrace(); + } + } + } + inputStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return tag; + } +} diff --git a/forge-core/src/main/java/forge/util/TextUtil.java b/forge-core/src/main/java/forge/util/TextUtil.java index c9388983c77..775cfdbec05 100644 --- a/forge-core/src/main/java/forge/util/TextUtil.java +++ b/forge-core/src/main/java/forge/util/TextUtil.java @@ -1,16 +1,23 @@ package forge.util; +import java.io.File; import java.text.DecimalFormat; import java.text.Normalizer; +import java.text.SimpleDateFormat; +import java.time.OffsetDateTime; import java.util.ArrayList; +import java.util.Calendar; +import java.util.Date; import java.util.List; import java.util.Locale; import java.util.Map; import java.util.Map.Entry; +import java.util.TimeZone; import forge.item.IPaperCard; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; +import org.apache.commons.text.StringEscapeUtils; import com.google.common.collect.ImmutableSortedMap; @@ -21,6 +28,8 @@ * */ public class TextUtil { + private static final StringBuilder changes = new StringBuilder(); + private static SimpleDateFormat simpleDate; static ImmutableSortedMap romanMap = ImmutableSortedMap.naturalOrder() .put(1000, "M").put(900, "CM") @@ -313,8 +322,8 @@ public static String fastReplace( String str, String target, String replacement return sb.toString(); } //Convert to Mana String - public static String toManaString(String ManaProduced){ - if (ManaProduced == "mana"|| ManaProduced.contains("Combo")|| ManaProduced.contains("Any")) + public static String toManaString(String ManaProduced) { + if ("mana".equals(ManaProduced) || ManaProduced.contains("Combo")|| ManaProduced.contains("Any")) return "mana";//fix manamorphose stack description and probably others.. return "{"+TextUtil.fastReplace(ManaProduced," ","}{")+"}"; } @@ -363,4 +372,83 @@ public static String moveArticleToEnd(String str) { } return str; } + /* + * Strip non valid XML Characters + */ + public static String stripNonValidXMLCharacters(String in) { + StringBuffer out = new StringBuffer(); + char current; + + if (in == null || ("".equals(in))) { + return ""; + } + for (int i = 0; i < in.length(); i++) { + current = in.charAt(i); + if ((current == 0x9) || (current == 0xA) || (current == 0xD) + || ((current >= 0x20) && (current <= 0xD7FF)) + || ((current >= 0xE000) && (current <= 0xFFFD)) + || ((current >= 0x10000) && (current <= 0x10FFFF))) { + out.append(current); + } + } + return out.toString(); + } + public static SimpleDateFormat getSimpleDate() { + if (simpleDate == null) + simpleDate = new SimpleDateFormat("E, MMM dd, yyyy - hh:mm:ss a"); + return simpleDate; + } + //format changelog + public static String getFormattedChangelog(File changelog, String defaultLog) { + if (!changelog.exists()) + return defaultLog; + if (changes == null || changes.toString().isEmpty()) { + try { + Calendar calendar = Calendar.getInstance(); + SimpleDateFormat original = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + SimpleDateFormat formatted = getSimpleDate(); + String offset = " GMT " + OffsetDateTime.now().getOffset(); + List toformat = FileUtil.readAllLines(changelog, false); + boolean skip = false; + int count = 0; + for (String line : toformat) { + if (line.isEmpty() || line.startsWith("#") || line.length() < 4) + continue; + if (line.contains("**Merge")) { + skip = true; + continue; + } + if (line.startsWith("[")) { + if (skip) { + skip = false; + continue; + } + count++; + String datestring = line.substring(line.lastIndexOf(" *")+1).replace("*", ""); + try { + original.setTimeZone(TimeZone.getTimeZone("UTC")); + Date toDate = original.parse(datestring); + calendar.setTime(toDate); + formatted.setTimeZone(TimeZone.getDefault()); + changes.append("\n(").append(formatted.format(calendar.getTime())).append(offset).append(")\n\n"); + } catch (Exception e2) { + changes.append("\n(").append(datestring).append(")\n\n"); + } + if (count > 20) + break; + } else { + if (skip) + continue; + if (line.startsWith(" * ")) + changes.append("\n").append(StringEscapeUtils.unescapeXml(line)); + else + changes.append(StringEscapeUtils.unescapeXml(line)); + } + } + } catch (Exception e) { + return defaultLog; + } + } + return changes.toString(); + } } diff --git a/forge-gui-android/assets/shaders/grayscale.frag b/forge-gui-android/assets/shaders/grayscale.frag deleted file mode 100644 index 4d801c770a4..00000000000 --- a/forge-gui-android/assets/shaders/grayscale.frag +++ /dev/null @@ -1,16 +0,0 @@ -#ifdef GL_ES -precision mediump float; -#endif - -varying vec4 v_color; -varying vec2 v_texCoords; -uniform sampler2D u_texture; -uniform float u_grayness; -uniform float u_bias; - -void main() { - vec4 c = v_color * texture2D(u_texture, v_texCoords); - float grey = dot( c.rgb, vec3(0.22, 0.707, 0.071) ); - vec3 blendedColor = mix(c.rgb, vec3(grey), u_grayness); - gl_FragColor = mix(vec4(0.0, 0.0, 0.0, 1.0), vec4(blendedColor.rgb, c.a), u_bias); -} \ No newline at end of file diff --git a/forge-gui-android/assets/shaders/grayscale.vert b/forge-gui-android/assets/shaders/grayscale.vert deleted file mode 100644 index 17d96ca8dde..00000000000 --- a/forge-gui-android/assets/shaders/grayscale.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec4 a_position; -attribute vec4 a_color; -attribute vec2 a_texCoord0; - -uniform mat4 u_projTrans; - -varying vec4 v_color; -varying vec2 v_texCoords; - -void main() { - v_color = a_color; - v_texCoords = a_texCoord0; - gl_Position = u_projTrans * a_position; -} \ No newline at end of file diff --git a/forge-gui-android/assets/shaders/outline.frag b/forge-gui-android/assets/shaders/outline.frag deleted file mode 100644 index 738d23c1f71..00000000000 --- a/forge-gui-android/assets/shaders/outline.frag +++ /dev/null @@ -1,40 +0,0 @@ -#ifdef GL_ES -precision mediump float; -precision mediump int; -#endif - -uniform sampler2D u_texture; -uniform vec2 u_viewportInverse; -uniform vec3 u_color; -uniform float u_offset; -uniform float u_step; - -varying vec4 v_color; -varying vec2 v_texCoord; - -#define ALPHA_VALUE_BORDER 0.5 - -void main() { - vec2 T = v_texCoord.xy; - - float alpha = 0.0; - bool allin = true; - for( float ix = -u_offset; ix < u_offset; ix += u_step ) - { - for( float iy = -u_offset; iy < u_offset; iy += u_step ) - { - float newAlpha = texture2D(u_texture, T + vec2(ix, iy) * u_viewportInverse).a; - allin = allin && newAlpha > ALPHA_VALUE_BORDER; - if (newAlpha > ALPHA_VALUE_BORDER && newAlpha >= alpha) - { - alpha = newAlpha; - } - } - } - if (allin) - { - alpha = 0.0; - } - - gl_FragColor = vec4(u_color,alpha); -} \ No newline at end of file diff --git a/forge-gui-android/assets/shaders/outline.vert b/forge-gui-android/assets/shaders/outline.vert deleted file mode 100644 index 1b6e438116d..00000000000 --- a/forge-gui-android/assets/shaders/outline.vert +++ /dev/null @@ -1,16 +0,0 @@ -uniform mat4 u_projTrans; - -attribute vec4 a_position; -attribute vec2 a_texCoord0; -attribute vec4 a_color; - -varying vec4 v_color; -varying vec2 v_texCoord; - -uniform vec2 u_viewportInverse; - -void main() { - gl_Position = u_projTrans * a_position; - v_texCoord = a_texCoord0; - v_color = a_color; -} \ No newline at end of file diff --git a/forge-gui-android/assets/shaders/underwater.frag b/forge-gui-android/assets/shaders/underwater.frag deleted file mode 100644 index 6287a862c9c..00000000000 --- a/forge-gui-android/assets/shaders/underwater.frag +++ /dev/null @@ -1,26 +0,0 @@ -#ifdef GL_ES -#define PRECISION mediump -precision PRECISION float; -precision PRECISION int; -#else -#define PRECISION -#endif - -varying vec2 v_texCoords; -uniform sampler2D u_texture; -uniform float u_amount; -uniform float u_speed; -uniform float u_time; -uniform float u_bias; - -void main () { - vec2 uv = v_texCoords; - - uv.y += (cos((uv.y + (u_time * 0.04 * u_speed)) * 45.0) * 0.0019 * u_amount) + (cos((uv.y + (u_time * 0.1 * u_speed)) * 10.0) * 0.002 * u_amount); - - uv.x += (sin((uv.y + (u_time * 0.07 * u_speed)) * 15.0) * 0.0029 * u_amount) + (sin((uv.y + (u_time * 0.1 * u_speed)) * 15.0) * 0.002 * u_amount); - - vec4 texColor = texture2D(u_texture, uv); - - gl_FragColor = mix(vec4(0.0, 0.0, 0.0, 1.0), texColor, u_bias); -} \ No newline at end of file diff --git a/forge-gui-android/assets/shaders/warp.frag b/forge-gui-android/assets/shaders/warp.frag deleted file mode 100644 index f8a7022fa2c..00000000000 --- a/forge-gui-android/assets/shaders/warp.frag +++ /dev/null @@ -1,57 +0,0 @@ -#ifdef GL_ES -precision mediump float; -#endif - -varying vec2 v_texCoords; -uniform sampler2D u_texture; - -uniform float u_time; -uniform float u_speed; -uniform float u_amount; -uniform vec2 u_viewport; -uniform vec2 u_position; - -float random2d(vec2 n) { - return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453); -} - -float randomRange (in vec2 seed, in float min, in float max) { - return min + random2d(seed) * (max - min); -} - -float insideRange(float v, float bottom, float top) { - return step(bottom, v) - step(top, v); -} - -void main() -{ - float time = floor(u_time * u_speed * 60.0); - - vec3 outCol = texture2D(u_texture, v_texCoords).rgb; - - float maxOffset = u_amount/2.0; - for (float i = 0.0; i < 2.0; i += 1.0) { - float sliceY = random2d(vec2(time, 2345.0 + float(i))); - float sliceH = random2d(vec2(time, 9035.0 + float(i))) * 0.25; - float hOffset = randomRange(vec2(time, 9625.0 + float(i)), -maxOffset, maxOffset); - vec2 uvOff = v_texCoords; - uvOff.x += hOffset; - if (insideRange(v_texCoords.y, sliceY, fract(sliceY+sliceH)) == 1.0){ - outCol = texture2D(u_texture, uvOff).rgb; - } - } - - float maxColOffset = u_amount / 6.0; - float rnd = random2d(vec2(time , 9545.0)); - vec2 colOffset = vec2(randomRange(vec2(time , 9545.0), -maxColOffset, maxColOffset), - randomRange(vec2(time , 7205.0), -maxColOffset, maxColOffset)); - if (rnd < 0.33) { - outCol.r = texture2D(u_texture, v_texCoords + colOffset).r; - } else if (rnd < 0.66) { - outCol.g = texture2D(u_texture, v_texCoords + colOffset).g; - } else { - outCol.b = texture2D(u_texture, v_texCoords + colOffset).b; - } - - gl_FragColor = vec4(outCol, 1.0); -} \ No newline at end of file diff --git a/forge-gui-android/src/com/badlogic/gdx/backends/android/ForgeAndroidApplication.java b/forge-gui-android/src/com/badlogic/gdx/backends/android/ForgeAndroidApplication.java index 4a38863d058..a1c08bab642 100644 --- a/forge-gui-android/src/com/badlogic/gdx/backends/android/ForgeAndroidApplication.java +++ b/forge-gui-android/src/com/badlogic/gdx/backends/android/ForgeAndroidApplication.java @@ -172,10 +172,10 @@ public void dispose () { createWakeLock(config.useWakelock); useImmersiveMode(this.useImmersiveMode); - if (this.useImmersiveMode) { + /*if (this.useImmersiveMode) { AndroidVisibilityListener vlistener = new AndroidVisibilityListener(); vlistener.createListener(this); - } + }*/ // detect an already connected bluetooth keyboardAvailable if (getResources().getConfiguration().keyboard != Configuration.KEYBOARD_NOKEYS) input.setKeyboardAvailable(true); @@ -229,13 +229,13 @@ public void onWindowFocusChanged (boolean hasFocus) { @Override public void useImmersiveMode (boolean use) { - if (!use) return; + /*if (!use) return; View view = getWindow().getDecorView(); int code = View.SYSTEM_UI_FLAG_LAYOUT_STABLE | View.SYSTEM_UI_FLAG_LAYOUT_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_LAYOUT_FULLSCREEN | View.SYSTEM_UI_FLAG_HIDE_NAVIGATION | View.SYSTEM_UI_FLAG_FULLSCREEN | View.SYSTEM_UI_FLAG_IMMERSIVE_STICKY; - view.setSystemUiVisibility(code); + view.setSystemUiVisibility(code);*/ } @Override diff --git a/forge-gui-android/src/forge/app/AtomReader.java b/forge-gui-android/src/forge/app/AtomReader.java new file mode 100644 index 00000000000..b48e6762f6d --- /dev/null +++ b/forge-gui-android/src/forge/app/AtomReader.java @@ -0,0 +1,133 @@ +package forge.app; + +import android.util.Xml; +import org.xmlpull.v1.XmlPullParser; + +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; + +public class AtomReader { + private static final String ns = null; + + public List parse(InputStream in) throws Exception { + try { + XmlPullParser parser = Xml.newPullParser(); + parser.setFeature(XmlPullParser.FEATURE_PROCESS_NAMESPACES, false); + parser.setInput(in, "UTF-8"); + parser.nextTag(); + return readFeed(parser); + } finally { + in.close(); + } + } + + private List readFeed(XmlPullParser parser) throws Exception { + List entries = new ArrayList<>(); + + parser.require(XmlPullParser.START_TAG, ns, "feed"); + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String name = parser.getName(); + // Starts by looking for the entry tag. + if (name.equals("entry")) { + entries.add(readEntry(parser)); + } else { + skip(parser); + } + } + return entries; + } + + private void skip(XmlPullParser parser) throws Exception { + if (parser.getEventType() != XmlPullParser.START_TAG) { + throw new IllegalStateException(); + } + int depth = 1; + while (depth != 0) { + switch (parser.next()) { + case XmlPullParser.END_TAG: + depth--; + break; + case XmlPullParser.START_TAG: + depth++; + break; + } + } + } + + public static class Entry { + public final String title; + public final String updated; + public final String link; + + private Entry(String title, String updated, String link) { + this.title = title; + this.updated = updated; + this.link = link; + } + } + + private Entry readEntry(XmlPullParser parser) throws Exception { + parser.require(XmlPullParser.START_TAG, ns, "entry"); + String title = null; + String updated = null; + String link = null; + while (parser.next() != XmlPullParser.END_TAG) { + if (parser.getEventType() != XmlPullParser.START_TAG) { + continue; + } + String name = parser.getName(); + if (name.equals("title")) { + title = readTitle(parser); + } else if (name.equals("updated")) { + updated = readUpdated(parser); + } else if (name.equals("link")) { + link = readLink(parser); + } else { + skip(parser); + } + } + return new Entry(title, updated, link); + } + + private String readTitle(XmlPullParser parser) throws Exception { + parser.require(XmlPullParser.START_TAG, ns, "title"); + String title = readText(parser); + parser.require(XmlPullParser.END_TAG, ns, "title"); + return title; + } + + private String readUpdated(XmlPullParser parser) throws Exception { + parser.require(XmlPullParser.START_TAG, ns, "updated"); + String updated = readText(parser); + parser.require(XmlPullParser.END_TAG, ns, "updated"); + return updated; + } + + private String readLink(XmlPullParser parser) throws Exception { + String link = ""; + parser.require(XmlPullParser.START_TAG, ns, "link"); + String tag = parser.getName(); + String relType = parser.getAttributeValue(null, "rel"); + if (tag.equals("link")) { + if (relType.equals("alternate")) { + link = parser.getAttributeValue(null, "href"); + parser.nextTag(); + } + } + parser.require(XmlPullParser.END_TAG, ns, "link"); + return link; + } + + private String readText(XmlPullParser parser) throws Exception { + String result = ""; + if (parser.next() == XmlPullParser.TEXT) { + result = parser.getText(); + parser.nextTag(); + } + return result; + } +} diff --git a/forge-gui-android/src/forge/app/GitLogs.java b/forge-gui-android/src/forge/app/GitLogs.java new file mode 100644 index 00000000000..6d7b0e32dde --- /dev/null +++ b/forge-gui-android/src/forge/app/GitLogs.java @@ -0,0 +1,73 @@ +package forge.app; + +import forge.util.TextUtil; +import org.apache.commons.text.StringEscapeUtils; + +import java.io.InputStream; +import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.Date; +import java.util.List; + +public class GitLogs { + public String getLatest(String commitsAtom, Date buildDateOriginal, Date maxDate) { + String message = ""; + try { + URL url = new URL(commitsAtom); + InputStream inputStream = url.openStream(); + List entries = new AtomReader().parse(inputStream); + StringBuilder logs = new StringBuilder(); + SimpleDateFormat simpleDate = TextUtil.getSimpleDate(); + SimpleDateFormat atomDate = new SimpleDateFormat("yyyy-MM-dd'T'hh:mm:ss"); + int c = 0; + for (AtomReader.Entry entry : entries) { + if (entry.title == null) + continue; + String title = TextUtil.stripNonValidXMLCharacters(entry.title); + if (title.contains("Merge")) + continue; + if (entry.updated == null) + continue; + Date feedDate = atomDate.parse(entry.updated); + if (buildDateOriginal != null && feedDate.before(buildDateOriginal)) + continue; + if (maxDate != null && feedDate.after(maxDate)) + continue; + logs.append(simpleDate.format(feedDate)).append(" | ").append(StringEscapeUtils.unescapeXml(title).replace("\n", "").replace(" ", "")).append("\n\n"); + if (c >= 15) + break; + c++; + } + if (logs.length() > 0) + message += ("\n\nLatest Changes:\n\n" + logs); + inputStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return message; + } + + public String getLatestReleaseTag(String releaseAtom) { + String tag = ""; + try { + URL url = new URL(releaseAtom); + InputStream inputStream = url.openStream(); + List entries = new AtomReader().parse(inputStream); + for (AtomReader.Entry entry : entries) { + if (entry.link != null) { + try { + String val = entry.link; + tag = val.substring(val.lastIndexOf("forge")); + break; + } catch (Exception e) { + e.printStackTrace(); + } + } + } + inputStream.close(); + } catch (Exception e) { + e.printStackTrace(); + } + return tag; + } +} diff --git a/forge-gui-android/src/forge/app/Main.java b/forge-gui-android/src/forge/app/Main.java index 1456dd14155..a405d43a124 100644 --- a/forge-gui-android/src/forge/app/Main.java +++ b/forge-gui-android/src/forge/app/Main.java @@ -71,6 +71,7 @@ import java.io.OutputStream; import java.text.Normalizer; import java.util.ArrayList; +import java.util.Date; public class Main extends ForgeAndroidApplication { private AndroidAdapter Gadapter; @@ -633,6 +634,16 @@ public String getVersionString() { return versionString; } + @Override + public String getLatestChanges(String commitsAtom, Date buildDateOriginal, Date maxDate) { + return new GitLogs().getLatest(commitsAtom, buildDateOriginal, maxDate); + } + + @Override + public String getReleaseTag(String releaseAtom) { + return new GitLogs().getLatestReleaseTag(releaseAtom); + } + @Override public boolean openFile(String filename) { try { diff --git a/forge-gui-desktop/filters/build.txt b/forge-gui-desktop/filters/build.txt new file mode 100644 index 00000000000..aca99ef22d7 --- /dev/null +++ b/forge-gui-desktop/filters/build.txt @@ -0,0 +1 @@ +${timestamp} \ No newline at end of file diff --git a/forge-gui-desktop/filters/version.txt b/forge-gui-desktop/filters/version.txt new file mode 100644 index 00000000000..fb02fab734c --- /dev/null +++ b/forge-gui-desktop/filters/version.txt @@ -0,0 +1 @@ +${snapshot-version} \ No newline at end of file diff --git a/forge-gui-desktop/pom.xml b/forge-gui-desktop/pom.xml index 85cf10bf48e..1aeb7a74877 100644 --- a/forge-gui-desktop/pom.xml +++ b/forge-gui-desktop/pom.xml @@ -15,11 +15,47 @@ 0 0 0 + ${maven.build.timestamp} + yyyy-MM-dd HH:mm:ss --add-opens java.desktop/java.beans=ALL-UNNAMED --add-opens java.desktop/javax.swing.border=ALL-UNNAMED --add-opens java.desktop/javax.swing.event=ALL-UNNAMED --add-opens java.desktop/sun.swing=ALL-UNNAMED --add-opens java.desktop/java.awt.image=ALL-UNNAMED --add-opens java.desktop/java.awt.color=ALL-UNNAMED --add-opens java.desktop/sun.awt.image=ALL-UNNAMED --add-opens java.desktop/javax.swing=ALL-UNNAMED --add-opens java.desktop/java.awt=ALL-UNNAMED --add-opens java.base/java.util=ALL-UNNAMED --add-opens java.base/java.lang=ALL-UNNAMED --add-opens java.base/java.lang.reflect=ALL-UNNAMED --add-opens java.base/java.text=ALL-UNNAMED --add-opens java.desktop/java.awt.font=ALL-UNNAMED --add-opens java.base/jdk.internal.misc=ALL-UNNAMED --add-opens java.base/sun.nio.ch=ALL-UNNAMED --add-opens java.base/java.nio=ALL-UNNAMED --add-opens java.base/java.math=ALL-UNNAMED --add-opens java.base/java.util.concurrent=ALL-UNNAMED --add-opens java.base/java.net=ALL-UNNAMED -Dio.netty.tryReflectionSetAccessible=true -Dfile.encoding=UTF-8 + + org.codehaus.mojo + build-helper-maven-plugin + 3.6.0 + + + timestamp-property + initialize + + timestamp-property + + + + month.date + MM.dd + + + + regex-property + initialize + + regex-property + + + + snapshot-version + ${revision} + -SNAPSHOT + -SNAPSHOT-${month.date} + false + + + + com.akathist.maven.plugins.launch4j launch4j-maven-plugin @@ -75,22 +111,6 @@ - - org.apache.maven.plugins - maven-jar-plugin - 3.3.0 - - - - true - true - - - java.desktop/java.beans java.desktop/javax.swing.border java.desktop/javax.swing.event java.desktop/sun.swing java.desktop/java.awt.image java.desktop/java.awt.color java.desktop/sun.awt.image java.desktop/javax.swing java.desktop/java.awt java.base/java.util java.base/java.lang java.base/java.lang.reflect java.base/java.text java.desktop/java.awt.font java.base/jdk.internal.misc java.base/sun.nio.ch java.base/java.nio java.base/java.math java.base/java.util.concurrent java.base/java.net - - - - com.google.code.maven-replacer-plugin replacer @@ -129,10 +149,10 @@ jar-with-dependencies - - true - + ${project.name} + ${snapshot-version} + ${project.organization.name} java.desktop/java.beans java.desktop/javax.swing.border java.desktop/javax.swing.event java.desktop/sun.swing java.desktop/java.awt.image java.desktop/java.awt.color java.desktop/sun.awt.image java.desktop/javax.swing java.desktop/java.awt java.base/java.util java.base/java.lang java.base/java.lang.reflect java.base/java.text java.desktop/java.awt.font java.base/jdk.internal.misc java.base/sun.nio.ch java.base/java.nio java.base/java.math java.base/java.util.concurrent java.base/java.net forge.view.Main @@ -222,6 +242,12 @@ + + + filters + true + + diff --git a/forge-gui-desktop/src/main/java/forge/control/FControl.java b/forge-gui-desktop/src/main/java/forge/control/FControl.java index 6ebdbb2783b..620fcbdc08b 100644 --- a/forge-gui-desktop/src/main/java/forge/control/FControl.java +++ b/forge-gui-desktop/src/main/java/forge/control/FControl.java @@ -26,6 +26,7 @@ import java.awt.event.WindowEvent; import java.io.File; import java.io.IOException; +import java.net.URL; import java.util.Collections; import java.util.List; @@ -61,8 +62,10 @@ import forge.screens.deckeditor.CDeckEditorUI; import forge.toolbox.FOptionPane; import forge.toolbox.FSkin; +import forge.util.FileUtil; import forge.util.Localizer; import forge.util.RestartUtil; +import forge.util.TextUtil; import forge.view.FFrame; import forge.view.FView; @@ -83,6 +86,7 @@ public enum FControl implements KeyEventDispatcher { private boolean altKeyLastDown; private CloseAction closeAction; private final List currentMatches = Lists.newArrayList(); + private String snapsVersion = ""; public enum CloseAction { NONE, @@ -210,6 +214,12 @@ public boolean exitForge() { /** After view and model have been initialized, control can start.*/ public void initialize() { + //get version string + try { + URL url = new URL("https://downloads.cardforge.org/dailysnapshots/version.txt"); + snapsVersion = FileUtil.readFileToString(url); + + } catch (Exception e) {} // Preloads skin components (using progress bar). FSkin.loadFull(true); @@ -237,7 +247,10 @@ public void initialize() { System.err.printf("Error loading quest data (%s).. skipping for now..%n", questname); } } - + // format release notes upon loading + try { + TextUtil.getFormattedChangelog(new File(FileUtil.pathCombine(System.getProperty("user.dir"), ForgeConstants.CHANGES_FILE_NO_RELEASE)),""); + } catch (Exception e){} // Handles resizing in null layouts of layers in JLayeredPane as well as saving window layout final FFrame window = Singletons.getView().getFrame(); window.addComponentListener(new ComponentAdapter() { @@ -262,6 +275,12 @@ public void componentMoved(final ComponentEvent e) { FView.SINGLETON_INSTANCE.setSplashProgessBarMessage(localizer.getMessage("lblOpeningMainWindow")); SwingUtilities.invokeLater(() -> Singletons.getView().initialize()); } + public String compareVersion(String currentVersion) { + if (currentVersion.isEmpty() || snapsVersion.isEmpty() + || !currentVersion.contains("SNAPSHOT") || currentVersion.equalsIgnoreCase(snapsVersion)) + return ""; + return "NEW SNAPSHOT AVAILABLE!!!"; + } private void setGlobalKeyboardHandler() { final KeyboardFocusManager manager = KeyboardFocusManager.getCurrentKeyboardFocusManager(); diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuDownloaders.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuDownloaders.java index 34c027b6ee0..cff8d450da9 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuDownloaders.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuDownloaders.java @@ -15,6 +15,11 @@ import forge.gui.download.GuiDownloadSkins; import forge.gui.error.BugReporter; import forge.gui.framework.ICDoc; +import forge.util.RSSReader; + +import java.util.concurrent.CompletableFuture; + +import static forge.localinstance.properties.ForgeConstants.GITHUB_COMMITS_URL_ATOM; /** * Controls the utilities submenu in the home UI. @@ -27,7 +32,7 @@ public enum CSubmenuDownloaders implements ICDoc { SINGLETON_INSTANCE; private final UiCommand cmdLicensing = VSubmenuDownloaders.SINGLETON_INSTANCE::showLicensing; - private final UiCommand cmdCheckForUpdates = () -> new AutoUpdater(false).attemptToUpdate(); + private final UiCommand cmdCheckForUpdates = () -> new AutoUpdater(false).attemptToUpdate(CompletableFuture.supplyAsync(() -> RSSReader.getCommitLog(GITHUB_COMMITS_URL_ATOM, null, null))); private final UiCommand cmdPicDownload = () -> new GuiDownloader(new GuiDownloadPicturesLQ()).show(); private final UiCommand cmdPicDownloadHQ = () -> new GuiDownloader(new GuiDownloadPicturesHQ()).show(); diff --git a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuReleaseNotes.java b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuReleaseNotes.java index f4efe48833b..89497f98bd0 100644 --- a/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuReleaseNotes.java +++ b/forge-gui-desktop/src/main/java/forge/screens/home/settings/CSubmenuReleaseNotes.java @@ -20,7 +20,11 @@ import forge.gui.framework.ICDoc; import forge.localinstance.properties.ForgeConstants; +import forge.util.BuildInfo; import forge.util.FileUtil; +import forge.util.TextUtil; + +import java.io.File; /** * Controller for VSubmenuReleaseNotes submenu in the home UI. @@ -80,7 +84,8 @@ private static String getReleaseNotes() { String notes; if (FileUtil.doesFileExist(filePath)) { - notes = filePath + "\n\n" + FileUtil.readFileToString(filePath); + // get release notes + notes = BuildInfo.getVersionString() +" Changelog:\n\n" + TextUtil.getFormattedChangelog(new File(filePath), ForgeConstants.CHANGES_FILE_NO_RELEASE); } else if (FileUtil.doesFileExist(filePathRelease)) { notes = filePathRelease + "\n\n" + FileUtil.readFileToString(filePathRelease); } else { diff --git a/forge-gui-desktop/src/main/java/forge/view/FTitleBarBase.java b/forge-gui-desktop/src/main/java/forge/view/FTitleBarBase.java index 017e9daf455..65457a699fb 100644 --- a/forge-gui-desktop/src/main/java/forge/view/FTitleBarBase.java +++ b/forge-gui-desktop/src/main/java/forge/view/FTitleBarBase.java @@ -12,17 +12,24 @@ import java.awt.event.MouseAdapter; import java.awt.event.MouseEvent; import java.awt.event.WindowEvent; +import java.util.concurrent.CompletableFuture; import javax.swing.SpringLayout; import javax.swing.SwingUtilities; +import forge.control.FControl; +import forge.download.AutoUpdater; import forge.gui.framework.ILocalRepaint; import forge.toolbox.FSkin; import forge.toolbox.FSkin.Colors; import forge.toolbox.FSkin.SkinColor; import forge.toolbox.FSkin.SkinnedLabel; import forge.toolbox.FSkin.SkinnedMenuBar; +import forge.util.BuildInfo; import forge.util.Localizer; +import forge.util.RSSReader; + +import static forge.localinstance.properties.ForgeConstants.GITHUB_COMMITS_URL_ATOM; @SuppressWarnings("serial") public abstract class FTitleBarBase extends SkinnedMenuBar { @@ -42,6 +49,7 @@ public abstract class FTitleBarBase extends SkinnedMenuBar { protected final FullScreenButton btnFullScreen = new FullScreenButton(); protected final MaximizeButton btnMaximize = new MaximizeButton(); protected final CloseButton btnClose = new CloseButton(); + protected final UpdaterButton btnUpdateShortcut = new UpdaterButton(); protected FTitleBarBase(ITitleBarOwner owner0) { this.owner = owner0; @@ -71,6 +79,11 @@ protected void addControls() { add(btnLockTitleBar); layout.putConstraint(SpringLayout.EAST, btnLockTitleBar, 0, SpringLayout.WEST, btnMinimize); layout.putConstraint(SpringLayout.SOUTH, btnLockTitleBar, 0, SpringLayout.SOUTH, btnMinimize); + + add(btnUpdateShortcut); + layout.putConstraint(SpringLayout.EAST, btnUpdateShortcut, 0, SpringLayout.WEST, btnMinimize); + layout.putConstraint(SpringLayout.SOUTH, btnUpdateShortcut, 0, SpringLayout.SOUTH, btnMinimize); + } else { int offset = owner instanceof FDialog && ((FDialog)owner).allowResize() ? 0 : -1; @@ -408,4 +421,34 @@ public void paintComponent(Graphics g) { g2d.drawLine(x2, y1, x1, y2); } } + public class UpdaterButton extends TitleBarButton { + final int MARQUEE_SPEED_DIV = 15; + final int REPAINT_WITHIN_MS = 25; + final String displayText = FControl.instance.compareVersion(BuildInfo.getVersionString()); + private UpdaterButton() { + setToolTipText(Localizer.getInstance().getMessage("btnCheckForUpdates")); + setPreferredSize(new Dimension(160, 25)); + setEnabled(!displayText.isEmpty()); + } + @Override + protected void onClick() { + try { + new AutoUpdater(false).attemptToUpdate(CompletableFuture.supplyAsync(() -> RSSReader.getCommitLog(GITHUB_COMMITS_URL_ATOM, null, null))); + } catch (Exception e) { + e.printStackTrace(); + } + } + @Override + public void paintComponent(Graphics g) { + g.translate((int)((System.currentTimeMillis() / MARQUEE_SPEED_DIV) % (getWidth() * 2)) - getWidth(), 0); + super.paintComponent(g); + int thickness = 2; + Graphics2D g2d = (Graphics2D) g; + FSkin.setGraphicsColor(g2d, foreColor); + g2d.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON); + g2d.setStroke(new BasicStroke(thickness)); + g2d.drawString(displayText, 0, 17); + repaint(REPAINT_WITHIN_MS); + } + } } diff --git a/forge-gui-ios/src/forge/ios/Main.java b/forge-gui-ios/src/forge/ios/Main.java index fc452ca70ba..cbfc78b65bd 100644 --- a/forge-gui-ios/src/forge/ios/Main.java +++ b/forge-gui-ios/src/forge/ios/Main.java @@ -4,6 +4,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Date; import org.apache.commons.lang3.tuple.Pair; import org.robovm.apple.foundation.NSAutoreleasePool; @@ -78,6 +79,16 @@ public String getVersionString() { return "0.0"; } + @Override + public String getLatestChanges(String commitsAtom, Date buildDateOriginal, Date maxDate) { + return ""; + } + + @Override + public String getReleaseTag(String releaseAtom) { + return ""; + } + @Override public boolean openFile(final String filename) { return new IOSFiles().local(filename).exists(); diff --git a/forge-gui-mobile-dev/filters/build.txt b/forge-gui-mobile-dev/filters/build.txt new file mode 100644 index 00000000000..aca99ef22d7 --- /dev/null +++ b/forge-gui-mobile-dev/filters/build.txt @@ -0,0 +1 @@ +${timestamp} \ No newline at end of file diff --git a/forge-gui-mobile-dev/filters/version.txt b/forge-gui-mobile-dev/filters/version.txt new file mode 100644 index 00000000000..fb02fab734c --- /dev/null +++ b/forge-gui-mobile-dev/filters/version.txt @@ -0,0 +1 @@ +${snapshot-version} \ No newline at end of file diff --git a/forge-gui-mobile-dev/pom.xml b/forge-gui-mobile-dev/pom.xml index 1f69a13cb61..bf7c57c2260 100644 --- a/forge-gui-mobile-dev/pom.xml +++ b/forge-gui-mobile-dev/pom.xml @@ -18,25 +18,41 @@ src - - - ${project.basedir} - - **/*.vert - **/*.frag - **/title_bg_lq.png - **/title_bg_lq_portrait.png - **/transition.png - **/adv_bg_texture.jpg - **/adv_bg_splash.png - **/bg_splash.png - **/bg_texture.jpg - **/font1.ttf - **/logo.gif - - - + + org.codehaus.mojo + build-helper-maven-plugin + 3.6.0 + + + timestamp-property + initialize + + timestamp-property + + + + month.date + MM.dd + + + + regex-property + initialize + + regex-property + + + + snapshot-version + ${revision} + -SNAPSHOT + -SNAPSHOT-${month.date} + false + + + + maven-compiler-plugin @@ -133,10 +149,10 @@ jar-with-dependencies - - true - + ${project.name} + ${snapshot-version} + ${project.organization.name} java.desktop/java.beans java.desktop/javax.swing.border java.desktop/javax.swing.event java.desktop/sun.swing java.desktop/java.awt.image java.desktop/java.awt.color java.desktop/sun.awt.image java.desktop/javax.swing java.desktop/java.awt java.base/java.util java.base/java.lang java.base/java.lang.reflect java.base/java.text java.desktop/java.awt.font java.base/jdk.internal.misc java.base/sun.nio.ch java.base/java.nio java.base/java.math java.base/java.util.concurrent java.base/java.net forge.app.Main splash/logo.gif @@ -156,6 +172,26 @@ + + + ${project.basedir} + + **/title_bg_lq.png + **/title_bg_lq_portrait.png + **/transition.png + **/adv_bg_texture.jpg + **/adv_bg_splash.png + **/bg_splash.png + **/bg_texture.jpg + **/font1.ttf + **/logo.gif + + + + filters + true + + diff --git a/forge-gui-mobile-dev/shaders/grayscale.frag b/forge-gui-mobile-dev/shaders/grayscale.frag deleted file mode 100644 index 4d801c770a4..00000000000 --- a/forge-gui-mobile-dev/shaders/grayscale.frag +++ /dev/null @@ -1,16 +0,0 @@ -#ifdef GL_ES -precision mediump float; -#endif - -varying vec4 v_color; -varying vec2 v_texCoords; -uniform sampler2D u_texture; -uniform float u_grayness; -uniform float u_bias; - -void main() { - vec4 c = v_color * texture2D(u_texture, v_texCoords); - float grey = dot( c.rgb, vec3(0.22, 0.707, 0.071) ); - vec3 blendedColor = mix(c.rgb, vec3(grey), u_grayness); - gl_FragColor = mix(vec4(0.0, 0.0, 0.0, 1.0), vec4(blendedColor.rgb, c.a), u_bias); -} \ No newline at end of file diff --git a/forge-gui-mobile-dev/shaders/grayscale.vert b/forge-gui-mobile-dev/shaders/grayscale.vert deleted file mode 100644 index 17d96ca8dde..00000000000 --- a/forge-gui-mobile-dev/shaders/grayscale.vert +++ /dev/null @@ -1,14 +0,0 @@ -attribute vec4 a_position; -attribute vec4 a_color; -attribute vec2 a_texCoord0; - -uniform mat4 u_projTrans; - -varying vec4 v_color; -varying vec2 v_texCoords; - -void main() { - v_color = a_color; - v_texCoords = a_texCoord0; - gl_Position = u_projTrans * a_position; -} \ No newline at end of file diff --git a/forge-gui-mobile-dev/shaders/outline.frag b/forge-gui-mobile-dev/shaders/outline.frag deleted file mode 100644 index 738d23c1f71..00000000000 --- a/forge-gui-mobile-dev/shaders/outline.frag +++ /dev/null @@ -1,40 +0,0 @@ -#ifdef GL_ES -precision mediump float; -precision mediump int; -#endif - -uniform sampler2D u_texture; -uniform vec2 u_viewportInverse; -uniform vec3 u_color; -uniform float u_offset; -uniform float u_step; - -varying vec4 v_color; -varying vec2 v_texCoord; - -#define ALPHA_VALUE_BORDER 0.5 - -void main() { - vec2 T = v_texCoord.xy; - - float alpha = 0.0; - bool allin = true; - for( float ix = -u_offset; ix < u_offset; ix += u_step ) - { - for( float iy = -u_offset; iy < u_offset; iy += u_step ) - { - float newAlpha = texture2D(u_texture, T + vec2(ix, iy) * u_viewportInverse).a; - allin = allin && newAlpha > ALPHA_VALUE_BORDER; - if (newAlpha > ALPHA_VALUE_BORDER && newAlpha >= alpha) - { - alpha = newAlpha; - } - } - } - if (allin) - { - alpha = 0.0; - } - - gl_FragColor = vec4(u_color,alpha); -} \ No newline at end of file diff --git a/forge-gui-mobile-dev/shaders/outline.vert b/forge-gui-mobile-dev/shaders/outline.vert deleted file mode 100644 index 1b6e438116d..00000000000 --- a/forge-gui-mobile-dev/shaders/outline.vert +++ /dev/null @@ -1,16 +0,0 @@ -uniform mat4 u_projTrans; - -attribute vec4 a_position; -attribute vec2 a_texCoord0; -attribute vec4 a_color; - -varying vec4 v_color; -varying vec2 v_texCoord; - -uniform vec2 u_viewportInverse; - -void main() { - gl_Position = u_projTrans * a_position; - v_texCoord = a_texCoord0; - v_color = a_color; -} \ No newline at end of file diff --git a/forge-gui-mobile-dev/shaders/underwater.frag b/forge-gui-mobile-dev/shaders/underwater.frag deleted file mode 100644 index 6287a862c9c..00000000000 --- a/forge-gui-mobile-dev/shaders/underwater.frag +++ /dev/null @@ -1,26 +0,0 @@ -#ifdef GL_ES -#define PRECISION mediump -precision PRECISION float; -precision PRECISION int; -#else -#define PRECISION -#endif - -varying vec2 v_texCoords; -uniform sampler2D u_texture; -uniform float u_amount; -uniform float u_speed; -uniform float u_time; -uniform float u_bias; - -void main () { - vec2 uv = v_texCoords; - - uv.y += (cos((uv.y + (u_time * 0.04 * u_speed)) * 45.0) * 0.0019 * u_amount) + (cos((uv.y + (u_time * 0.1 * u_speed)) * 10.0) * 0.002 * u_amount); - - uv.x += (sin((uv.y + (u_time * 0.07 * u_speed)) * 15.0) * 0.0029 * u_amount) + (sin((uv.y + (u_time * 0.1 * u_speed)) * 15.0) * 0.002 * u_amount); - - vec4 texColor = texture2D(u_texture, uv); - - gl_FragColor = mix(vec4(0.0, 0.0, 0.0, 1.0), texColor, u_bias); -} \ No newline at end of file diff --git a/forge-gui-mobile-dev/shaders/warp.frag b/forge-gui-mobile-dev/shaders/warp.frag deleted file mode 100644 index f8a7022fa2c..00000000000 --- a/forge-gui-mobile-dev/shaders/warp.frag +++ /dev/null @@ -1,57 +0,0 @@ -#ifdef GL_ES -precision mediump float; -#endif - -varying vec2 v_texCoords; -uniform sampler2D u_texture; - -uniform float u_time; -uniform float u_speed; -uniform float u_amount; -uniform vec2 u_viewport; -uniform vec2 u_position; - -float random2d(vec2 n) { - return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453); -} - -float randomRange (in vec2 seed, in float min, in float max) { - return min + random2d(seed) * (max - min); -} - -float insideRange(float v, float bottom, float top) { - return step(bottom, v) - step(top, v); -} - -void main() -{ - float time = floor(u_time * u_speed * 60.0); - - vec3 outCol = texture2D(u_texture, v_texCoords).rgb; - - float maxOffset = u_amount/2.0; - for (float i = 0.0; i < 2.0; i += 1.0) { - float sliceY = random2d(vec2(time, 2345.0 + float(i))); - float sliceH = random2d(vec2(time, 9035.0 + float(i))) * 0.25; - float hOffset = randomRange(vec2(time, 9625.0 + float(i)), -maxOffset, maxOffset); - vec2 uvOff = v_texCoords; - uvOff.x += hOffset; - if (insideRange(v_texCoords.y, sliceY, fract(sliceY+sliceH)) == 1.0){ - outCol = texture2D(u_texture, uvOff).rgb; - } - } - - float maxColOffset = u_amount / 6.0; - float rnd = random2d(vec2(time , 9545.0)); - vec2 colOffset = vec2(randomRange(vec2(time , 9545.0), -maxColOffset, maxColOffset), - randomRange(vec2(time , 7205.0), -maxColOffset, maxColOffset)); - if (rnd < 0.33) { - outCol.r = texture2D(u_texture, v_texCoords + colOffset).r; - } else if (rnd < 0.66) { - outCol.g = texture2D(u_texture, v_texCoords + colOffset).g; - } else { - outCol.b = texture2D(u_texture, v_texCoords + colOffset).b; - } - - gl_FragColor = vec4(outCol, 1.0); -} \ No newline at end of file diff --git a/forge-gui-mobile-dev/src/forge/app/GameLauncher.java b/forge-gui-mobile-dev/src/forge/app/GameLauncher.java index 83d8560dffc..046c6c126ba 100644 --- a/forge-gui-mobile-dev/src/forge/app/GameLauncher.java +++ b/forge-gui-mobile-dev/src/forge/app/GameLauncher.java @@ -9,8 +9,6 @@ import com.badlogic.gdx.utils.SharedLibraryLoader; import forge.Forge; import forge.adventure.util.Config; -import forge.assets.AssetsDownloader; -import forge.util.FileUtil; import org.lwjgl.system.Configuration; import java.nio.file.Files; @@ -28,16 +26,9 @@ public GameLauncher(final String versionString) { if (!Files.exists(Paths.get(desktopModeAssetsDir + "res"))) desktopModeAssetsDir = "../forge-gui/";//try IDE run - // Assets directory used when the game fully emulates smartphone/tablet mode (desktopMode = false), useful when debugging from IDE - String assetsDir; - if (!AssetsDownloader.SHARE_DESKTOP_ASSETS) { - assetsDir = "testAssets/"; - FileUtil.ensureDirectoryExists(assetsDir); - } else { - assetsDir = "./"; - if (!Files.exists(Paths.get(assetsDir + "res"))) - assetsDir = "../forge-gui/"; - } + String assetsDir = "./"; + if (!Files.exists(Paths.get(assetsDir + "res"))) + assetsDir = "../forge-gui/"; // Place the file "switch_orientation.ini" to your assets folder to make the game switch to landscape orientation (unless desktopMode = true) String switchOrientationFile = assetsDir + "switch_orientation.ini"; diff --git a/forge-gui-mobile-dev/src/forge/app/Main.java b/forge-gui-mobile-dev/src/forge/app/Main.java index 040e0c28f8b..a8e9c424299 100644 --- a/forge-gui-mobile-dev/src/forge/app/Main.java +++ b/forge-gui-mobile-dev/src/forge/app/Main.java @@ -2,10 +2,7 @@ import com.badlogic.gdx.Gdx; import forge.interfaces.IDeviceAdapter; -import forge.util.BuildInfo; -import forge.util.FileUtil; -import forge.util.OperatingSystem; -import forge.util.RestartUtil; +import forge.util.*; import org.apache.commons.lang3.tuple.Pair; import javax.imageio.ImageIO; @@ -17,6 +14,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Date; import java.util.Optional; public class Main { @@ -53,10 +51,28 @@ public String getVersionString() { return versionString; } + @Override + public String getLatestChanges(String commitsAtom, Date buildDateOriginal, Date max) { + return RSSReader.getCommitLog(commitsAtom, buildDateOriginal, max); + } + + @Override + public String getReleaseTag(String releaseAtom) { + return RSSReader.getLatestReleaseTag(releaseAtom); + } + @Override public boolean openFile(String filename) { try { - Desktop.getDesktop().open(new File(filename)); + File installer = new File(filename); + if (installer.exists()) { + if (filename.endsWith(".jar")) { + installer.setExecutable(true, false); + Desktop.getDesktop().open(installer); + } else { + Desktop.getDesktop().open(installer.getParentFile()); + } + } return true; } catch (IOException e) { e.printStackTrace(); diff --git a/forge-gui-mobile/libs/gdx-natives.jar b/forge-gui-mobile/libs/gdx-natives.jar new file mode 100644 index 00000000000..15c0b61ffe5 Binary files /dev/null and b/forge-gui-mobile/libs/gdx-natives.jar differ diff --git a/forge-gui-mobile/src/forge/Forge.java b/forge-gui-mobile/src/forge/Forge.java index 4636aa2b07a..73502b18914 100644 --- a/forge-gui-mobile/src/forge/Forge.java +++ b/forge-gui-mobile/src/forge/Forge.java @@ -76,7 +76,7 @@ public class Forge implements ApplicationListener { protected static ClosingScreen closingScreen; protected static TransitionScreen transitionScreen; public static KeyInputAdapter keyInputAdapter; - private static boolean exited; + private static boolean exited, initialized; public boolean needsUpdate = false; public static boolean advStartup = false; public static boolean safeToClose = true; @@ -199,7 +199,8 @@ the app again (seems it doesnt dispose correctly...?!?) } else { skinName = "default"; //use default skin if preferences file doesn't exist yet } - FSkin.loadLight(skinName, splashScreen); + if (!initialized) + FSkin.loadLight(skinName, getSplashScreen()); textureFiltering = getForgePreferences().getPrefBoolean(FPref.UI_LIBGDX_TEXTURE_FILTERING); showFPS = getForgePreferences().getPrefBoolean(FPref.UI_SHOW_FPS); @@ -223,49 +224,45 @@ else if (getForgePreferences().getPref(FPref.UI_ENABLE_BORDER_MASKING).equals("f if (totalDeviceRAM > 5000) //devices with more than 10GB RAM will have 600 Cache size, 400 Cache size for morethan 5GB RAM cacheSize = totalDeviceRAM > 10000 ? 600 : 400; } - //init cache - ImageCache.initCache(cacheSize); + if (!initialized) { + initialized = true; + + Runnable runnable = () -> { + safeToClose = false; + ImageKeys.setIsLibGDXPort(GuiBase.getInterface().isLibgdxPort()); + FModel.initialize(getSplashScreen().getProgressBar(), null); + + getSplashScreen().getProgressBar().setDescription(getLocalizer().getMessage("lblLoadingFonts")); + FSkinFont.preloadAll(locale); + + getSplashScreen().getProgressBar().setDescription(getLocalizer().getMessage("lblLoadingCardTranslations")); + CardTranslation.preloadTranslation(locale, ForgeConstants.LANG_DIR); + + getSplashScreen().getProgressBar().setDescription(getLocalizer().getMessage("lblFinishingStartup")); + + //add reminder to preload + if (enablePreloadExtendedArt) { + if (autoCache) + getSplashScreen().getProgressBar().setDescription(getLocalizer().getMessage("lblPreloadExtendedArt") + "\nDetected RAM: " + totalDeviceRAM + "MB. Cache size: " + cacheSize); + else + getSplashScreen().getProgressBar().setDescription(getLocalizer().getMessage("lblPreloadExtendedArt")); + } else { + if (autoCache) + getSplashScreen().getProgressBar().setDescription(getLocalizer().getMessage("lblFinishingStartup") + "\nDetected RAM: " + totalDeviceRAM + "MB. Cache size: " + cacheSize); + else + getSplashScreen().getProgressBar().setDescription(getLocalizer().getMessage("lblFinishingStartup")); + } - //load model on background thread (using progress bar to report progress) - FThreads.invokeInBackgroundThread(() -> { + Gdx.app.postRunnable(() -> { + afterDbLoaded(); + /* call preloadExtendedArt here, if we put it above we will * + * get error: No OpenGL context found in the current thread. */ + preloadExtendedArt(); + }); + }; //see if app or assets need updating - AssetsDownloader.checkForUpdates(splashScreen); - if (exited) { - return; - } //don't continue if user chose to exit or couldn't download required assets - - safeToClose = false; - ImageKeys.setIsLibGDXPort(GuiBase.getInterface().isLibgdxPort()); - FModel.initialize(splashScreen.getProgressBar(), null); - - splashScreen.getProgressBar().setDescription(getLocalizer().getMessage("lblLoadingFonts")); - FSkinFont.preloadAll(locale); - - splashScreen.getProgressBar().setDescription(getLocalizer().getMessage("lblLoadingCardTranslations")); - CardTranslation.preloadTranslation(locale, ForgeConstants.LANG_DIR); - - splashScreen.getProgressBar().setDescription(getLocalizer().getMessage("lblFinishingStartup")); - - //add reminder to preload - if (enablePreloadExtendedArt) { - if (autoCache) - splashScreen.getProgressBar().setDescription(getLocalizer().getMessage("lblPreloadExtendedArt") + "\nDetected RAM: " + totalDeviceRAM + "MB. Cache size: " + cacheSize); - else - splashScreen.getProgressBar().setDescription(getLocalizer().getMessage("lblPreloadExtendedArt")); - } else { - if (autoCache) - splashScreen.getProgressBar().setDescription(getLocalizer().getMessage("lblFinishingStartup") + "\nDetected RAM: " + totalDeviceRAM + "MB. Cache size: " + cacheSize); - else - splashScreen.getProgressBar().setDescription(getLocalizer().getMessage("lblFinishingStartup")); - } - - Gdx.app.postRunnable(() -> { - afterDbLoaded(); - /* call preloadExtendedArt here, if we put it above we will * - * get error: No OpenGL context found in the current thread. */ - preloadExtendedArt(); - }); - }); + FThreads.invokeInBackgroundThread(() -> AssetsDownloader.checkForUpdates(exited, runnable)); + } } public static boolean hasGamepad() { //Classic Mode Various Screen GUI are not yet supported, needs control mapping for each screens @@ -282,6 +279,11 @@ public static InputProcessor getInputProcessor() { public static Graphics getGraphics() { return graphics; } + public static SplashScreen getSplashScreen() { + if (splashScreen == null) + splashScreen = new SplashScreen(); + return splashScreen; + } public static Scene getCurrentScene() { return currentScene; @@ -447,10 +449,10 @@ public static void setCursor(TextureRegion textureRegion, String name) { String path = "skin/cursor" + name + ".png"; Pixmap pm = new Pixmap(Config.instance().getFile(path)); - if (name == "0") { + if ("0".equals(name)) { cursorA0 = Gdx.graphics.newCursor(pm, 0, 0); setGdxCursor(cursorA0); - } else if (name == "1") { + } else if ("1".equals(name)) { cursorA1 = Gdx.graphics.newCursor(pm, 0, 0); setGdxCursor(cursorA1); } else { @@ -461,20 +463,20 @@ public static void setCursor(TextureRegion textureRegion, String name) { pm.dispose(); return; } - if (!FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ENABLE_MAGNIFIER) && name != "0") + if (!FModel.getPreferences().getPrefBoolean(ForgePreferences.FPref.UI_ENABLE_MAGNIFIER) && !"0".equals(name)) return; //don't change if it's disabled - if (currentScreen != null && !currentScreen.toString().toLowerCase().contains("match") && name != "0") + if (currentScreen != null && !currentScreen.toString().toLowerCase().contains("match") && !"0".equals(name)) return; // cursor indicator should be during matches if (textureRegion == null) { return; } - if (cursor0 != null && name == "0") { + if (cursor0 != null && "0".equals(name)) { setGdxCursor(cursor0); return; - } else if (cursor1 != null && name == "1") { + } else if (cursor1 != null && "1".equals(name)) { setGdxCursor(cursor1); return; - } else if (cursor2 != null && name == "2") { + } else if (cursor2 != null && "2".equals(name)) { setGdxCursor(cursor2); return; } @@ -496,10 +498,10 @@ public static void setCursor(TextureRegion textureRegion, String name) { textureRegion.getRegionWidth(), // The width of the area from the other Pixmap in pixels textureRegion.getRegionHeight() // The height of the area from the other Pixmap in pixels ); - if (name == "0") { + if ("0".equals(name)) { cursor0 = Gdx.graphics.newCursor(pm, 0, 0); setGdxCursor(cursor0); - } else if (name == "1") { + } else if ("1".equals(name)) { cursor1 = Gdx.graphics.newCursor(pm, 0, 0); setGdxCursor(cursor1); } else { @@ -829,8 +831,10 @@ private static void setCurrentScreen(FScreen screen0) { endKeyInput(); //end key input before switching screens ForgeAnimation.endAll(); //end all active animations before switching screens currentScreen = screen0; - currentScreen.setSize(screenWidth, screenHeight); - currentScreen.onActivate(); + if (currentScreen != null) { + currentScreen.setSize(screenWidth, screenHeight); + currentScreen.onActivate(); + } } catch (Exception ex) { graphics.end(); //check if sentry is enabled, if not it will call the gui interface but here we end the graphics so we only send it via sentry.. diff --git a/forge-gui-mobile/src/forge/Graphics.java b/forge-gui-mobile/src/forge/Graphics.java index b7ca391d9bf..bc6b01fe8ec 100644 --- a/forge-gui-mobile/src/forge/Graphics.java +++ b/forge-gui-mobile/src/forge/Graphics.java @@ -43,10 +43,10 @@ public class Graphics { private int failedClipCount; private float alphaComposite = 1; private int transformCount = 0; - private final ShaderProgram shaderOutline = new ShaderProgram(Gdx.files.internal("shaders").child("outline.vert"), Gdx.files.internal("shaders").child("outline.frag")); - private final ShaderProgram shaderGrayscale = new ShaderProgram(Gdx.files.internal("shaders").child("grayscale.vert"), Gdx.files.internal("shaders").child("grayscale.frag")); - private final ShaderProgram shaderWarp = new ShaderProgram(Gdx.files.internal("shaders").child("grayscale.vert"), Gdx.files.internal("shaders").child("warp.frag")); - private final ShaderProgram shaderUnderwater = new ShaderProgram(Gdx.files.internal("shaders").child("grayscale.vert"), Gdx.files.internal("shaders").child("underwater.frag")); + private final ShaderProgram shaderOutline = new ShaderProgram(Shaders.outlineVert, Shaders.outlineFrag); + private final ShaderProgram shaderGrayscale = new ShaderProgram(Shaders.grayscaleVert, Shaders.grayscaleFrag); + private final ShaderProgram shaderWarp = new ShaderProgram(Shaders.grayscaleVert, Shaders.warpFrag); + private final ShaderProgram shaderUnderwater = new ShaderProgram(Shaders.grayscaleVert, Shaders.underwaterFrag); private final ShaderProgram shaderNightDay = new ShaderProgram(Shaders.vertexShaderDayNight, Shaders.fragmentShaderDayNight); private final ShaderProgram shaderPixelate = new ShaderProgram(Shaders.vertPixelateShader, Shaders.fragPixelateShader); private final ShaderProgram shaderRipple = new ShaderProgram(Shaders.vertPixelateShader, Shaders.fragRipple); diff --git a/forge-gui-mobile/src/forge/Shaders.java b/forge-gui-mobile/src/forge/Shaders.java index beeb90d3396..784517459b9 100644 --- a/forge-gui-mobile/src/forge/Shaders.java +++ b/forge-gui-mobile/src/forge/Shaders.java @@ -1986,4 +1986,174 @@ public class Shaders { " luv.yz = (luv.yz) + (v_color.yz);\n" + " gl_FragColor = vec4(sRGB(clamp(luv2rgb(luv), 0.0, 1.0)), v_color.a * tgt.a);\n" + "}"; + // moved shaders from dirs to here + public static String warpFrag = "#ifdef GL_ES\n" + + "precision mediump float;\n" + + "#endif\n" + + "\n" + + "varying vec2 v_texCoords;\n" + + "uniform sampler2D u_texture;\n" + + "\n" + + "uniform float u_time;\n" + + "uniform float u_speed;\n" + + "uniform float u_amount;\n" + + "uniform vec2 u_viewport;\n" + + "uniform vec2 u_position;\n" + + "\n" + + "float random2d(vec2 n) {\n" + + " return fract(sin(dot(n, vec2(12.9898, 4.1414))) * 43758.5453);\n" + + "}\n" + + "\n" + + "float randomRange (in vec2 seed, in float min, in float max) {\n" + + " return min + random2d(seed) * (max - min);\n" + + "}\n" + + "\n" + + "float insideRange(float v, float bottom, float top) {\n" + + " return step(bottom, v) - step(top, v);\n" + + "}\n" + + "\n" + + "void main()\n" + + "{\n" + + " float time = floor(u_time * u_speed * 60.0);\n" + + "\n" + + " vec3 outCol = texture2D(u_texture, v_texCoords).rgb;\n" + + "\n" + + " float maxOffset = u_amount/2.0;\n" + + " for (float i = 0.0; i < 2.0; i += 1.0) {\n" + + " float sliceY = random2d(vec2(time, 2345.0 + float(i)));\n" + + " float sliceH = random2d(vec2(time, 9035.0 + float(i))) * 0.25;\n" + + " float hOffset = randomRange(vec2(time, 9625.0 + float(i)), -maxOffset, maxOffset);\n" + + " vec2 uvOff = v_texCoords;\n" + + " uvOff.x += hOffset;\n" + + " if (insideRange(v_texCoords.y, sliceY, fract(sliceY+sliceH)) == 1.0){\n" + + " outCol = texture2D(u_texture, uvOff).rgb;\n" + + " }\n" + + " }\n" + + "\n" + + " float maxColOffset = u_amount / 6.0;\n" + + " float rnd = random2d(vec2(time , 9545.0));\n" + + " vec2 colOffset = vec2(randomRange(vec2(time , 9545.0), -maxColOffset, maxColOffset),\n" + + " randomRange(vec2(time , 7205.0), -maxColOffset, maxColOffset));\n" + + " if (rnd < 0.33) {\n" + + " outCol.r = texture2D(u_texture, v_texCoords + colOffset).r;\n" + + " } else if (rnd < 0.66) {\n" + + " outCol.g = texture2D(u_texture, v_texCoords + colOffset).g;\n" + + " } else {\n" + + " outCol.b = texture2D(u_texture, v_texCoords + colOffset).b;\n" + + " }\n" + + "\n" + + " gl_FragColor = vec4(outCol, 1.0);\n" + + "}"; + public static String underwaterFrag = "#ifdef GL_ES\n" + + "#define PRECISION mediump\n" + + "precision PRECISION float;\n" + + "precision PRECISION int;\n" + + "#else\n" + + "#define PRECISION\n" + + "#endif\n" + + "\n" + + "varying vec2 v_texCoords;\n" + + "uniform sampler2D u_texture;\n" + + "uniform float u_amount;\n" + + "uniform float u_speed;\n" + + "uniform float u_time;\n" + + "uniform float u_bias;\n" + + "\n" + + "void main () {\n" + + " vec2 uv = v_texCoords;\n" + + "\n" + + " uv.y += (cos((uv.y + (u_time * 0.04 * u_speed)) * 45.0) * 0.0019 * u_amount) + (cos((uv.y + (u_time * 0.1 * u_speed)) * 10.0) * 0.002 * u_amount);\n" + + "\n" + + " uv.x += (sin((uv.y + (u_time * 0.07 * u_speed)) * 15.0) * 0.0029 * u_amount) + (sin((uv.y + (u_time * 0.1 * u_speed)) * 15.0) * 0.002 * u_amount);\n" + + "\n" + + "\tvec4 texColor = texture2D(u_texture, uv);\n" + + "\n" + + " gl_FragColor = mix(vec4(0.0, 0.0, 0.0, 1.0), texColor, u_bias);\n" + + "}"; + public static String outlineFrag = "#ifdef GL_ES\n" + + "precision mediump float;\n" + + "precision mediump int;\n" + + "#endif\n" + + "\n" + + "uniform sampler2D u_texture;\n" + + "uniform vec2 u_viewportInverse;\n" + + "uniform vec3 u_color;\n" + + "uniform float u_offset;\n" + + "uniform float u_step;\n" + + "\n" + + "varying vec4 v_color;\n" + + "varying vec2 v_texCoord;\n" + + "\n" + + "#define ALPHA_VALUE_BORDER 0.5\n" + + "\n" + + "void main() {\n" + + " vec2 T = v_texCoord.xy;\n" + + "\n" + + " float alpha = 0.0;\n" + + " bool allin = true;\n" + + " for( float ix = -u_offset; ix < u_offset; ix += u_step )\n" + + " {\n" + + " for( float iy = -u_offset; iy < u_offset; iy += u_step )\n" + + " {\n" + + " float newAlpha = texture2D(u_texture, T + vec2(ix, iy) * u_viewportInverse).a;\n" + + " allin = allin && newAlpha > ALPHA_VALUE_BORDER;\n" + + " if (newAlpha > ALPHA_VALUE_BORDER && newAlpha >= alpha)\n" + + " {\n" + + " alpha = newAlpha;\n" + + " }\n" + + " }\n" + + " }\n" + + " if (allin)\n" + + " {\n" + + " alpha = 0.0;\n" + + " }\n" + + "\n" + + " gl_FragColor = vec4(u_color,alpha);\n" + + "}"; + public static String outlineVert = "uniform mat4 u_projTrans;\n" + + "\n" + + "attribute vec4 a_position;\n" + + "attribute vec2 a_texCoord0;\n" + + "attribute vec4 a_color;\n" + + "\n" + + "varying vec4 v_color;\n" + + "varying vec2 v_texCoord;\n" + + "\n" + + "uniform vec2 u_viewportInverse;\n" + + "\n" + + "void main() {\n" + + " gl_Position = u_projTrans * a_position;\n" + + " v_texCoord = a_texCoord0;\n" + + " v_color = a_color;\n" + + "}"; + public static String grayscaleFrag = "#ifdef GL_ES\n" + + "precision mediump float;\n" + + "#endif\n" + + "\n" + + "varying vec4 v_color;\n" + + "varying vec2 v_texCoords;\n" + + "uniform sampler2D u_texture;\n" + + "uniform float u_grayness;\n" + + "uniform float u_bias;\n" + + "\n" + + "void main() {\n" + + " vec4 c = v_color * texture2D(u_texture, v_texCoords);\n" + + " float grey = dot( c.rgb, vec3(0.22, 0.707, 0.071) );\n" + + " vec3 blendedColor = mix(c.rgb, vec3(grey), u_grayness);\n" + + " gl_FragColor = mix(vec4(0.0, 0.0, 0.0, 1.0), vec4(blendedColor.rgb, c.a), u_bias);\n" + + "}"; + public static String grayscaleVert = "attribute vec4 a_position;\n" + + "attribute vec4 a_color;\n" + + "attribute vec2 a_texCoord0;\n" + + "\n" + + "uniform mat4 u_projTrans;\n" + + "\n" + + "varying vec4 v_color;\n" + + "varying vec2 v_texCoords;\n" + + "\n" + + "void main() {\n" + + " v_color = a_color;\n" + + " v_texCoords = a_texCoord0;\n" + + " gl_Position = u_projTrans * a_position;\n" + + "}"; } diff --git a/forge-gui-mobile/src/forge/assets/AssetsDownloader.java b/forge-gui-mobile/src/forge/assets/AssetsDownloader.java index 104840e8f4d..a7b00d4ac43 100644 --- a/forge-gui-mobile/src/forge/assets/AssetsDownloader.java +++ b/forge-gui-mobile/src/forge/assets/AssetsDownloader.java @@ -1,15 +1,18 @@ package forge.assets; -import java.io.File; import java.io.IOException; import java.net.URL; +import java.text.SimpleDateFormat; +import java.util.Calendar; +import java.util.Date; import java.util.List; +import java.util.TimeZone; import com.badlogic.gdx.files.FileHandle; import forge.gui.GuiBase; +import forge.util.TextUtil; import org.apache.commons.lang3.StringUtils; -import com.badlogic.gdx.Application.ApplicationType; import com.badlogic.gdx.Gdx; import com.google.common.collect.ImmutableList; @@ -18,26 +21,39 @@ import forge.gui.download.GuiDownloadZipService; import forge.gui.util.SOptionPane; import forge.localinstance.properties.ForgeConstants; -import forge.screens.SplashScreen; import forge.util.FileUtil; -public class AssetsDownloader { - public static final boolean SHARE_DESKTOP_ASSETS = true; //change to false to test downloading separate assets for desktop version +import static forge.localinstance.properties.ForgeConstants.GITHUB_COMMITS_URL_ATOM; +import static forge.localinstance.properties.ForgeConstants.GITHUB_RELEASES_URL_ATOM; +public class AssetsDownloader { private final static ImmutableList downloadIgnoreExit = ImmutableList.of("Download", "Ignore", "Exit"); private final static ImmutableList downloadExit = ImmutableList.of("Download", "Exit"); //if not sharing desktop assets, check whether assets are up to date - public static void checkForUpdates(final SplashScreen splashScreen) { - if (Gdx.app.getType() == ApplicationType.Desktop && SHARE_DESKTOP_ASSETS) { return; } - + public static void checkForUpdates(boolean exited, Runnable runnable) { + if (exited) + return; final String versionString = Forge.getDeviceAdapter().getVersionString(); + Forge.getSplashScreen().getProgressBar().setDescription("Checking for updates..."); + if (versionString.contains("GIT")) { + if (!GuiBase.isAndroid()) { + run(runnable); + return; + } + } + //currently for desktop/mobile-dev release on github + final String releaseTag = Forge.getDeviceAdapter().getReleaseTag(GITHUB_RELEASES_URL_ATOM); + final String packageSize = GuiBase.isAndroid() ? "160MB" : "270MB"; + final String apkSize = "12MB"; + final boolean isSnapshots = versionString.contains("SNAPSHOT"); final String snapsURL = "https://downloads.cardforge.org/dailysnapshots/"; final String releaseURL = "https://releases.cardforge.org/forge/forge-gui-android/"; final String versionText = isSnapshots ? snapsURL + "version.txt" : releaseURL + "version.txt"; - - splashScreen.getProgressBar().setDescription("Checking for updates..."); + FileHandle assetsDir = Gdx.files.absolute(ForgeConstants.ASSETS_DIR); + FileHandle resDir = Gdx.files.absolute(ForgeConstants.RES_DIR); + boolean mandatory = false; String message; boolean connectedToInternet = Forge.getDeviceAdapter().isConnectedToInternet(); @@ -45,80 +61,171 @@ public static void checkForUpdates(final SplashScreen splashScreen) { try { URL versionUrl = new URL(versionText); String version = FileUtil.readFileToString(versionUrl); - String filename = "forge-android-" + version + "-signed-aligned.apk"; - String apkURL = isSnapshots ? snapsURL + filename : releaseURL + version + "/" + filename; + String filename = ""; + String installerURL = ""; + if (GuiBase.isAndroid()) { + filename = "forge-android-" + version + "-signed-aligned.apk"; + installerURL = isSnapshots ? snapsURL + filename : releaseURL + version + "/" + filename; + } else { + //current release on github is tar.bz2, update this to jar installer in the future... + filename = isSnapshots ? "forge-installer-" + version + ".jar" : releaseTag.replace("forge-", "forge-gui-desktop-") + ".tar.bz2"; + String releaseBZ2URL = "https://github.com/Card-Forge/forge/releases/download/" + releaseTag + "/" + filename; + String snapsBZ2URL = "https://downloads.cardforge.org/dailysnapshots/"; + installerURL = isSnapshots ? snapsBZ2URL : releaseBZ2URL; + } + //TODO build version + /*String buildver = ""; + SimpleDateFormat DateFor = TextUtil.getSimpleDate(); + Calendar calendar = Calendar.getInstance(); + Date buildDateOriginal = null; + try { + FileHandle build = Gdx.files.classpath("build.txt"); + if (build.exists()) { + SimpleDateFormat original = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + original.setTimeZone(TimeZone.getTimeZone("UTC")); + Date buildDate = original.parse(build.readString()); + buildDateOriginal = original.parse(build.readString()); + calendar.setTime(buildDate); + DateFor.setTimeZone(TimeZone.getDefault()); + buildver = "\nForge Build: " + DateFor.format(calendar.getTime()); + } + } catch (Exception e) { + e.printStackTrace(); + }*/ + if (!StringUtils.isEmpty(version) && !versionString.equals(version)) { - splashScreen.prepareForDialogs(); + Forge.getSplashScreen().prepareForDialogs(); message = "A new version of Forge is available (" + version + ").\n" + "You are currently on an older version (" + versionString + ").\n\n" + "Would you like to update to the new version now?"; if (!Forge.getDeviceAdapter().isConnectedToWifi()) { - message += " If so, you may want to connect to wifi first. The download is around 12MB."; + message += " If so, you may want to connect to wifi first. The download is around " + (GuiBase.isAndroid() ? apkSize : packageSize) + "."; + } + if (!GuiBase.isAndroid()) { + message += Forge.getDeviceAdapter().getLatestChanges(GITHUB_COMMITS_URL_ATOM, null, null); } - if (SOptionPane.showConfirmDialog(message, "New Version Available", "Update Now", "Update Later", true, true)) { - String apkFile = new GuiDownloadZipService("", "update", apkURL, - Forge.getDeviceAdapter().getDownloadsDir(), null, splashScreen.getProgressBar()).download(filename); - if (apkFile != null) { - Forge.getDeviceAdapter().openFile(apkFile); + //failed to grab latest github tag + if (!isSnapshots && releaseTag.isEmpty()) { + if (!GuiBase.isAndroid()) + run(runnable); + } else if (SOptionPane.showConfirmDialog(message, "New Version Available", "Update Now", "Update Later", true, true)) { + String installer = new GuiDownloadZipService("", "update", installerURL, + Forge.getDeviceAdapter().getDownloadsDir(), null, Forge.getSplashScreen().getProgressBar()).download(filename); + if (installer != null) { + Forge.getDeviceAdapter().openFile(installer); Forge.isMobileAdventureMode = Forge.advStartup; Forge.exitAnimation(false); return; } - SOptionPane.showOptionDialog("Could not download update. " + - "Press OK to proceed without update.", "Update Failed", null, ImmutableList.of("Ok")); + switch (SOptionPane.showOptionDialog("Could not download update. " + + "Press OK to proceed without update.", "Update Failed", null, ImmutableList.of("Ok"))) { + default: + if (!GuiBase.isAndroid()) { + run(runnable); + return; + } + break; + } } + } else { + if (!GuiBase.isAndroid()) + run(runnable); } - } - catch (Exception e) { + } catch (Exception e) { e.printStackTrace(); + if (!GuiBase.isAndroid()) { + run(runnable); + return; + } } + } else { + if (!GuiBase.isAndroid()) { + run(runnable); + return; + } + } + // non android don't have seperate package to check + if (!GuiBase.isAndroid()) { + run(runnable); + return; } + // Android assets fallback + String build = ""; + String log = ""; //see if assets need updating - if (GuiBase.isAndroid()) { - FileHandle resDir = Gdx.files.absolute(ForgeConstants.RES_DIR); - FileHandle assetsDir = Gdx.files.absolute(ForgeConstants.ASSETS_DIR); - FileHandle advBG = Gdx.files.absolute(ForgeConstants.DEFAULT_SKINS_DIR).child(ForgeConstants.ADV_TEXTURE_BG_FILE); - if (!advBG.exists()) { - FileHandle deleteVersion = assetsDir.child("version.txt"); - if (deleteVersion.exists()) - deleteVersion.delete(); - FileHandle deleteBuild = resDir.child("build.txt"); - if (deleteBuild.exists()) - deleteBuild.delete(); - } + FileHandle advBG = Gdx.files.absolute(ForgeConstants.DEFAULT_SKINS_DIR).child(ForgeConstants.ADV_TEXTURE_BG_FILE); + if (!advBG.exists()) { + FileHandle deleteVersion = assetsDir.child("version.txt"); + if (deleteVersion.exists()) + deleteVersion.delete(); + FileHandle deleteBuild = resDir.child("build.txt"); + if (deleteBuild.exists()) + deleteBuild.delete(); } - File versionFile = new File(ForgeConstants.ASSETS_DIR + "version.txt"); + + FileHandle versionFile = assetsDir.child("version.txt"); if (!versionFile.exists()) { try { - versionFile.createNewFile(); - } - catch (IOException e) { + versionFile.file().createNewFile(); + } catch (IOException e) { e.printStackTrace(); + Forge.isMobileAdventureMode = Forge.advStartup; Forge.exitAnimation(false); //can't continue if this fails return; } - } - else if (versionString.equals(FileUtil.readFileToString(versionFile)) && FSkin.getSkinDir() != null) { + } else if (versionString.equals(FileUtil.readFileToString(versionFile.file())) && FSkin.getSkinDir() != null) { + run(runnable); return; //if version matches what had been previously saved and FSkin isn't requesting assets download, no need to download assets } - splashScreen.prepareForDialogs(); //ensure colors set up for showing message dialogs + FileHandle f = Gdx.files.classpath("build.txt"); + FileHandle t = resDir.child("build.txt"); + if (f.exists() && t.exists()) { + String buildString = f.readString(); + String target = t.readString(); + try { + Date buildDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(buildString); + Date targetDate = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").parse(target); + // if res folder has same build date then continue loading assets + if (buildDate.equals(targetDate) && versionString.equals(FileUtil.readFileToString(versionFile.file()))) { + run(runnable); + return; + } + mandatory = true; + //format to local date + SimpleDateFormat original = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss"); + original.setTimeZone(TimeZone.getTimeZone("UTC")); + targetDate = original.parse(target); + Calendar calendar = Calendar.getInstance(); + calendar.setTime(targetDate); + SimpleDateFormat simpleDate = TextUtil.getSimpleDate(); + simpleDate.setTimeZone(TimeZone.getDefault()); + build += "Installed resources date: " + simpleDate.format(calendar.getTime()) + "\n\n"; + log = Forge.getDeviceAdapter().getLatestChanges(GITHUB_COMMITS_URL_ATOM, null, null); + } catch (Exception e) { + e.printStackTrace(); + } + } - boolean canIgnoreDownload = FSkin.getAllSkins() != null; //don't allow ignoring download if resource files haven't been previously loaded + Forge.getSplashScreen().prepareForDialogs(); //ensure colors set up for showing message dialogs + + boolean canIgnoreDownload = resDir.exists() && FSkin.getAllSkins() != null && !FileUtil.readFileToString(versionFile.file()).isEmpty(); //don't allow ignoring download if resource files haven't been previously loaded + if (mandatory && connectedToInternet) + canIgnoreDownload = false; if (!connectedToInternet) { message = "Updated resource files cannot be downloaded due to lack of internet connection.\n\n"; if (canIgnoreDownload) { message += "You can continue without this download, but you may miss out on card fixes or experience other problems."; - } - else { + } else { message += "You cannot start the app since you haven't previously downloaded these files."; } switch (SOptionPane.showOptionDialog(message, "No Internet Connection", null, ImmutableList.of("Ok"))) { default: { if (!canIgnoreDownload) { + Forge.isMobileAdventureMode = Forge.advStartup; Forge.exitAnimation(false); //exit if can't ignore download } } @@ -128,11 +235,10 @@ else if (versionString.equals(FileUtil.readFileToString(versionFile)) && FSkin.g //prompt user whether they wish to download the updated resource files message = "There are updated resource files to download. " + - "This download is around 50MB, "; + "This download is around " + packageSize + ", "; if (Forge.getDeviceAdapter().isConnectedToWifi()) { message += "which shouldn't take long if your wifi connection is good."; - } - else { + } else { message += "so it's highly recommended that you connect to wifi first."; } final List options; @@ -145,13 +251,18 @@ else if (versionString.equals(FileUtil.readFileToString(versionFile)) && FSkin.g options = downloadExit; } - switch (SOptionPane.showOptionDialog(message, "", null, options)) { + switch (SOptionPane.showOptionDialog(message + build + log, "", null, options)) { case 1: if (!canIgnoreDownload) { + Forge.isMobileAdventureMode = Forge.advStartup; Forge.exitAnimation(false); //exit if can't ignore download + return; + } else { + run(runnable); + return; } - return; case 2: + Forge.isMobileAdventureMode = Forge.advStartup; Forge.exitAnimation(false); return; } @@ -160,7 +271,7 @@ else if (versionString.equals(FileUtil.readFileToString(versionFile)) && FSkin.g boolean allowDeletion = Forge.androidVersion < 30 || GuiBase.isUsingAppDirectory(); String assetURL = isSnapshots ? snapsURL + "assets.zip" : releaseURL + versionString + "/" + "assets.zip"; new GuiDownloadZipService("", "resource files", assetURL, - ForgeConstants.ASSETS_DIR, ForgeConstants.RES_DIR, splashScreen.getProgressBar(), allowDeletion).downloadAndUnzip(); + ForgeConstants.ASSETS_DIR, ForgeConstants.RES_DIR, Forge.getSplashScreen().getProgressBar(), allowDeletion).downloadAndUnzip(); if (allowDeletion) FSkinFont.deleteCachedFiles(); //delete cached font files in case any skin's .ttf file changed @@ -168,18 +279,38 @@ else if (versionString.equals(FileUtil.readFileToString(versionFile)) && FSkin.g //reload light version of skin after assets updated FThreads.invokeInEdtAndWait(() -> { FSkinFont.updateAll(); //update all fonts used by splash screen - FSkin.loadLight(FSkin.getName(), splashScreen); + FSkin.loadLight(FSkin.getName(), Forge.getSplashScreen()); }); //save version string to file once assets finish downloading //so they don't need to be re-downloaded until you upgrade again - FileUtil.writeFile(versionFile, versionString); + if (connectedToInternet) { + if (versionFile.exists()) + FileUtil.writeFile(versionFile.file(), versionString); + } + //final check if temp.zip exists then extraction is not complete... + FileHandle check = assetsDir.child("temp.zip"); + if (check.exists()) { + if (versionFile.exists()) + versionFile.delete(); + check.delete(); + } + // auto restart after update + Forge.isMobileAdventureMode = Forge.advStartup; + Forge.exitAnimation(true); + } - //add restart after assets update - String msg = allowDeletion ? "Resource update finished..." : "Forge misses some files for deletion.\nIf you encounter issues, try deleting the Forge/res folder and/or deleting Forge/cache/fonts folder and try to download and update the assets."; - switch (SOptionPane.showOptionDialog(msg, "", null, ImmutableList.of("Restart"))) { - default: - Forge.exitAnimation(true); + private static void run(Runnable toRun) { + if (toRun != null) { + if (!GuiBase.isAndroid()) { + Forge.getSplashScreen().getProgressBar().setDescription("Loading game resources..."); + } + FThreads.invokeInBackgroundThread(toRun); + return; + } + if (!GuiBase.isAndroid()) { + Forge.isMobileAdventureMode = Forge.advStartup; + Forge.exitAnimation(false); } } } diff --git a/forge-gui/res/languages/de-DE.properties b/forge-gui/res/languages/de-DE.properties index 74042508522..7be69997848 100644 --- a/forge-gui/res/languages/de-DE.properties +++ b/forge-gui/res/languages/de-DE.properties @@ -3436,4 +3436,7 @@ lblHideCollection=Sammlung Ausblenden lblShowCollection=Sammlung Anzeigen lblCracked=Geknackt! lblTake=Nehmen -lblRefund=Erstattung \ No newline at end of file +lblRefund=Erstattung +lblForgeUpdateMessage=Das Update wurde hier heruntergeladen: {0}.\nForge wird nun beendet und führt den Updater aus. +lblRelease=Freigeben +lblSnapshot=Schnappschuss \ No newline at end of file diff --git a/forge-gui/res/languages/en-US.properties b/forge-gui/res/languages/en-US.properties index 5f2ead89cf4..8fff4005151 100644 --- a/forge-gui/res/languages/en-US.properties +++ b/forge-gui/res/languages/en-US.properties @@ -3169,4 +3169,7 @@ lblHideCollection=Hide Collection lblShowCollection=Show Collection lblCracked=Cracked! lblTake=Take -lblRefund=Refund \ No newline at end of file +lblRefund=Refund +lblForgeUpdateMessage=Update has been downloaded here: {0}.\nForge will now exit and run the updater. +lblRelease=Release +lblSnapshot=Snapshot \ No newline at end of file diff --git a/forge-gui/res/languages/es-ES.properties b/forge-gui/res/languages/es-ES.properties index 258f1a5f6b8..102bbc1daaa 100644 --- a/forge-gui/res/languages/es-ES.properties +++ b/forge-gui/res/languages/es-ES.properties @@ -3450,4 +3450,7 @@ lblHideCollection=Ocultar Colección lblShowCollection=Mostrar Colección lblCracked=¡Agrietado! lblTake=Llevar -lblRefund=Reembolso \ No newline at end of file +lblRefund=Reembolso +lblForgeUpdateMessage=La actualización se descargó aquí: {0}.\nForge ahora saldrá y ejecutará el actualizador. +lblRelease=Liberar +lblSnapshot=Instantánea \ No newline at end of file diff --git a/forge-gui/res/languages/fr-FR.properties b/forge-gui/res/languages/fr-FR.properties index 76669bb1446..67d2ecd1d7a 100644 --- a/forge-gui/res/languages/fr-FR.properties +++ b/forge-gui/res/languages/fr-FR.properties @@ -3444,4 +3444,7 @@ lblHideCollection=Masquer la Collection lblShowCollection=Afficher la Collection lblCracked=Fissuré! lblTake=Prendre -lblRefund=Remboursement \ No newline at end of file +lblRefund=Remboursement +lblForgeUpdateMessage=La mise à jour a été téléchargée ici: {0}.\nForge va maintenant quitter et exécuter le programme de mise à jour. +lblRelease=Libérer +lblSnapshot=Instantané \ No newline at end of file diff --git a/forge-gui/res/languages/it-IT.properties b/forge-gui/res/languages/it-IT.properties index 9d08f89df5b..73947da262b 100644 --- a/forge-gui/res/languages/it-IT.properties +++ b/forge-gui/res/languages/it-IT.properties @@ -3442,4 +3442,7 @@ lblHideCollection=Nascondi Collezione lblShowCollection=Mostra Collezione lblCracked=Incrinato! lblTake=Prendere -lblRefund=Rimborso \ No newline at end of file +lblRefund=Rimborso +lblForgeUpdateMessage=L'aggiornamento è stato scaricato qui: {0}.\nForge ora uscirà ed eseguirà l'aggiornamento. +lblRelease=Pubblicazione +lblSnapshot=Istantanea \ No newline at end of file diff --git a/forge-gui/res/languages/ja-JP.properties b/forge-gui/res/languages/ja-JP.properties index 11fb5ebdee6..bf0e2bb66c6 100644 --- a/forge-gui/res/languages/ja-JP.properties +++ b/forge-gui/res/languages/ja-JP.properties @@ -3438,4 +3438,7 @@ lblHideCollection=コレクションを非表示にする lblShowCollection=ショーコレクション lblCracked=ひび割れた! lblTake=取る -lblRefund=返金 \ No newline at end of file +lblRefund=返金 +lblForgeUpdateMessage=アップデートはここにダウンロードされました: {0}。\nForge が終了し、アップデーターが実行されます。 +lblRelease=リリース +lblSnapshot=スナップショット \ No newline at end of file diff --git a/forge-gui/res/languages/pt-BR.properties b/forge-gui/res/languages/pt-BR.properties index 6e28dd5b91f..a6413fe876c 100644 --- a/forge-gui/res/languages/pt-BR.properties +++ b/forge-gui/res/languages/pt-BR.properties @@ -3528,4 +3528,7 @@ lblHideCollection=Ocultar Coleção lblShowCollection=Mostrar Coleção lblCracked=Rachado! lblTake=Pegar -lblRefund=Reembolso \ No newline at end of file +lblRefund=Reembolso +lblForgeUpdateMessage=A atualização foi baixada aqui: {0}.\nO Forge agora sairá e executará o atualizador. +lblRelease=Liberar +lblSnapshot=Instantâneo \ No newline at end of file diff --git a/forge-gui/res/languages/zh-CN.properties b/forge-gui/res/languages/zh-CN.properties index 9b65d372bce..5a5fb9d4c95 100644 --- a/forge-gui/res/languages/zh-CN.properties +++ b/forge-gui/res/languages/zh-CN.properties @@ -3429,4 +3429,7 @@ lblHideCollection=隐藏收藏 lblShowCollection=展会系列 lblCracked=破裂了! lblTake=拿 -lblRefund=退款 \ No newline at end of file +lblRefund=退款 +lblForgeUpdateMessage=更新已在此处下载:{0}。\nForge 现在将退出并运行更新程序。 +lblRelease=发布 +lblSnapshot=快照 \ No newline at end of file diff --git a/forge-gui/src/main/java/forge/download/AutoUpdater.java b/forge-gui/src/main/java/forge/download/AutoUpdater.java index 73907ab5379..008e8563166 100644 --- a/forge-gui/src/main/java/forge/download/AutoUpdater.java +++ b/forge-gui/src/main/java/forge/download/AutoUpdater.java @@ -10,6 +10,8 @@ import java.net.URISyntaxException; import java.net.URL; import java.util.List; +import java.util.concurrent.CompletableFuture; +import java.util.concurrent.ExecutionException; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -57,15 +59,15 @@ public boolean updateAvailable() { return verifyUpdateable(); } - public boolean attemptToUpdate() { + public boolean attemptToUpdate(CompletableFuture cf) { if (!verifyUpdateable()) { return false; } try { - if (downloadUpdate()) { + if (downloadUpdate(cf)) { extractAndRestart(); } - } catch(IOException | URISyntaxException e) { + } catch(IOException | URISyntaxException | ExecutionException | InterruptedException e) { return false; } return true; @@ -86,7 +88,7 @@ private boolean verifyUpdateable() { return false; } else if (updateChannel.equals("none")) { String message = localizer.getMessage("lblYouHaventSetUpdateChannel"); - List options = ImmutableList.of("Cancel", "release", "snapshot"); + List options = ImmutableList.of(localizer.getMessageorUseDefault("lblCancel", "Cancel"), localizer.getMessageorUseDefault("lblRelease", "Release"), localizer.getMessageorUseDefault("lblSnapshot", "Snapshot")); int option = SOptionPane.showOptionDialog(message, localizer.getMessage("lblManualCheck"), null, options, 0); if (option < 1) { return false; @@ -95,14 +97,14 @@ private boolean verifyUpdateable() { } if (buildVersion.contains("SNAPSHOT")) { - if (!updateChannel.equals("snapshot")) { + if (!updateChannel.equalsIgnoreCase(localizer.getMessageorUseDefault("lblSnapshot", "Snapshot"))) { System.out.println("Snapshot build versions must use snapshot update channel to work"); return false; } versionUrlString = SNAPSHOT_VERSION_INDEX + "version.txt"; } else { - if (!updateChannel.equals("release")) { + if (!updateChannel.equalsIgnoreCase(localizer.getMessageorUseDefault("lblRelease", "Release"))) { System.out.println("Release build versions must use release update channel to work"); return false; } @@ -149,16 +151,16 @@ private boolean compareBuildWithLatestChannelVersion() { } private void retrieveVersion() throws MalformedURLException { - if (VERSION_FROM_METADATA && updateChannel.equals("release")) { + if (VERSION_FROM_METADATA && updateChannel.equalsIgnoreCase(localizer.getMessageorUseDefault("lblRelease", "Release"))) { extractVersionFromMavenRelease(); } else { URL versionUrl = new URL(versionUrlString); version = FileUtil.readFileToString(versionUrl); } - if (updateChannel.equals("release")) { + if (updateChannel.equalsIgnoreCase(localizer.getMessageorUseDefault("lblRelease", "Release"))) { packageUrl = RELEASE_VERSION_INDEX + "forge/forge-gui-desktop/" + version + "/forge-gui-desktop-" + version + ".tar.bz2"; } else { - packageUrl = SNAPSHOT_VERSION_INDEX + "forge-gui-desktop-" + version + ".tar.bz2"; + packageUrl = SNAPSHOT_VERSION_INDEX + "forge-installer-" + version + ".jar"; } } @@ -174,15 +176,15 @@ private void extractVersionFromMavenRelease() throws MalformedURLException { } } - private boolean downloadUpdate() throws URISyntaxException, IOException { + private boolean downloadUpdate(CompletableFuture cf) throws URISyntaxException, IOException, ExecutionException, InterruptedException { // TODO Change the "auto" to be more auto. if (isLoading) { // We need to preload enough of a Skins to show a dialog and a button if we're in loading // splashScreen.prepareForDialogs(); return downloadFromBrowser(); } - - String message = localizer.getMessage("lblNewVersionForgeAvailableUpdateConfirm", version, buildVersion); + String log = cf.get(); + String message = localizer.getMessage("lblNewVersionForgeAvailableUpdateConfirm", version, buildVersion) + log; final List options = ImmutableList.of(localizer.getMessage("lblUpdateNow"), localizer.getMessage("lblUpdateLater")); if (SOptionPane.showOptionDialog(message, localizer.getMessage("lblNewVersionAvailable"), null, options, 0) == 0) { return downloadFromForge(); @@ -204,16 +206,16 @@ private boolean downloadFromBrowser() throws URISyntaxException, IOException { } private boolean downloadFromForge() { - System.out.println("Downloading update from " + packageUrl + " to tmp/"); + System.out.println("Downloading update from " + packageUrl + " to Downloads folder"); WaitCallback callback = new WaitCallback() { @Override public void run() { - GuiBase.getInterface().download(new GuiDownloadZipService("Auto Updater", localizer.getMessage("lblNewVersionDownloading"), packageUrl, "tmp/", null, null) { + GuiBase.getInterface().download(new GuiDownloadZipService("Auto Updater", localizer.getMessage("lblNewVersionDownloading"), packageUrl, System.getProperty("user.home") + "/Downloads/", null, null) { @Override public void downloadAndUnzip() { packagePath = download(version + "-upgrade.tar.bz2"); if (packagePath != null) { - extractAndRestart(); + restartAndUpdate(packagePath); } } }, this); @@ -224,7 +226,29 @@ public void downloadAndUnzip() { return false; } - + private void restartAndUpdate(String packagePath) { + if (SOptionPane.showOptionDialog(localizer.getMessage("lblForgeUpdateMessage", packagePath), localizer.getMessage("lblRestart"), null, ImmutableList.of(localizer.getMessage("lblOK")), 0) == 0) { + final Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; + if (desktop != null) { + try { + File installer = new File(packagePath); + if (installer.exists()) { + if (packagePath.endsWith(".jar")) { + installer.setExecutable(true, false); + desktop.open(installer); + } else { + desktop.open(installer.getParentFile()); + } + } + } catch (IOException e) { + e.printStackTrace(); + } + } else { + System.out.println(packagePath); + } + System.exit(0); + } + } private void extractUpdate() { // TODO Something like https://stackoverflow.com/questions/315618/how-do-i-extract-a-tar-file-in-java final Desktop desktop = Desktop.isDesktopSupported() ? Desktop.getDesktop() : null; diff --git a/forge-gui/src/main/java/forge/interfaces/IDeviceAdapter.java b/forge-gui/src/main/java/forge/interfaces/IDeviceAdapter.java index bfa1da0b429..835317e941f 100644 --- a/forge-gui/src/main/java/forge/interfaces/IDeviceAdapter.java +++ b/forge-gui/src/main/java/forge/interfaces/IDeviceAdapter.java @@ -6,6 +6,7 @@ import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; +import java.util.Date; public interface IDeviceAdapter { boolean isConnectedToInternet(); @@ -13,6 +14,8 @@ public interface IDeviceAdapter { boolean isTablet(); String getDownloadsDir(); String getVersionString(); + String getLatestChanges(String commitsAtom, Date buildDateOriginal, Date maxDate); + String getReleaseTag(String releaseAtom); boolean openFile(String filename); void setLandscapeMode(boolean landscapeMode); void preventSystemSleep(boolean preventSleep); diff --git a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java index 9016e25f5f4..77fe392459d 100644 --- a/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java +++ b/forge-gui/src/main/java/forge/localinstance/properties/ForgeConstants.java @@ -25,6 +25,8 @@ import java.util.Map; public final class ForgeConstants { + public static final String GITHUB_RELEASES_URL_ATOM = "https://github.com/Card-Forge/forge/releases.atom"; + public static final String GITHUB_COMMITS_URL_ATOM = "https://github.com/Card-Forge/forge/commits/master.atom"; public static final String PATH_SEPARATOR = File.separator; public static final String ASSETS_DIR = GuiBase.getInterface().getAssetsDir(); public static final String PROFILE_FILE = ASSETS_DIR + "forge.profile.properties";