diff --git a/.circleci/config.yml b/.circleci/config.yml
index 3ebdd89..038e7cf 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -1,11 +1,12 @@
-version: 2
+version: '2.1'
+orbs:
+ android: circleci/android@2.0
jobs:
build:
- working_directory: ~/code
- docker:
- - image: circleci/android:api-29
- environment:
- JVM_OPTS: -Xmx3200m
+ executor:
+ name: android/android-machine
+ resource-class: medium
+ tag: 2021.10.1
steps:
- checkout
- restore_cache:
diff --git a/.gitignore b/.gitignore
index 70c4606..e5582e8 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,5 @@ out
.DS_Store
build
/captures
+apks/
+app/release/
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 1259af3..db063d3 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,123 +1,253 @@
-RELEASE NOTES
-=============
+# CHANGELOG
-2017-01-XX / 1.8.3
----
+Possible tags:
- - Change links from SpaceAPI.net to SpaceDirectory.org
- - Display all webcams
+- [info] An information not directly related to code changes
+- [feature] A new feature or an improvement
+- [bug] A bugfix
+- [change] A change that's neither a feature nor a bugfix
+- [i18n] Internationalization / translation
-2016-07-02 / 1.8.2
----
- - Fix camera and stream url being displayed
- - Fix twitter link to the new url format
+# Unreleased
-2016-05-06 / 1.8.1
----
- - Uses custom API directory end point (https://spaceapi.fixme.ch/directory.json)
- - Allow editing of the API directory end point and the current hackerspace API
- - Add Danish translation (thanks Mikkel)
+# v2.1.2 (2023-10-02)
-2016-04-14 / 1.8
----
+- [feature] Show state "last change" timestamp as localized datetime ([#47])
+- [bug] Use correct mastodon property ([#49])
+- [bug] Fix network error message
+- [change] Drop support for Android 5–7, require at least Android 8
+- [change] Remove old autolinking-workaround for HTC devices ([#48])
- - Supports invalid SSL certificates
- - Allow widget to be resized
- - Add Dutch translation
- - Fix http to https redirection
- - General fixes
+Contributors to this version:
-2014-08-26 / 1.7.4.1
--------
+- cyroxx (@cyroxx)
+- Danilo Bargen (@dbrgn)
- - Fix crash when there's no error message
+Thanks!
-2014-08-07 / 1.7.4
----
+[#47]: https://github.com/spaceapi-community/my-hackerspace/pull/47
+[#48]: https://github.com/spaceapi-community/my-hackerspace/pull/48
+[#49]: https://github.com/spaceapi-community/my-hackerspace/pull/49
- - German translation (thanks to Lokke and Phervieux)
- - Better hs list with alphabetical index
- - Better errors messages
- - Caching for http requests (images, hs directory)
- - Add status message to the widget (thanks Fpletz)
- - Fix bugs: widget updates, ignore ext fields, click from widget
-2013-10-25 / 1.7.3
------
+## v2.1.1 (2023-04-27)
- - Fix regression with widget custom open/close logo
- - Fix order of hackerspaces with different cases
+- [bug] Fix a bug when parsing v14 endpoints that contain a SpaceFED key
+ without a "spacephone" field ([#44])
+- [bug] Temporarily use directory from GitHub directly to speed up loading
+ ([#45])
-2013-09-09 / 1.7.2
------
+Contributors to this version:
- - Better layout for sensors
- - Support more fields for sensors (machines, names, properties)
+- Danilo Bargen (@dbrgn)
-2013-09-06 / 1.7.1
------
+Thanks!
- - Faster http requests (Use DefaultHttpClient instead of HttpURLConnection)
+[#44]: https://github.com/spaceapi-community/my-hackerspace/pull/44
+[#45]: https://github.com/spaceapi-community/my-hackerspace/pull/45
-2013-09-05 / 1.7
------
- - Full support of SpaceAPI 0.13, drops mixed api definition: hackerspaces must comply to the level they declare!
- - Widget transparency preference added (by default transparency is deactivated)
+## v2.1.0 (2023-04-16)
-2013-06-04 / 1.6.1
------
+- [feature] Add information about the app to settings ([#18])
+- [bug] Fix widget crashing on Android 12 ([#24])
+- [bug] Export widget config activity to fix crashing OpenLauncher ([#33])
+- [change] Reverse latitude and longitude displayed on the screen ([#37])
+- [change] Improvements for widget ([#34])
+- [change] Upgrade dependencies, change TargetSDK to 33 ([#42])
- - French translation
- - Fix the widget's image not updating
- - Change to the new spaceapi url
+Contributors to this version:
-2013-01-02 / 1.6
----
+- Danilo Bargen (@dbrgn)
+- Nicco Kunzmann (@niccokunzmann)
+- Adrian Pascu (@adipascu)
+- Frieder Hannenheim (@FriederHannenheim)
- - Better layout in general
- - Use Holo light theme for Android >=3
- - Refresh the current hackerspace
- - Default to 15mn for the Widget
- - Settings button to change the widget interval
- - Fix lat/lon link
- - Fix crash when maps/email app not found
+Thanks!
-2012-10-29 / 1.5.1
------
+[#18]: https://github.com/spaceapi-community/my-hackerspace/pull/18
+[#24]: https://github.com/spaceapi-community/my-hackerspace/pull/24
+[#33]: https://github.com/spaceapi-community/my-hackerspace/pull/33
+[#34]: https://github.com/spaceapi-community/my-hackerspace/pull/34
+[#37]: https://github.com/spaceapi-community/my-hackerspace/pull/37
+[#42]: https://github.com/spaceapi-community/my-hackerspace/pull/42
+[#44]: https://github.com/spaceapi-community/my-hackerspace/pull/44
- - Bug fixes
- - Add a spinner when loading image
- - Faster download
-2012-05-19 / 1.5
----
+## v2.0.2 (2022-08-07)
- - Only download image if there is a change of state (better battery live and reduce network usage)
+Unfortunately the app was pulled down from Google Play by Google due to
+"developer inactivity". Due to the lack of e-mail forwarding, we never noticed
+the warnings, so the app is now gone.
-2012-05-15 / 1.4
----
+To fix this, we had to change the app ID. Additionally, there will be an F-Droid release.
- - Add Cam and Stream links if present
- - Link for adresses opening GMaps
- - Sort Hackerspaces by name
- - Accept untrusted SSL certificates
- - Better error reporting
- - BUGFIX: Theme shoud be correct on all devices/versions
- - BUGFIX: Should work after reboot correctly
+Changes:
-2012-05-08 / 1.3
----
+- [change] Update dependencies
+- [change] Rename package to `io.spaceapi.community.myhackerspace`
+- [info] Add F-Droid metadata
- - White theme by default (may break on samsung devices)
- - Check if network is enabled
- - Handle rotation correctly
-2012-05-06 / 1.2
-2012-05-04 / 1.1
-2012-04-29 / 1.0
----
+## v2.0.1 (2021-05-14)
- - Initial release
+- [bug] Fix refresh button ([#5][i5])
+
+[i5]: https://github.com/spaceapi-community/my-hackerspace/pull/5
+
+
+## v2.0.0 (2021-02-20)
+
+- [info] App was re-released by the SpaceAPI project under a new package name ([#1][i1])
+- [info] GitHub is now at https://github.com/spaceapi-community/my-hackerspace/
+- [info] The app now requires at least Android 5 (API 21) ([#75][i75])
+- [feature] Support for SpaceAPI v14 ([#85][i85])
+- [feature] New app launcher icon ([#3][i3])
+- [feature] More modern icons in app UI ([#74][i74])
+- [bug] Don't save empty data in application state ([#64][i64])
+- [change] Update all domains to spaceapi.io ([#65][i65], [#71][i71])
+- [change] Switch to Java 8 ([#73][i73])
+- [change] Remove MemorizingTrustManager ([#65][i65])
+- [change] Upgrade dependencies ([#69][i69])
+- [change] Switch to CircleCI ([#69][i69])
+- [change] Add support for annotations ([#77][i77])
+- [i18n] Improved translations
+
+[i1]: https://github.com/spaceapi-community/my-hackerspace/pull/1
+[i3]: https://github.com/spaceapi-community/my-hackerspace/pull/3
+[i64]: https://github.com/fixme-lausanne/MyHackerspace/pull/64
+[i65]: https://github.com/fixme-lausanne/MyHackerspace/pull/65
+[i69]: https://github.com/fixme-lausanne/MyHackerspace/pull/69
+[i71]: https://github.com/fixme-lausanne/MyHackerspace/pull/71
+[i73]: https://github.com/fixme-lausanne/MyHackerspace/pull/73
+[i74]: https://github.com/fixme-lausanne/MyHackerspace/pull/74
+[i75]: https://github.com/fixme-lausanne/MyHackerspace/pull/75
+[i77]: https://github.com/fixme-lausanne/MyHackerspace/pull/77
+[i85]: https://github.com/fixme-lausanne/MyHackerspace/pull/85
+
+
+## v1.8.3 (2017-01-XX)
+
+- Change links from SpaceAPI.net to SpaceDirectory.org
+- Display all webcams
+
+
+## v1.8.2 (2016-07-02)
+
+- Fix camera and stream url being displayed
+- Fix twitter link to the new url format
+
+
+## v1.8.1 (2016-05-06)
+
+- Uses custom API directory end point (https://spaceapi.fixme.ch/directory.json)
+- Allow editing of the API directory end point and the current hackerspace API
+- Add Danish translation (thanks Mikkel)
+
+
+## v1.8 (2016-04-14)
+
+- Supports invalid SSL certificates
+- Allow widget to be resized
+- Add Dutch translation
+- Fix http to https redirection
+- General fixes
+
+
+## v1.7.4.1 (2014-08-26)
+
+- Fix crash when there's no error message
+
+
+## v1.7.4 (2014-08-07)
+
+- German translation (thanks to Lokke and Phervieux)
+- Better hs list with alphabetical index
+- Better errors messages
+- Caching for http requests (images, hs directory)
+- Add status message to the widget (thanks Fpletz)
+- Fix bugs: widget updates, ignore ext fields, click from widget
+
+
+## v1.7.3 (2013-10-25)
+
+- Fix regression with widget custom open/close logo
+- Fix order of hackerspaces with different cases
+
+
+## v1.7.2 (2013-09-09)
+
+- Better layout for sensors
+- Support more fields for sensors (machines, names, properties)
+
+
+## v1.7.1 (2013-09-06)
+
+- Faster http requests (Use DefaultHttpClient instead of HttpURLConnection)
+
+
+## v1.7 (2013-09-05)
+
+- Full support of SpaceAPI 0.13, drops mixed api definition: hackerspaces must comply to the level they declare!
+- Widget transparency preference added (by default transparency is deactivated)
+
+
+## v1.6.1 (2013-06-04)
+
+- French translation
+- Fix the widget's image not updating
+- Change to the new spaceapi url
+
+
+## v1.6 (2013-01-02)
+
+- Better layout in general
+- Use Holo light theme for Android >=3
+- Refresh the current hackerspace
+- Default to 15mn for the Widget
+- Settings button to change the widget interval
+- Fix lat/lon link
+- Fix crash when maps/email app not found
+
+
+## v1.5.1 (2012-10-29)
+
+- Bug fixes
+- Add a spinner when loading image
+- Faster download
+
+
+## v1.5 (2012-05-19)
+
+- Only download image if there is a change of state (better battery live and reduce network usage)
+
+
+## v1.4 (2012-05-15)
+
+- Add Cam and Stream links if present
+- Link for adresses opening GMaps
+- Sort Hackerspaces by name
+- Accept untrusted SSL certificates
+- Better error reporting
+- BUGFIX: Theme shoud be correct on all devices/versions
+- BUGFIX: Should work after reboot correctly
+
+
+## v1.3 (2012-05-08)
+
+- White theme by default (may break on samsung devices)
+- Check if network is enabled
+- Handle rotation correctly
+
+
+## v1.2 (2012-05-06)
+
+
+## v1.1 (2012-05-04)
+
+
+## v1.0 (2012-04-29)
+
+- Initial release
diff --git a/MAINTENANCE.md b/MAINTENANCE.md
new file mode 100644
index 0000000..899d04e
--- /dev/null
+++ b/MAINTENANCE.md
@@ -0,0 +1,31 @@
+# Maintenance
+
+This project is maintained by the SpaceAPI community.
+
+## Current Maintainers
+
+Maintainers (may review and merge):
+
+- @dbrgn
+- @niccokunzmann
+
+People with publication rights on Google Play:
+
+- @dbrgn
+
+## Review and Merge Policy
+
+In case there are multiple maintainers, non-trivial changes should be reviewed
+by another team member. A review is requested through the GitHub UI.
+
+If a change is not reviewed within 1-2 weeks, the merge request may be merged
+without a review.
+
+For urgent bugfixes, the review may be skipped.
+
+## Non-Responsive Maintainers
+
+If maintainers are not responding, send a reminder and/or try to contact them
+through other communication channels. If this situation persists for a longer
+time period, other members of the SpaceAPI community can and should take over
+the maintenance of the project.
diff --git a/README.md b/README.md
index 25dde92..566591e 100644
--- a/README.md
+++ b/README.md
@@ -1,39 +1,65 @@
-My Hackerspace
-==============
+
+
+
+
+
-- Status of hackerspaces using the [SpaceAPI](https://spaceapi.io/)
+# My Hackerspace
+
+[](https://circleci.com/gh/spaceapi-community/my-hackerspace)
+
+This is an Android app with the following features:
+
+- Show the opening status of hacker- and makerspaces using the [SpaceAPI](https://spaceapi.io/)
- Show information about the space (contact, location, sensors, ...)
- Status widget, multiple widgets supported
-- Available on [f-droid](https://f-droid.org/repository/browse/?fdid=ch.fixme.status) and [play store](https://play.google.com/store/apps/details?id=ch.fixme.status)
-Master branch: [](https://travis-ci.org/fixme-lausanne/MyHackerspace)
+The app was originally developed in 2012 by [@rorist] from [FIXME Lausanne]. In
+2021, the app was transferred to the [SpaceAPI community repositories] and is
+now mainly being developed by members of [Coredump].
+You can join our [Matrix](https://matrix.org/) chat at `#spaceapi:matrix.coredump.ch`.
-HOW TO COMPILE
-=============
+[@rorist]: https://github.com/rorist
+[FIXME Lausanne]: https://fixme.ch/
+[SpaceAPI community repositories]: https://github.com/spaceapi-community/
+[Coredump]: https://www.coredump.ch/
-First, get the sources.
+
+
- git clone --recursive https://github.com/fixme-lausanne/MyHackerspace.git
- cd MyHackerspace
+## How it works
-Get the 3rd party librairies
+The app will get the list of hackspaces from [https://directory.spaceapi.io](https://directory.spaceapi.io).
+You can then choose the space by its name from a list.
+When the space is chosen, the associated data is retrieved from the space's
+SpaceAPI endpoint (which is registered in the SpaceAPI directory).
+If you would like to add your space to the directory, have a look at
+[the SpaceAPI website](https://spaceapi.io/provide-an-endpoint/).
- git submodule init
- git submodule update
+### The Widget
-Android Studio
---------------
+The image for the widget is specified in the SpaceAPI endpoint JSON.
+Have a look at the [schema documentation](https://spaceapi.io/docs/) to make your
+widget more pretty!
+
+1. `open.icon` - if present, the widget chooses the specific open/closed images
+2. `logo` - the widget chooses the logo of the hackspace to display
+
+## How to Compile
+
+First, get the sources.
+
+ git clone --recursive https://github.com/spaceapi-community/my-hackerspace.git
+ cd my-hackerspace
+
+### Android Studio
With Android Studio, simply open the project directory and you should be set.
-Command Line
-------------
+### Command Line
You can build the project using Gradle.
-You'll first need the Android SDK, and install build tools 21.1.1 which is considered obsolete.
-You can find this version by ticking obsolete in the Android SDK Manager.
-
The following examples use the gradle wrapper script which will automatically
download gradle to your local directory. If you want to use your own
system-wide installation instead, simply replace `./gradlew` commands with
@@ -56,24 +82,4 @@ To see other tasks that gradle offers, run
./gradlew tasks
-LOCAL DIRECTORY
-===============
-
-For testing purposes you can run a local directory using this technique:
-
-* Create a new Android AVD called for instance "android6"
-* Start the AVD from the command line:
- `emulator -avd android6 -shared-net-id 16`
-* Make sure a network interface on your host computer is reachable by this IP:
- `sudo ifconfig eth0 10.0.2.3`
-* On the host, go to the test directory and run the serv.py script:
- `./serv.py`
-* Go in the app preferences and set the OpenSpaceDirectory URL to the following:
- `https://10.0.2.3:8443/directory.json`
-
-TODO
-====
-
-- Auto recognize field types in the API (array, obj, string, etc)
-- Integrate woozzu library as 3rd party
diff --git a/RELEASING.md b/RELEASING.md
new file mode 100644
index 0000000..eab589a
--- /dev/null
+++ b/RELEASING.md
@@ -0,0 +1,21 @@
+# Releasing
+
+Set variables:
+
+ $ export VERSION=X.Y.Z
+ $ export GPG_KEY=20EE002D778AE197EF7D0D2CB993FF98A90C9AB1 # Danilo
+
+Update version numbers:
+
+ $ vim app/build.gradle
+
+Update changelog:
+
+ $ vim CHANGELOG.md
+
+Add the changelog to `metadata/en-US/changelogs/.txt` as well.
+
+Commit & tag:
+
+ $ git commit -S${GPG_KEY} -m "Release v${VERSION}"
+ $ git tag -s -u ${GPG_KEY} v${VERSION} -m "Version ${VERSION}"
diff --git a/apks/MyHackerspace-1.6.1.apk b/apks/MyHackerspace-1.6.1.apk
deleted file mode 100644
index d0d42cb..0000000
Binary files a/apks/MyHackerspace-1.6.1.apk and /dev/null differ
diff --git a/apks/MyHackerspace-1.6.apk b/apks/MyHackerspace-1.6.apk
deleted file mode 100644
index a8bbfd4..0000000
Binary files a/apks/MyHackerspace-1.6.apk and /dev/null differ
diff --git a/apks/MyHackerspace-1.7.3.apk b/apks/MyHackerspace-1.7.3.apk
deleted file mode 100644
index 4d7cf75..0000000
Binary files a/apks/MyHackerspace-1.7.3.apk and /dev/null differ
diff --git a/apks/MyHackerspace-1.7.4.apk b/apks/MyHackerspace-1.7.4.apk
deleted file mode 100644
index 97c69c1..0000000
Binary files a/apks/MyHackerspace-1.7.4.apk and /dev/null differ
diff --git a/apks/MyHackerspace-1.8.2.apk b/apks/MyHackerspace-1.8.2.apk
deleted file mode 100644
index 39c24ce..0000000
Binary files a/apks/MyHackerspace-1.8.2.apk and /dev/null differ
diff --git a/app/build.gradle b/app/build.gradle
index 45dc106..1cd2343 100644
--- a/app/build.gradle
+++ b/app/build.gradle
@@ -1,15 +1,15 @@
apply plugin: 'com.android.application'
android {
- compileSdkVersion 29
- buildToolsVersion "29.0.2"
+ namespace 'io.spaceapi.community.myhackerspace'
defaultConfig {
- applicationId "ch.fixme.status"
- minSdkVersion 21
- targetSdkVersion 29
- versionCode 20
- versionName "1.8.1"
+ applicationId "io.spaceapi.community.myhackerspace"
+ minSdkVersion 26
+ compileSdk 34
+ targetSdkVersion 34
+ versionCode 105
+ versionName "2.1.2"
}
buildTypes {
@@ -18,12 +18,23 @@ android {
proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
}
}
+
compileOptions {
- sourceCompatibility = 1.8
- targetCompatibility = 1.8
+ sourceCompatibility = JavaVersion.VERSION_17
+ targetCompatibility = JavaVersion.VERSION_17
+ }
+
+ lint {
+ warning 'MissingTranslation'
}
}
dependencies {
- implementation "androidx.annotation:annotation:1.1.0"
+ implementation "androidx.annotation:annotation:1.8.0"
+
+ // SpaceAPI Library
+ implementation "io.github.spaceapi-community:spaceapi-kt:0.6.1"
+
+ // JUnit for testing
+ testImplementation 'junit:junit:4.13.2'
}
diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml
index 343f498..7e51668 100644
--- a/app/src/main/AndroidManifest.xml
+++ b/app/src/main/AndroidManifest.xml
@@ -1,17 +1,17 @@
-
+
+ android:launchMode="singleTask"
+ android:exported="true">
@@ -39,20 +40,25 @@
+ android:label="@string/app_name"
+ android:exported="true">
-
+
-
+
diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png
new file mode 100644
index 0000000..c960fff
Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ
diff --git a/app/src/main/java/ch/fixme/status/Parse12.java b/app/src/main/java/ch/fixme/status/Parse12.java
deleted file mode 100644
index 1a6f3b7..0000000
--- a/app/src/main/java/ch/fixme/status/Parse12.java
+++ /dev/null
@@ -1,150 +0,0 @@
-/*
- * Copyright (C) 2012-2017 Aubort Jean-Baptiste (Rorist)
- * Licensed under GNU's GPL 3, see README
- */
-package ch.fixme.status;
-
-import android.util.Log;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.sql.Date;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.HashMap;
-
-public class Parse12 extends ParseGeneric {
-
- public Parse12(JSONObject jsonObject) throws JSONException {
- super(jsonObject);
- }
-
- protected HashMap parse() throws JSONException {
-
- // Mandatory fields
- mResult.put(API_STATUS, mApi.getBoolean(API_STATUS));
- mResult.put(API_NAME, mApi.getString(API_NAME));
- mResult.put(API_URL, mApi.getString(API_URL));
- mResult.put(API_LOGO, mApi.getString(API_LOGO));
-
- // Status icons
- JSONObject icons = mApi.getJSONObject(API_ICON);
- mResult.put(API_ICON + API_ICON_OPEN, icons.getString(API_ICON_OPEN));
- mResult.put(API_ICON + API_ICON_CLOSED,
- icons.getString(API_ICON_CLOSED));
-
- // Status text
- if (!mApi.isNull(API_STATUS_TXT)) {
- mResult.put(API_STATUS_TXT, mApi.getString(API_STATUS_TXT));
- }
-
- // Last change date
- if (!mApi.isNull(API_LASTCHANGE)) {
- Date date = new Date(mApi.getLong(API_LASTCHANGE) * 1000);
- DateFormat formatter = SimpleDateFormat.getDateTimeInstance();
- mResult.put(API_LASTCHANGE, formatter.format(date));
- }
-
- // Location
- if (!mApi.isNull(API_LON) && !mApi.isNull(API_LAT)) {
- mResult.put(API_LON, mApi.getString(API_LON));
- mResult.put(API_LAT, mApi.getString(API_LAT));
- }
- if (!mApi.isNull(API_ADDRESS)) {
- mResult.put(API_ADDRESS, mApi.getString(API_ADDRESS));
- }
-
- // Contact
- if (!mApi.isNull(API_CONTACT)) {
- JSONObject contact = mApi.getJSONObject(API_CONTACT);
-
- // Phone
- if (!contact.isNull(API_PHONE)) {
- mResult.put(API_PHONE, contact.getString(API_PHONE));
- }
- // Twitter
- if (!contact.isNull(API_TWITTER)) {
- mResult.put(API_TWITTER, contact.getString(API_TWITTER));
- }
- // IRC
- if (!contact.isNull(API_IRC)) {
- mResult.put(API_IRC, contact.getString(API_IRC));
- }
- // Email
- if (!contact.isNull(API_EMAIL)) {
- mResult.put(API_EMAIL, contact.getString(API_EMAIL));
- }
- // Mailing-List
- if (!contact.isNull(API_ML)) {
- mResult.put(API_ML, contact.getString(API_ML));
- }
- }
-
- // Sensors
- if (!mApi.isNull(API_SENSORS)) {
- JSONArray sensors = mApi.getJSONArray(API_SENSORS);
- JSONObject elem;
- HashMap>> result = new HashMap<>(sensors.length());
- for (int i = 0; i < sensors.length(); i++) {
- elem = (JSONObject) sensors.get(i);
- try {
- for (int j = 0; j < elem.length(); j++) {
- ArrayList> elem_value = new ArrayList<>();
- String name = (String) elem.names().get(j);
- JSONObject obj = elem.getJSONObject(name);
- for (int k = 0; k < obj.length(); k++) {
- String name2 = (String) obj.names().get(k);
- HashMap elem_value_map = new HashMap<>();
- elem_value_map.put(API_SENSOR_NAME, name2);
- elem_value_map.put(API_SENSOR_VALUE, obj.getString(name2));
- elem_value.add(elem_value_map);
- }
- result.put(name, elem_value);
- }
- } catch (Exception e) {
- Log.e(Main.TAG, e.getLocalizedMessage());
- e.printStackTrace();
- ArrayList> elem_value = new ArrayList<>();
- HashMap elem_value_map = new HashMap<>();
- elem_value_map.put(API_SENSOR_VALUE, elem.toString());
- elem_value.add(elem_value_map);
- result.put((String) elem.names().get(0), elem_value);
- }
- }
- mResult.put(API_SENSORS, result);
- }
-
- if (!mApi.isNull(API_STREAM) || !mApi.isNull(API_CAM)) {
- // Stream
- if (!mApi.isNull(API_STREAM)) {
- JSONObject stream = mApi.optJSONObject(API_STREAM);
- if (stream != null) {
- HashMap streamMap = new HashMap<>(stream.length());
- JSONArray names = stream.names();
- for (int i = 0; i < stream.length(); i++) {
- final String type = names.getString(i);
- final String url = stream.getString(type);
- streamMap.put(type, url);
- }
- mResult.put(API_STREAM, streamMap);
- }
- }
- // Cam
- if (!mApi.isNull(API_CAM)) {
- JSONArray cam = mApi.optJSONArray(API_CAM);
- if (cam != null) {
- HashMap camMap = new HashMap<>(cam.length());
- for (int i = 0; i < cam.length(); i++) {
- camMap.put("http", cam.getString(i));
- }
- mResult.put(API_CAM, camMap);
- }
- }
- }
-
- return mResult;
- }
-}
diff --git a/app/src/main/java/ch/fixme/status/Parse13.java b/app/src/main/java/ch/fixme/status/Parse13.java
deleted file mode 100644
index a1480e6..0000000
--- a/app/src/main/java/ch/fixme/status/Parse13.java
+++ /dev/null
@@ -1,214 +0,0 @@
-/*
- * Copyright (C) 2012-2017 Aubort Jean-Baptiste (Rorist)
- * Licensed under GNU's GPL 3, see README
- */
-package ch.fixme.status;
-
-import android.util.Log;
-
-import org.json.JSONArray;
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.sql.Date;
-import java.text.DateFormat;
-import java.text.SimpleDateFormat;
-import java.util.ArrayList;
-import java.util.HashMap;
-
-public class Parse13 extends ParseGeneric {
-
- public Parse13(JSONObject jsonObject) throws JSONException {
- super(jsonObject);
- }
-
- protected HashMap parse() throws JSONException {
-
- // Mandatory fields
- JSONObject state = mApi.getJSONObject(API_STATE);
- if (!state.isNull(API_STATUS)){
- mResult.put(API_STATUS, state.getBoolean(API_STATUS));
- } else {
- mResult.put(API_STATUS, null);
- }
- mResult.put(API_NAME, mApi.getString(API_NAME));
- mResult.put(API_URL, mApi.getString(API_URL));
- mResult.put(API_LOGO, mApi.getString(API_LOGO));
-
- // Status icons
- if (!state.isNull(API_ICON)) {
- JSONObject icon = state.getJSONObject(API_ICON);
- mResult.put(API_ICON + API_ICON_OPEN, icon.getString(API_ICON_OPEN));
- mResult.put(API_ICON + API_ICON_CLOSED,
- icon.getString(API_ICON_CLOSED));
- }
-
- // Status text
- if (!state.isNull(API_STATE_MESSAGE)) {
- mResult.put(API_STATUS_TXT, state.getString(API_STATE_MESSAGE));
- }
-
- // Last change date
- if (!state.isNull(API_LASTCHANGE)) {
- Date date = new Date(state.getLong(API_LASTCHANGE) * 1000);
- DateFormat formatter = SimpleDateFormat.getDateTimeInstance();
- mResult.put(API_LASTCHANGE, formatter.format(date));
- }
-
- // Duration (FIXME addition)
- if (!state.isNull(API_EXT_DURATION)) {
- mResult.put(API_EXT_DURATION, state.getString(API_EXT_DURATION));
- }
-
- // Location (Mandatory)
- if (!mApi.isNull(API_LOCATION)) {
- JSONObject loc = mApi.getJSONObject(API_LOCATION);
- if (!loc.isNull(API_LON) && !loc.isNull(API_LAT)) {
- mResult.put(API_LON, loc.getString(API_LON));
- mResult.put(API_LAT, loc.getString(API_LAT));
- }
- if (!loc.isNull(API_ADDRESS)) {
- mResult.put(API_ADDRESS, loc.getString(API_ADDRESS));
- }
- }
-
- // Contact
- if (!mApi.isNull(API_CONTACT)) {
- JSONObject contact = mApi.getJSONObject(API_CONTACT);
-
- // Phone
- if (!contact.isNull(API_PHONE)) {
- mResult.put(API_PHONE, contact.getString(API_PHONE));
- }
- // SIP
- if (!contact.isNull(API_SIP)) {
- mResult.put(API_SIP, contact.getString(API_SIP));
- }
- // Twitter
- if (!contact.isNull(API_TWITTER)) {
- mResult.put(API_TWITTER, contact.getString(API_TWITTER));
- }
- // Identica
- if (!contact.isNull(API_IDENTICA)) {
- mResult.put(API_IDENTICA, contact.getString(API_IDENTICA));
- }
- // Foursquare
- if (!contact.isNull(API_FOURSQUARE)) {
- mResult.put(API_FOURSQUARE, contact.getString(API_FOURSQUARE));
- }
- // IRC
- if (!contact.isNull(API_IRC)) {
- mResult.put(API_IRC, contact.getString(API_IRC));
- }
- // Jabber
- if (!contact.isNull(API_JABBER)) {
- mResult.put(API_JABBER, contact.getString(API_JABBER));
- }
- // Email
- if (!contact.isNull(API_EMAIL)) {
- mResult.put(API_EMAIL, contact.getString(API_EMAIL));
- }
- // Mailing-List
- if (!contact.isNull(API_ML)) {
- mResult.put(API_ML, contact.getString(API_ML));
- }
- }
-
- // Sensors
- if (!mApi.isNull(API_SENSORS)) {
- JSONObject sensors = mApi.getJSONObject(API_SENSORS);
- JSONArray names = sensors.names();
- JSONArray elem;
- ArrayList> elem_value;
- HashMap>> result = new HashMap>>(
- sensors.length());
- for (int i = 0; i < names.length(); i++) {
- String sensor_name = names.getString(i);
- if (sensor_name.startsWith(API_EXT) || sensor_name.startsWith(API_RADIATION)) {
- continue;
- }
- elem = sensors.getJSONArray(sensor_name);
- elem_value = new ArrayList<>(elem.length());
- for (int j = 0; j < elem.length(); j++) {
- HashMap elem_value_map = new HashMap<>();
- try {
- JSONObject obj = (JSONObject) elem.get(j);
- if (!obj.isNull(API_SENSOR_VALUE)
- && !"".equals(obj.getString(API_SENSOR_VALUE))) {
- elem_value_map.put(API_SENSOR_VALUE, obj.getString(API_SENSOR_VALUE));
- }
- if (!obj.isNull(API_SENSOR_UNIT)
- && !"".equals(obj.getString(API_SENSOR_UNIT))) {
- elem_value_map.put(API_SENSOR_UNIT, obj.getString(API_SENSOR_UNIT));
- }
- if (!obj.isNull(API_SENSOR_NAME)
- && !"".equals(obj.getString(API_SENSOR_NAME))) {
- elem_value_map.put(API_SENSOR_NAME, obj.getString(API_SENSOR_NAME));
- }
- if (!obj.isNull(API_SENSOR_LOCATION)
- && !"".equals(obj.getString(API_SENSOR_LOCATION))) {
- elem_value_map.put(API_SENSOR_LOCATION, obj.getString(API_SENSOR_LOCATION));
- }
- if (!obj.isNull(API_SENSOR_DESCRIPTION)
- && !"".equals(obj.getString(API_SENSOR_DESCRIPTION))) {
- elem_value_map.put(API_SENSOR_DESCRIPTION, obj.getString(API_SENSOR_DESCRIPTION));
- }
- if (!obj.isNull(API_SENSOR_MACHINES) && obj.getJSONArray(API_SENSOR_MACHINES).length() > 0) {
- elem_value_map.put(API_SENSOR_MACHINES, obj.get(API_SENSOR_MACHINES).toString());
- }
- if (!obj.isNull(API_SENSOR_NAMES) && obj.getJSONArray(API_SENSOR_NAMES).length() > 0) {
- elem_value_map.put(API_SENSOR_NAMES, obj.get(API_SENSOR_NAMES).toString());
- }
- if (!obj.isNull(API_SENSOR_PROPERTIES)) {
-
- JSONObject obj2 = obj.getJSONObject(API_SENSOR_PROPERTIES);
- String prop = "";
- for (int k = 0; k < obj2.length(); k++) {
- String name = (String) obj2.names().get(k);
- JSONObject obj3 = obj2.getJSONObject(name);
- prop += name + ": " + obj3.getString(API_SENSOR_VALUE) + " " + obj3.getString(API_SENSOR_UNIT) + ", ";
- }
- elem_value_map.put(API_SENSOR_PROPERTIES, prop.substring(0, prop.length() - 2));
- }
- } catch (Exception e) {
- Log.e(Main.TAG, e.getMessage());
- elem_value_map.put(API_SENSOR_VALUE, elem.get(j).toString());
- }
- elem_value.add(elem_value_map);
- }
- result.put(sensor_name, elem_value);
- }
- mResult.put(API_SENSORS, result);
- }
-
- // Stream
- if (!mApi.isNull(API_STREAM)) {
- JSONObject stream = mApi.optJSONObject(API_STREAM);
- if (stream != null) {
- HashMap streamMap = new HashMap<>(
- stream.length());
- JSONArray names = stream.names();
- for (int i = 0; i < stream.length(); i++) {
- final String type = names.getString(i);
- final String url = stream.getString(type);
- streamMap.put(type, url);
- }
- mResult.put(API_STREAM, streamMap);
- }
- }
-
- // Cam
- if (!mApi.isNull(API_CAM)) {
- JSONArray cam = mApi.optJSONArray(API_CAM);
- if (cam != null) {
- ArrayList camList = new ArrayList<>(cam.length());
- for (int i = 0; i < cam.length(); i++) {
- camList.add(cam.getString(i));
- }
- mResult.put(API_CAM, camList);
- }
- }
-
- return mResult;
- }
-}
diff --git a/app/src/main/java/ch/fixme/status/ParseGeneric.java b/app/src/main/java/ch/fixme/status/ParseGeneric.java
deleted file mode 100644
index bc21ebf..0000000
--- a/app/src/main/java/ch/fixme/status/ParseGeneric.java
+++ /dev/null
@@ -1,82 +0,0 @@
-/*
- * Copyright (C) 2012-2017 Aubort Jean-Baptiste (Rorist)
- * Licensed under GNU's GPL 3, see README
- */
-package ch.fixme.status;
-
-import org.json.JSONException;
-import org.json.JSONObject;
-
-import java.util.HashMap;
-
-public class ParseGeneric {
- protected HashMap mResult = new HashMap<>();
- protected JSONObject mApi;
-
- protected static final String API_NAME = "space";
- protected static final String API_LON = "lon";
- protected static final String API_LOCATION = "location";
- protected static final String API_LAT = "lat";
- protected static final String API_URL = "url";
- protected static final String API_LEVEL = "api";
- protected static final String API_STATE = "state";
- protected static final String API_STATE_MESSAGE = "message";
- protected static final String API_STATUS_TXT = "status";
- protected static final String API_EXT_DURATION = "ext_duration";
- protected static final String API_ADDRESS = "address";
- protected static final String API_CONTACT = "contact";
- protected static final String API_EMAIL = "email";
- protected static final String API_IRC = "irc";
- protected static final String API_JABBER = "jabber";
- protected static final String API_PHONE = "phone";
- protected static final String API_SIP = "sip";
- protected static final String API_TWITTER = "twitter";
- protected static final String API_IDENTICA = "identica";
- protected static final String API_FOURSQUARE = "foursquare";
- protected static final String API_ML = "ml";
- protected static final String API_STREAM = "stream";
- protected static final String API_CAM = "cam";
- protected static final String API_SENSORS = "sensors";
- protected static final String API_EXT = "ext_";
- protected static final String API_RADIATION = "radiation";
-
- // Sensors
- protected static final String API_SENSOR_VALUE = "value";
- protected static final String API_SENSOR_UNIT = "unit";
- protected static final String API_SENSOR_LOCATION = "location";
- protected static final String API_SENSOR_NAME = "name";
- protected static final String API_SENSOR_DESCRIPTION = "description";
- protected static final String API_SENSOR_MACHINES = "machines";
- protected static final String API_SENSOR_NAMES = "names";
- protected static final String API_SENSOR_PROPERTIES = "properties";
-
- // State
- protected static final String API_DEFAULT = "https://fixme.ch/status.json";
- protected static final String API_ICON = "icon";
- protected static final String API_ICON_OPEN = "open";
- protected static final String API_ICON_CLOSED = "closed";
- protected static final String API_LOGO = "logo";
- protected static final String API_STATUS = "open";
- protected static final String API_LASTCHANGE = "lastchange";
-
- public ParseGeneric(JSONObject jsonObject) {
- mApi = jsonObject;
- }
-
- public ParseGeneric(String jsonString) throws JSONException {
- mApi = new JSONObject(jsonString);
- }
-
- public HashMap getData() throws JSONException {
- if ("0.13".equals(mApi.getString(ParseGeneric.API_LEVEL))) {
- mResult = new Parse13(mApi).parse();
- } else if ("0.12".equals(mApi.getString(ParseGeneric.API_LEVEL))) {
- mResult = new Parse12(mApi).parse();
- } else {
- throw new JSONException("API LEVEL NOT SUPPORTED: "
- + mApi.getString(ParseGeneric.API_LEVEL));
- }
- return mResult;
- }
-
-}
diff --git a/app/src/main/java/io/spaceapi/community/myhackerspace/AboutLayout.java b/app/src/main/java/io/spaceapi/community/myhackerspace/AboutLayout.java
new file mode 100644
index 0000000..5a087d9
--- /dev/null
+++ b/app/src/main/java/io/spaceapi/community/myhackerspace/AboutLayout.java
@@ -0,0 +1,40 @@
+package io.spaceapi.community.myhackerspace;
+
+import android.content.Context;
+import android.util.AttributeSet;
+import android.view.LayoutInflater;
+import android.widget.LinearLayout;
+import android.widget.TextView;
+
+import androidx.annotation.Nullable;
+
+public class AboutLayout extends LinearLayout {
+
+ public AboutLayout(Context context) {
+ super(context);
+ }
+
+ public AboutLayout(Context context, @Nullable AttributeSet attrs) {
+ super(context, attrs);
+ }
+
+ public AboutLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
+ super(context, attrs, defStyleAttr);
+ }
+
+ public AboutLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
+ super(context, attrs, defStyleAttr, defStyleRes);
+ }
+
+ public void init() {
+ TextView version = findViewById(R.id.about_version_text);
+ version.setText(BuildConfig.VERSION_NAME + " (" + Integer.toString(BuildConfig.VERSION_CODE) + ")");
+ }
+
+ public static AboutLayout create(Context context) {
+ LayoutInflater layoutInflater = LayoutInflater.from(context);
+ AboutLayout about = (AboutLayout) layoutInflater.inflate(R.layout.about, null, false);
+ about.init();
+ return about;
+ }
+}
diff --git a/app/src/main/java/ch/fixme/status/Main.java b/app/src/main/java/io/spaceapi/community/myhackerspace/Main.java
similarity index 54%
rename from app/src/main/java/ch/fixme/status/Main.java
rename to app/src/main/java/io/spaceapi/community/myhackerspace/Main.java
index 4581ac0..16f720b 100644
--- a/app/src/main/java/ch/fixme/status/Main.java
+++ b/app/src/main/java/io/spaceapi/community/myhackerspace/Main.java
@@ -1,9 +1,9 @@
/*
* Copyright (C) 2012-2017 Aubort Jean-Baptiste (Rorist)
+ * Copyright (C) 2020-2023 Danilo Bargen (dbrgn)
* Licensed under GNU's GPL 3, see README
*/
-
-package ch.fixme.status;
+package io.spaceapi.community.myhackerspace;
import android.app.Activity;
import android.app.AlertDialog;
@@ -19,7 +19,6 @@
import android.graphics.drawable.AnimationDrawable;
import android.net.ConnectivityManager;
import android.net.NetworkInfo;
-import android.net.Uri;
import android.net.http.HttpResponseCache;
import android.os.AsyncTask;
import android.os.Bundle;
@@ -32,8 +31,6 @@
import android.view.MenuItem;
import android.view.View;
import android.view.ViewGroup;
-import android.widget.AdapterView;
-import android.widget.AdapterView.OnItemClickListener;
import android.widget.ArrayAdapter;
import android.widget.ImageView;
import android.widget.LinearLayout;
@@ -42,6 +39,11 @@
import android.widget.SectionIndexer;
import android.widget.TextView;
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+import androidx.annotation.StringRes;
+import androidx.annotation.UiThread;
+
import com.woozzu.android.util.StringMatcher;
import com.woozzu.android.widget.IndexableListView;
@@ -50,22 +52,37 @@
import java.io.File;
import java.io.IOException;
+import java.time.ZoneId;
+import java.time.ZonedDateTime;
+import java.time.format.DateTimeFormatter;
+import java.time.format.FormatStyle;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
-import java.util.Locale;
-import java.util.Map.Entry;
-import java.util.Set;
+import java.util.Objects;
import java.util.regex.Pattern;
-import androidx.annotation.UiThread;
+import io.spaceapi.SpaceApiParser;
+import io.spaceapi.types.AccountBalance;
+import io.spaceapi.types.Barometer;
+import io.spaceapi.types.BeverageSupply;
+import io.spaceapi.types.DoorLocked;
+import io.spaceapi.types.Humidity;
+import io.spaceapi.types.MemberCount;
+import io.spaceapi.types.NetworkConnection;
+import io.spaceapi.types.PeoplePresent;
+import io.spaceapi.types.PowerConsumption;
+import io.spaceapi.types.Status;
+import io.spaceapi.types.Temperature;
public class Main extends Activity {
+ protected static final String TAG = "MyHackerspace";
// API: https://spaceapi.io/
- protected static final String TAG = "MyHackerspace";
+ static final String API_DEFAULT = "https://fixme.ch/status.json";
+
protected static final String PREF_API_URL_WIDGET = "api_url_widget_";
protected static final String PREF_LAST_WIDGET = "last_widget_";
protected static final String PREF_FORCE_WIDGET = "force_widget_";
@@ -74,14 +91,17 @@ public class Main extends Activity {
protected static final String STATE_URL = "url";
private static final int DIALOG_LOADING = 0;
private static final int DIALOG_LIST = 1;
- private static final String TWITTER = "https://twitter.com/";
- private static final String FOURSQUARE = "https://foursquare.com/v/";
- private static final String MAP_SEARCH = "geo:0,0?q=";
- private static final String MAP_COORD = "geo:%s,%s?z=23&q=%s&";
+ private static final String MAP_COORD = "geo:%s,%s?z=23&q=";
+
+ private final DateTimeFormatter dateTimeFormatter = DateTimeFormatter.ofLocalizedDateTime(FormatStyle.MEDIUM);
+ // Shared preferences
private SharedPreferences mPrefs;
+ // Hashmap with the endpoint URL as key and the endpoint JSON string as value
private HashMap mResultHs;
+ // Contains directory endpoint JSON data as string
public String mResultDir;
+ // The endpoint URL of the currently showing space
private String mApiUrl;
private boolean finishApi = false;
private boolean finishDir = false;
@@ -97,18 +117,25 @@ public class Main extends Activity {
@UiThread
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
+
+ // Load layout
setContentView(R.layout.main);
+
+ // Hide views until loaded
setViewVisibility(false);
+
+ // Load shared prefs
mPrefs = PreferenceManager.getDefaultSharedPreferences(Main.this);
+
+ // Load data
mResultHs = new HashMap<>();
- if (checkNetwork()) {
- Log.d(TAG, "onCreate() intent="+ getIntent().toString());
+ if (hasNetwork()) {
+ Log.d(TAG, "onCreate() intent=" + getIntent().toString());
setCache();
- getHsList(savedInstanceState);
- showHsInfo(getIntent());
+ getHsList();
+ showHsInfo(getIntent(), true);
} else {
- showError(getString(R.string.error_title) + getString(R.string.error_network_title),
- getString(R.string.error_network_msg));
+ showNetworkError();
}
}
@@ -116,7 +143,7 @@ public void onCreate(Bundle savedInstanceState) {
@UiThread
protected void onNewIntent(Intent intent) {
Log.d(TAG, "onNewIntent()=" + intent);
- showHsInfo(intent);
+ showHsInfo(intent, false);
}
@Override
@@ -143,24 +170,22 @@ public boolean onCreateOptionsMenu(Menu menu) {
@Override
public boolean onOptionsItemSelected(MenuItem item) {
- switch (item.getItemId()) {
- case R.id.menu_refresh:
- if (checkNetwork()){
- showHsInfo(getIntent());
+ final int id = item.getItemId();
+ if (id == R.id.menu_refresh) {
+ if (hasNetwork()) {
+ showHsInfo(getIntent(), true);
} else {
- showError(getString(R.string.error_title) + getString(R.string.error_network_title),
- getString(R.string.error_network_msg));
+ showNetworkError();
}
return true;
- case R.id.menu_choose:
+ } else if (id == R.id.menu_choose) {
showDialog(DIALOG_LIST);
return true;
- case R.id.menu_prefs:
+ } else if (id == R.id.menu_prefs) {
startActivity(new Intent(Main.this, Prefs.class));
return true;
- default:
- return super.onOptionsItemSelected(item);
}
+ return super.onOptionsItemSelected(item);
}
@Override
@@ -194,14 +219,14 @@ protected void onSaveInstanceState(Bundle data) {
@Override
protected Dialog onCreateDialog(int id) {
- AlertDialog dialog = null;
+ ProgressDialog dialog = null;
switch (id) {
case DIALOG_LOADING:
dialog = new ProgressDialog(this);
dialog.setCancelable(false);
dialog.setMessage(getString(R.string.msg_loading));
dialog.setCancelable(true);
- ((ProgressDialog) dialog).setIndeterminate(true);
+ dialog.setIndeterminate(true);
break;
case DIALOG_LIST:
return createHsDialog();
@@ -209,27 +234,6 @@ protected Dialog onCreateDialog(int id) {
return dialog;
}
- @Override
- @UiThread
- public void startActivity(Intent intent) {
- // http://stackoverflow.com/questions/13691241/autolink-not-working-on-htc-htclinkifydispatcher
- try {
- /* First attempt at fixing an HTC broken by evil Apple patents. */
- if (intent.getComponent() != null
- && ".HtcLinkifyDispatcherActivity".equals(intent
- .getComponent().getShortClassName()))
- intent.setComponent(null);
- super.startActivity(intent);
- } catch (ActivityNotFoundException e) {
- /*
- * Probably an HTC broken by evil Apple patents. This is not
- * perfect, but better than crashing the whole application.
- */
- Log.e(Main.TAG, e.getMessage());
- super.startActivity(Intent.createChooser(intent, null));
- }
- }
-
private void setViewVisibility(boolean show) {
int visibility1 = View.GONE;
int visibility2 = View.VISIBLE;
@@ -281,7 +285,7 @@ private AlertDialog createHsDialog() {
edit.putString(Prefs.KEY_API_URL, url);
getApiTask = new GetApiTask();
getApiTask.execute(url);
- edit.commit();
+ edit.apply();
setIntent(null);
dismissDialog(DIALOG_LIST);
Log.i(TAG, "Item clicked=" + url + " (" + position + ")");
@@ -296,7 +300,10 @@ private AlertDialog createHsDialog() {
}
}
- private void getHsList(Bundle savedInstanceState) {
+ /**
+ * Fetch the directory and update the `mResultDir` variable.
+ */
+ private void getHsList() {
final Bundle data = (Bundle) getLastNonConfigurationInstance();
if (data == null) {
Log.d(TAG, "getHsList(fresh data)");
@@ -310,35 +317,39 @@ private void getHsList(Bundle savedInstanceState) {
}
}
- private void showHsInfo(Intent intent) {
+ /**
+ * Fetch the endpoint and update the `mApiUrl` and `mResultHs` variables.
+ */
+ private void showHsInfo(@Nullable Intent intent, boolean skipCache) {
final Bundle data = (Bundle) getLastNonConfigurationInstance();
- // Get hackerspace api url
- if(data != null && data.containsKey(STATE_URL)) {
+
+ // Get space endpoint URL
+ if (data != null && data.containsKey(STATE_URL)) {
Log.d(TAG, "showHsInfo(uri from state)");
mApiUrl = data.getString(STATE_URL);
- } else if (intent != null
- && intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
+ } else if (intent != null && intent.hasExtra(AppWidgetManager.EXTRA_APPWIDGET_ID)) {
Log.d(TAG, "showHsInfo(uri from widget intent)");
mApiUrl = mPrefs.getString(
PREF_API_URL_WIDGET
+ intent.getIntExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID),
- ParseGeneric.API_DEFAULT);
+ API_DEFAULT);
} else if (intent != null && intent.hasExtra(STATE_HS)) {
Log.d(TAG, "showHsInfo(uri from intent)");
mApiUrl = intent.getStringExtra(STATE_HS);
} else {
Log.d(TAG, "showHsInfo(uri from prefs)");
- mApiUrl = mPrefs.getString(Prefs.KEY_API_URL, ParseGeneric.API_DEFAULT);
+ mApiUrl = mPrefs.getString(Prefs.KEY_API_URL, API_DEFAULT);
}
- // Get Data
- if(data != null && data.containsKey(STATE_HS)) {
+
+ // Now that we have the URL, fetch the data
+ if (data != null && data.containsKey(STATE_HS)) {
Log.d(TAG, "showHsInfo(data from state)");
finishApi = true;
mResultHs = (HashMap) data.getSerializable(STATE_HS);
populateDataHs();
- } else if(mResultHs.containsKey(mApiUrl)) {
+ } else if(mResultHs.containsKey(mApiUrl) && !skipCache) {
Log.d(TAG, "showHsInfo(data from cache)");
finishApi = true;
populateDataHs();
@@ -352,14 +363,20 @@ private void showHsInfo(Intent intent) {
Widget.UpdateAllWidgets(getApplicationContext(), false);
}
- private boolean checkNetwork() {
- return checkNetwork(getApplicationContext());
+ /**
+ * Return whether the phone has an active network connection or not.
+ */
+ private boolean hasNetwork() {
+ return hasNetwork(getApplicationContext());
}
- protected static boolean checkNetwork(Context ctxt) {
- ConnectivityManager cm = (ConnectivityManager) ctxt
- .getSystemService(Context.CONNECTIVITY_SERVICE);
- NetworkInfo netInfo = cm.getActiveNetworkInfo();
+ /**
+ * Return whether the phone has an active network connection or not.
+ */
+ protected static boolean hasNetwork(Context context) {
+ final ConnectivityManager cm = (ConnectivityManager) context
+ .getSystemService(Context.CONNECTIVITY_SERVICE);
+ final NetworkInfo netInfo = Objects.requireNonNull(cm).getActiveNetworkInfo();
return netInfo != null && netInfo.isConnected();
}
@@ -380,7 +397,7 @@ private void showError(String title, String msg) {
private AlertDialog showError(String title, String msg, boolean ret) {
if (title != null && msg != null) {
AlertDialog dialog = new AlertDialog.Builder(Main.this)
- .setTitle(getString(R.string.error_title) + title)
+ .setTitle(getString(R.string.error_title) + " " + title)
.setMessage(msg)
.setNeutralButton(getString(R.string.ok), null).create();
if (ret) {
@@ -392,6 +409,10 @@ private AlertDialog showError(String title, String msg, boolean ret) {
return null;
}
+ private void showNetworkError() {
+ showError(getString(R.string.error_network_title), getString(R.string.error_network_msg));
+ }
+
private void dismissLoading() {
if (finishApi && finishDir) {
try {
@@ -476,7 +497,7 @@ protected void onPostExecute(String result) {
dismissLoading();
if (mErrorMsg == null) {
mResultHs.put(mUrl, result);
- showHsInfo(getIntent());
+ showHsInfo(getIntent(), false);
} else {
setViewVisibility(false);
showError(mErrorTitle, mErrorMsg);
@@ -540,24 +561,89 @@ private TextView addEntry(LayoutInflater inflater, LinearLayout vg, String value
vg.addView(tv);
return tv;
}
- private TextView addTitle(LayoutInflater inflater, LinearLayout vg, int value) {
+
+ /**
+ * Shortcut function. If the value is not null, add it as an entry.
+ */
+ private @Nullable TextView addEntryIfValueNotNull(
+ @NonNull LayoutInflater inflater,
+ @NonNull LinearLayout vg,
+ @Nullable @StringRes int title,
+ @Nullable Object value
+ ) {
+ if (value != null) {
+ return addEntry(inflater, vg, getString(title) + ": " + value.toString());
+ }
+ return null;
+ }
+
+ private TextView addTitle(LayoutInflater inflater, LinearLayout vg, @StringRes int value) {
return addTitle(inflater, vg, getString(value));
}
- private TextView addTitle(LayoutInflater inflater, LinearLayout vg, String value) {
- TextView title = (TextView) inflater.inflate(R.layout.title, null);
+ private TextView addTitle(LayoutInflater inflater, LinearLayout vg, @NonNull String value) {
+ final TextView title = (TextView) inflater.inflate(R.layout.title, null);
title.setText(value);
vg.addView(title);
inflater.inflate(R.layout.separator, vg);
return title;
}
+ private TextView addSubtitle(LayoutInflater inflater, LinearLayout vg, @StringRes int value) {
+ return addSubtitle(inflater, vg, getString(value));
+ }
+
+ private TextView addSubtitle(LayoutInflater inflater, LinearLayout vg, @NonNull String value) {
+ final TextView title = (TextView) inflater.inflate(R.layout.subtitle, null);
+ title.setText(value);
+ vg.addView(title);
+ return title;
+ }
+
+ /**
+ * Add a sensor with the "sensor" layout.
+ */
+ private void addSensor(
+ @NonNull LayoutInflater inflater,
+ @NonNull LinearLayout vg,
+ @NonNull String value,
+ @Nullable String details1,
+ @Nullable String details2
+ ) {
+ final RelativeLayout rl = (RelativeLayout) inflater.inflate(R.layout.entry_sensor, null);
+ final TextView viewValue = rl.findViewById(R.id.entry_value);
+ final TextView viewDetails1 = rl.findViewById(R.id.entry_details1);
+ final TextView viewDetails2 = rl.findViewById(R.id.entry_details2);
+ viewValue.setText(value);
+ if (details1 != null) {
+ viewDetails1.setText(details1);
+ } else {
+ viewDetails1.setVisibility(View.GONE);
+ }
+ if (details2 != null) {
+ viewDetails2.setText(details2);
+ } else {
+ viewDetails2.setVisibility(View.GONE);
+ }
+ vg.addView(rl);
+ }
+
+ /**
+ * Parse the endpoint JSON and populate UI with parsed information.
+ */
private void populateDataHs() {
try {
Log.i(TAG, "populateDataHs()=" + mApiUrl);
setViewVisibility(false);
- HashMap data = new ParseGeneric(mResultHs.get(mApiUrl))
- .getData();
+
+ // Look up endpoint JSOn
+ final String endpointJson = mResultHs.get(mApiUrl);
+ if (endpointJson == null) {
+ throw new IllegalStateException("Endpoint JSON not found");
+ }
+
+ // Parse the JSON string using the `spaceapi-kt` library.
+ final Status data = SpaceApiParser.parseString(endpointJson);
// Initialize views
LayoutInflater iftr = getLayoutInflater();
@@ -567,234 +653,261 @@ private void populateDataHs() {
scroll.addView(vg);
// Mandatory fields
- ((TextView) findViewById(R.id.space_name)).setText((String) data
- .get(ParseGeneric.API_NAME));
- ((TextView) findViewById(R.id.space_url)).setText((String) data
- .get(ParseGeneric.API_URL));
+ ((TextView) findViewById(R.id.space_name)).setText(data.space);
+ ((TextView) findViewById(R.id.space_url)).setText(data.url.toString());
getImageTask = new GetImage(R.id.space_image);
- getImageTask.execute((String) data.get(ParseGeneric.API_LOGO));
+ getImageTask.execute(data.logo);
// Status text
String status_txt;
- if (data.get(ParseGeneric.API_STATUS) == null) {
+ if (data.state == null) {
status_txt = getString(R.string.status_unknown);
((TextView) findViewById(R.id.status_txt))
- .setCompoundDrawablesWithIntrinsicBounds(
- android.R.drawable.presence_invisible, 0, 0, 0);
- } else if ((Boolean) data.get(ParseGeneric.API_STATUS)) {
+ .setCompoundDrawablesWithIntrinsicBounds(
+ android.R.drawable.presence_invisible, 0, 0, 0);
+ } else if (Boolean.TRUE.equals(data.state.open)) {
status_txt = getString(R.string.status_open);
((TextView) findViewById(R.id.status_txt))
- .setCompoundDrawablesWithIntrinsicBounds(
- android.R.drawable.presence_online, 0, 0, 0);
+ .setCompoundDrawablesWithIntrinsicBounds(
+ android.R.drawable.presence_online, 0, 0, 0);
} else {
status_txt = getString(R.string.status_closed);
((TextView) findViewById(R.id.status_txt))
- .setCompoundDrawablesWithIntrinsicBounds(
- android.R.drawable.presence_busy, 0, 0, 0);
+ .setCompoundDrawablesWithIntrinsicBounds(
+ android.R.drawable.presence_busy, 0, 0, 0);
}
- if (data.containsKey(ParseGeneric.API_STATUS_TXT)) {
- status_txt += ": "
- + data.get(ParseGeneric.API_STATUS_TXT);
+ if (data.state.message != null) {
+ status_txt += ": " + data.state.message;
}
((TextView) findViewById(R.id.status_txt)).setText(status_txt);
// Status last change
- if (data.containsKey(ParseGeneric.API_LASTCHANGE)) {
- addEntry(iftr, vg, getString(R.string.api_lastchange) + " "
- + data.get(ParseGeneric.API_LASTCHANGE));
- }
-
- // Status duration
- if (data.containsKey(ParseGeneric.API_EXT_DURATION)
- && data.get(ParseGeneric.API_STATUS) != null
- && (Boolean) data.get(ParseGeneric.API_STATUS)) {
- addEntry(iftr, vg, getString(R.string.api_duration) + " "
- + data.get(ParseGeneric.API_EXT_DURATION)
- + getString(R.string.api_duration_hours));
+ if (data.state != null && data.state.lastchange != null) {
+ final ZonedDateTime lastchange = data.state.lastchange.atZone(ZoneId.systemDefault());
+ final String lastchangeString = lastchange.format(this.dateTimeFormatter);
+ addEntry(iftr, vg, getString(R.string.api_lastchange) + " " + lastchangeString);
}
// Location
- Pattern ptn = Pattern.compile("^.*$", Pattern.DOTALL);
- if (data.containsKey(ParseGeneric.API_ADDRESS)
- || data.containsKey(ParseGeneric.API_LON)) {
-
- addTitle(iftr, vg, R.string.api_location);
-
- // Address
- if (data.containsKey(ParseGeneric.API_ADDRESS)) {
- TextView tv = addEntry(iftr, vg, (String) data.get(ParseGeneric.API_ADDRESS));
- Linkify.addLinks(tv, ptn, MAP_SEARCH);
- }
- // Lon/Lat
- if (data.containsKey(ParseGeneric.API_LON)
- && data.containsKey(ParseGeneric.API_LAT)) {
- TextView tv = addEntry(iftr, vg, data.get(ParseGeneric.API_LON) + ", "
- + data.get(ParseGeneric.API_LAT));
- String addr = (data.containsKey(ParseGeneric.API_ADDRESS)) ? (String) data
- .get(ParseGeneric.API_ADDRESS) : getString(R.string.empty);
- Linkify.addLinks(tv, ptn, String.format(MAP_COORD,
- data.get(ParseGeneric.API_LAT), data.get(ParseGeneric.API_LON), addr));
- }
+ addTitle(iftr, vg, R.string.api_location);
+ TextView latLonTv = addEntry(iftr, vg, data.location.lat + ", " + data.location.lon);
+ Linkify.addLinks(
+ latLonTv,
+ Pattern.compile("^.*$", Pattern.DOTALL),
+ String.format(MAP_COORD, data.location.lat, data.location.lon)
+ );
+
+ // Postal address
+ if (data.location.address != null) {
+ addTitle(iftr, vg, R.string.api_postal_addr);
+ addEntry(iftr, vg, data.location.address);
}
// Contact
- if (data.containsKey(ParseGeneric.API_PHONE)
- || data.containsKey(ParseGeneric.API_TWITTER)
- || data.containsKey(ParseGeneric.API_IRC)
- || data.containsKey(ParseGeneric.API_EMAIL)
- || data.containsKey(ParseGeneric.API_ML)) {
-
- addTitle(iftr, vg, R.string.api_contact);
-
- if (data.containsKey(ParseGeneric.API_PHONE)) {
- addEntry(iftr, vg, (String) data.get(ParseGeneric.API_PHONE));
- }
- if (data.containsKey(ParseGeneric.API_SIP)) {
- addEntry(iftr, vg, (String) data.get(ParseGeneric.API_SIP));
- }
- if (data.containsKey(ParseGeneric.API_TWITTER)) {
- addEntry(iftr, vg, TWITTER
- + ((String) data.get(ParseGeneric.API_TWITTER)).replace("@", ""));
+ addTitle(iftr, vg, R.string.api_contact);
+ addEntryIfValueNotNull(iftr, vg, R.string.api_contact_phone, data.contact.phone);
+ addEntryIfValueNotNull(iftr, vg, R.string.api_contact_sip, data.contact.sip);
+ addEntryIfValueNotNull(iftr, vg, R.string.api_contact_email, data.contact.email);
+ addEntryIfValueNotNull(iftr, vg, R.string.api_contact_ml, data.contact.ml);
+ addEntryIfValueNotNull(iftr, vg, R.string.api_contact_irc, data.contact.irc);
+ addEntryIfValueNotNull(iftr, vg, R.string.api_contact_twitter, data.contact.twitter);
+ addEntryIfValueNotNull(iftr, vg, R.string.api_contact_identica, data.contact.identica);
+ addEntryIfValueNotNull(iftr, vg, R.string.api_contact_mastodon, data.contact.mastodon);
+ addEntryIfValueNotNull(iftr, vg, R.string.api_contact_facebook, data.contact.facebook); // Eeeew!
+ addEntryIfValueNotNull(iftr, vg, R.string.api_contact_foursquare, data.contact.foursquare);
+ addEntryIfValueNotNull(iftr, vg, R.string.api_contact_xmpp, data.contact.xmpp);
+ addEntryIfValueNotNull(iftr, vg, R.string.api_contact_gopher, data.contact.gopher);
+ addEntryIfValueNotNull(iftr, vg, R.string.api_contact_matrix, data.contact.matrix);
+ addEntryIfValueNotNull(iftr, vg, R.string.api_contact_mumble, data.contact.mumble);
+
+ // Sensors: Space
+ if (data.sensors != null && (
+ data.sensors.people_now_present.length > 0
+ || data.sensors.door_locked.length > 0
+ || data.sensors.beverage_supply.length > 0
+ || data.sensors.power_consumption.length > 0
+ || data.sensors.network_connections.length > 0
+ /*|| data.sensors.network_traffic.length > 0*/
+ )) {
+ addTitle(iftr, vg, getString(R.string.api_sensors) + ": " + getString(R.string.api_sensors_space));
+
+ // People present
+ if (data.sensors.people_now_present.length > 0) {
+ addSubtitle(iftr, vg, R.string.api_sensor_people_now_present);
+ for (PeoplePresent entry : data.sensors.people_now_present) {
+ addSensor(
+ iftr,
+ vg,
+ String.format("%d", entry.value),
+ Utils.joinStrings(" / ", entry.location, entry.name),
+ entry.description
+ );
+ }
}
- if (data.containsKey(ParseGeneric.API_IDENTICA)) {
- addEntry(iftr, vg, (String) data.get(ParseGeneric.API_IDENTICA));
+
+ // Door status
+ if (data.sensors.door_locked.length > 0) {
+ addSubtitle(iftr, vg, R.string.api_sensor_door_locked);
+ for (DoorLocked entry : data.sensors.door_locked) {
+ addSensor(
+ iftr,
+ vg,
+ getString(entry.value ? R.string.api_sensor_door_locked_yes : R.string.api_sensor_door_locked_no),
+ Utils.joinStrings(" / ", entry.location, entry.name),
+ entry.description
+ );
+ }
}
- if (data.containsKey(ParseGeneric.API_FOURSQUARE)) {
- addEntry(iftr, vg, FOURSQUARE + data.get(ParseGeneric.API_FOURSQUARE));
+
+ // Beverage supply
+ if (data.sensors.beverage_supply.length > 0) {
+ addSubtitle(iftr, vg, R.string.api_sensor_beverage_supply);
+ for (BeverageSupply entry : data.sensors.beverage_supply) {
+ addSensor(
+ iftr,
+ vg,
+ String.format("%.1f %s", entry.value, entry.unit),
+ Utils.joinStrings(" / ", entry.location, entry.name),
+ entry.description
+ );
+ }
}
- if (data.containsKey(ParseGeneric.API_IRC)) {
- addEntry(iftr, vg, (String) data.get(ParseGeneric.API_IRC));
+
+ // Power consumption
+ if (data.sensors.power_consumption.length > 0) {
+ addSubtitle(iftr, vg, R.string.api_sensor_power_consumption);
+ for (PowerConsumption entry : data.sensors.power_consumption) {
+ addSensor(
+ iftr,
+ vg,
+ String.format("%.1f %s", entry.value, entry.unit),
+ Utils.joinStrings(" / ", entry.location, entry.name),
+ entry.description
+ );
+ }
}
- if (data.containsKey(ParseGeneric.API_EMAIL)) {
- addEntry(iftr, vg, (String) data.get(ParseGeneric.API_EMAIL));
+
+ // Network connections
+ if (data.sensors.network_connections.length > 0) {
+ addSubtitle(iftr, vg, R.string.api_sensor_network_connections);
+ for (NetworkConnection entry : data.sensors.network_connections) {
+ String details = Utils.joinStrings(" / ", entry.location, entry.name);
+ if (details != null && entry.type != null) {
+ details += " (" + entry.type + ")";
+ } else if (entry.type != null) {
+ details = entry.type;
+ }
+ addSensor(
+ iftr,
+ vg,
+ String.format("%d", entry.value),
+ details,
+ entry.description
+ );
+ }
}
- if (data.containsKey(ParseGeneric.API_JABBER)) {
- addEntry(iftr, vg, (String) data.get(ParseGeneric.API_JABBER));
+ }
+
+ // Sensors: Environment
+ if (data.sensors != null && (
+ data.sensors.temperature.length > 0
+ || data.sensors.humidity.length > 0
+ || data.sensors.barometer.length > 0
+ || data.sensors.wind.length > 0
+ )) {
+ addTitle(iftr, vg, getString(R.string.api_sensors) + ": " + getString(R.string.api_sensors_environment));
+
+ // Temperature
+ if (data.sensors.temperature.length > 0) {
+ addSubtitle(iftr, vg, R.string.api_sensor_temperature);
+ for (Temperature entry : data.sensors.temperature) {
+ addSensor(
+ iftr,
+ vg,
+ String.format("%.1f %s", entry.value, entry.unit),
+ Utils.joinStrings(" / ", entry.location, entry.name),
+ entry.description
+ );
+ }
}
- if (data.containsKey(ParseGeneric.API_ML)) {
- addEntry(iftr, vg, (String) data.get(ParseGeneric.API_ML));
+
+ // Humidity
+ if (data.sensors.humidity.length > 0) {
+ addSubtitle(iftr, vg, R.string.api_sensor_humidity);
+ for (Humidity entry : data.sensors.humidity) {
+ addSensor(
+ iftr,
+ vg,
+ String.format("%.1f %s", entry.value, entry.unit),
+ Utils.joinStrings(" / ", entry.location, entry.name),
+ entry.description
+ );
+ }
}
- }
- // Sensors
- if (data.containsKey(ParseGeneric.API_SENSORS)) {
-
- addTitle(iftr, vg, R.string.api_sensors);
-
- HashMap>> sensors =
- (HashMap>>)
- data.get(ParseGeneric.API_SENSORS);
- Set names = sensors.keySet();
- for (String name : names) {
-
- // Subtitle
- String name_title = name.toLowerCase(Locale.getDefault()).replace("_", " ");
- name_title = name_title.substring(0, 1).toUpperCase(Locale.getDefault())
- + name_title.substring(1, name_title.length());
- TextView subtitle = (TextView) iftr.inflate(
- R.layout.subtitle, null);
- subtitle.setText(name_title);
- vg.addView(subtitle);
-
- // Sensors data
- ArrayList> sensorsData = sensors
- .get(name);
- for (HashMap elem : sensorsData) {
- RelativeLayout rl = (RelativeLayout) iftr.inflate(
- R.layout.entry_sensor, null);
- if (elem.containsKey(ParseGeneric.API_SENSOR_VALUE)) {
- ((TextView) rl.findViewById(R.id.entry_value))
- .setText(elem.get(ParseGeneric.API_SENSOR_VALUE));
- } else {
- rl.findViewById(R.id.entry_value).setVisibility(
- View.GONE);
- }
- if (elem.containsKey(ParseGeneric.API_SENSOR_UNIT)) {
- ((TextView) rl.findViewById(R.id.entry_unit))
- .setText(elem.get(ParseGeneric.API_SENSOR_UNIT));
- } else {
- rl.findViewById(R.id.entry_unit).setVisibility(
- View.GONE);
- }
- if (elem.containsKey(ParseGeneric.API_SENSOR_NAME)) {
- ((TextView) rl.findViewById(R.id.entry_name))
- .setText(elem.get(ParseGeneric.API_SENSOR_NAME));
- } else {
- rl.findViewById(R.id.entry_name).setVisibility(
- View.GONE);
- }
- if (elem.containsKey(ParseGeneric.API_SENSOR_LOCATION)) {
- ((TextView) rl.findViewById(R.id.entry_location))
- .setText(elem
- .get(ParseGeneric.API_SENSOR_LOCATION));
- } else {
- rl.findViewById(R.id.entry_location).setVisibility(
- View.GONE);
- }
- if (elem.containsKey(ParseGeneric.API_SENSOR_DESCRIPTION)) {
- ((TextView) rl.findViewById(R.id.entry_description))
- .setText(elem
- .get(ParseGeneric.API_SENSOR_DESCRIPTION));
- } else {
- rl.findViewById(R.id.entry_description)
- .setVisibility(View.GONE);
- }
- if (elem.containsKey(ParseGeneric.API_SENSOR_PROPERTIES)) {
- ((TextView) rl.findViewById(R.id.entry_properties))
- .setText(elem
- .get(ParseGeneric.API_SENSOR_PROPERTIES));
- } else {
- rl.findViewById(R.id.entry_properties)
- .setVisibility(View.GONE);
- }
- if (elem.containsKey(ParseGeneric.API_SENSOR_MACHINES)) {
- ((TextView) rl.findViewById(R.id.entry_other))
- .setText(elem
- .get(ParseGeneric.API_SENSOR_MACHINES));
- } else if (elem.containsKey(ParseGeneric.API_SENSOR_NAMES)) {
- ((TextView) rl.findViewById(R.id.entry_other))
- .setText(elem.get(ParseGeneric.API_SENSOR_NAMES));
- } else {
- rl.findViewById(R.id.entry_other).setVisibility(
- View.GONE);
- }
- vg.addView(rl);
+ // Air pressure
+ if (data.sensors.barometer.length > 0) {
+ addSubtitle(iftr, vg, R.string.api_sensor_barometer);
+ for (Barometer entry : data.sensors.barometer) {
+ addSensor(
+ iftr,
+ vg,
+ String.format("%.1f %s", entry.value, entry.unit),
+ Utils.joinStrings(" / ", entry.location, entry.name),
+ entry.description
+ );
}
}
+
+ // Missing: Wind (not used by any space right now)
+ // and Radiation (I don't know how to interpret and visualize the measurements in a meaningful way)
}
- // Stream and cam
- if (data.containsKey(ParseGeneric.API_STREAM)
- || data.containsKey(ParseGeneric.API_CAM)) {
-
- addTitle(iftr, vg, R.string.api_stream);
-
- // Stream
- if (data.containsKey(ParseGeneric.API_STREAM)) {
- HashMap stream = (HashMap) data
- .get(ParseGeneric.API_STREAM);
- for (Entry entry : stream.entrySet()) {
- final String type = entry.getKey();
- final String url = entry.getValue();
- TextView tv = addEntry(iftr, vg, url);
- tv.setOnClickListener(new View.OnClickListener() {
- public void onClick(View v) {
- Intent i = new Intent(Intent.ACTION_VIEW);
- i.setDataAndType(Uri.parse(url), type);
- startActivity(i);
- }
- });
+ // Sensors: Organization
+ if (data.sensors != null && (
+ data.sensors.total_member_count.length > 0
+ || data.sensors.account_balance.length > 0
+ )) {
+ addTitle(iftr, vg, getString(R.string.api_sensors) + ": " + getString(R.string.api_sensors_organization));
+
+ // Total Member Count
+ if (data.sensors.total_member_count.length > 0) {
+ addSubtitle(iftr, vg, R.string.api_sensor_total_member_count);
+ for (MemberCount entry : data.sensors.total_member_count) {
+ addSensor(
+ iftr,
+ vg,
+ String.format("%d", entry.value),
+ Utils.joinStrings(" / ", entry.location, entry.name),
+ entry.description
+ );
}
}
- // Cam
- if (data.containsKey(ParseGeneric.API_CAM)) {
- ArrayList cams = (ArrayList) data
- .get(ParseGeneric.API_CAM);
- for (String value : cams) {
- addEntry(iftr, vg, value);
+
+ // Account balance
+ if (data.sensors.account_balance.length > 0) {
+ addSubtitle(iftr, vg, R.string.api_sensor_account_balance);
+ for (AccountBalance entry : data.sensors.account_balance) {
+ addSensor(
+ iftr,
+ vg,
+ String.format("%.2f %s", entry.value, entry.unit),
+ Utils.joinStrings(" / ", entry.location, entry.name),
+ entry.description
+ );
}
}
}
+
+ // Webcams
+ if (data.cam.length > 0) {
+ addTitle(iftr, vg, R.string.api_webcams);
+ for (String url : data.cam) {
+ final TextView tv = addEntry(iftr, vg, url);
+ Linkify.addLinks(tv, Pattern.compile("^.*$", Pattern.DOTALL), null);
+ }
+ }
+
setViewVisibility(true);
} catch (Exception e) {
e.printStackTrace();
diff --git a/app/src/main/java/ch/fixme/status/Net.java b/app/src/main/java/io/spaceapi/community/myhackerspace/Net.java
similarity index 97%
rename from app/src/main/java/ch/fixme/status/Net.java
rename to app/src/main/java/io/spaceapi/community/myhackerspace/Net.java
index 628bac3..f0056c5 100644
--- a/app/src/main/java/ch/fixme/status/Net.java
+++ b/app/src/main/java/io/spaceapi/community/myhackerspace/Net.java
@@ -1,9 +1,9 @@
/*
* Copyright (C) 2012-2017 Aubort Jean-Baptiste (Rorist)
+ * Copyright (C) 2020-2023 Danilo Bargen (dbrgn)
* Licensed under GNU's GPL 3, see README
*/
-
-package ch.fixme.status;
+package io.spaceapi.community.myhackerspace;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
diff --git a/app/src/main/java/ch/fixme/status/Network.java b/app/src/main/java/io/spaceapi/community/myhackerspace/Network.java
similarity index 81%
rename from app/src/main/java/ch/fixme/status/Network.java
rename to app/src/main/java/io/spaceapi/community/myhackerspace/Network.java
index 24f8277..9bb1df2 100644
--- a/app/src/main/java/ch/fixme/status/Network.java
+++ b/app/src/main/java/io/spaceapi/community/myhackerspace/Network.java
@@ -1,8 +1,9 @@
/*
* Copyright (C) 2012-2017 Aubort Jean-Baptiste (Rorist)
+ * Copyright (C) 2020-2023 Danilo Bargen (dbrgn)
* Licensed under GNU's GPL 3, see README
*/
-package ch.fixme.status;
+package io.spaceapi.community.myhackerspace;
import android.content.BroadcastReceiver;
import android.content.Context;
@@ -13,7 +14,7 @@ public class Network extends BroadcastReceiver {
@Override
public void onReceive(Context ctxt, Intent intent) {
- if (Main.checkNetwork(ctxt)) {
+ if (Main.hasNetwork(ctxt)) {
Log.i(Main.TAG, "Update widget on " + intent.getAction());
Widget.UpdateAllWidgets(ctxt, true);
} else {
diff --git a/app/src/main/java/ch/fixme/status/Prefs.java b/app/src/main/java/io/spaceapi/community/myhackerspace/Prefs.java
similarity index 77%
rename from app/src/main/java/ch/fixme/status/Prefs.java
rename to app/src/main/java/io/spaceapi/community/myhackerspace/Prefs.java
index 84b4767..03cc2e2 100644
--- a/app/src/main/java/ch/fixme/status/Prefs.java
+++ b/app/src/main/java/io/spaceapi/community/myhackerspace/Prefs.java
@@ -1,20 +1,26 @@
/*
* Copyright (C) 2012-2017 Aubort Jean-Baptiste (Rorist)
+ * Copyright (C) 2020-2023 Danilo Bargen (dbrgn)
* Licensed under GNU's GPL 3, see README
*/
-package ch.fixme.status;
+package io.spaceapi.community.myhackerspace;
+
+import static android.view.ViewGroup.LayoutParams.*;
import android.content.SharedPreferences;
import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
import android.os.Bundle;
import android.preference.PreferenceActivity;
import android.preference.PreferenceScreen;
+import android.view.LayoutInflater;
+import android.view.ViewGroup;
+import android.widget.LinearLayout;
public class Prefs extends PreferenceActivity implements
OnSharedPreferenceChangeListener {
public static final String KEY_API_ENDPOINT = "api_endpoint";
- public static final String DEFAULT_API_ENDPOINT = "https://directory.spaceapi.io/";
+ public static final String DEFAULT_API_ENDPOINT = "https://raw.githubusercontent.com/SpaceApi/directory/master/directory.json";
public static final String KEY_API_URL = "apiurl";
@@ -30,6 +36,7 @@ public class Prefs extends PreferenceActivity implements
public void onCreate(Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
addPreferencesFromResource(R.xml.preferences);
+ this.getListView().addFooterView(AboutLayout.create(this)); // tried addContentView
PreferenceScreen ps = getPreferenceScreen();
ps.getSharedPreferences()
.registerOnSharedPreferenceChangeListener(this);
diff --git a/app/src/main/java/io/spaceapi/community/myhackerspace/Utils.java b/app/src/main/java/io/spaceapi/community/myhackerspace/Utils.java
new file mode 100644
index 0000000..cc70159
--- /dev/null
+++ b/app/src/main/java/io/spaceapi/community/myhackerspace/Utils.java
@@ -0,0 +1,29 @@
+/*
+ * Copyright (C) 2020-2023 Danilo Bargen (dbrgn)
+ * Licensed under GNU's GPL 3, see README
+ */
+package io.spaceapi.community.myhackerspace;
+
+import androidx.annotation.NonNull;
+import androidx.annotation.Nullable;
+
+public class Utils {
+ /**
+ * Join the strings using the specified separator.
+ */
+ public static @Nullable String joinStrings(@NonNull String separator, String... strings) {
+ final StringBuilder builder = new StringBuilder();
+ boolean empty = true;
+ for (String string : strings) {
+ if (string != null) {
+ if (empty) {
+ builder.append(string);
+ empty = false;
+ } else {
+ builder.append(separator).append(string);
+ }
+ }
+ }
+ return empty ? null : builder.toString();
+ }
+}
diff --git a/app/src/main/java/ch/fixme/status/Widget.java b/app/src/main/java/io/spaceapi/community/myhackerspace/Widget.java
similarity index 76%
rename from app/src/main/java/ch/fixme/status/Widget.java
rename to app/src/main/java/io/spaceapi/community/myhackerspace/Widget.java
index a78e849..52670e1 100644
--- a/app/src/main/java/ch/fixme/status/Widget.java
+++ b/app/src/main/java/io/spaceapi/community/myhackerspace/Widget.java
@@ -1,9 +1,9 @@
/*
* Copyright (C) 2012-2017 Aubort Jean-Baptiste (Rorist)
+ * Copyright (C) 2020-2023 Danilo Bargen (dbrgn)
* Licensed under GNU's GPL 3, see README
*/
-
-package ch.fixme.status;
+package io.spaceapi.community.myhackerspace;
import android.app.AlarmManager;
import android.app.IntentService;
@@ -17,6 +17,7 @@
import android.content.SharedPreferences.Editor;
import android.graphics.Bitmap;
import android.os.AsyncTask;
+import android.os.Build;
import android.os.Handler;
import android.os.Looper;
import android.preference.PreferenceManager;
@@ -25,10 +26,12 @@
import android.widget.RemoteViews;
import android.widget.Toast;
-import org.json.JSONException;
-
import java.lang.ref.WeakReference;
-import java.util.HashMap;
+import java.net.MalformedURLException;
+import java.net.URL;
+
+import io.spaceapi.ParseError;
+import io.spaceapi.SpaceApiParser;
public class Widget extends AppWidgetProvider {
@@ -36,6 +39,18 @@ public class Widget extends AppWidgetProvider {
static final String WIDGET_IDS = "widget_ids";
static final String WIDGET_FORCE = "widget_force";
+ public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
+ // see https://intrepidgeeks.com/tutorial/android-app-widgets
+ final int N = appWidgetIds.length;
+
+ // Perform this loop procedure for each App Widget that belongs to this provider
+ for (int i=0; i= Build.VERSION_CODES.S ? PendingIntent.FLAG_MUTABLE : 0;
+ }
+
protected static Intent getIntent(Context ctxt, int widgetId) {
Intent i = new Intent(ctxt, UpdateService.class);
i.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
@@ -102,15 +121,16 @@ protected static void setAlarm(Context ctxt, Intent i, int widgetId,
// Set alarm
AlarmManager am = (AlarmManager) ctxt
.getSystemService(Context.ALARM_SERVICE);
- PendingIntent pi = PendingIntent.getService(ctxt, widgetId, i, 0);
+ PendingIntent pi = PendingIntent.getService(ctxt, widgetId, i, getPendingIntentMutableFlag());
am.cancel(pi);
am.setInexactRepeating(AlarmManager.ELAPSED_REALTIME_WAKEUP,
delay, update_interval, pi);
// Log.i(TAG, "start notification every " + update_interval / 1000
// + "s");
+ startWidgetUpdateTask(ctxt, widgetId);
}
- private static class GetImage extends AsyncTask {
+ private static class GetImage extends AsyncTask {
private final int mId;
private WeakReference mCtxt;
@@ -124,9 +144,9 @@ public GetImage(Context ctxt, int id, String text) {
}
@Override
- protected Bitmap doInBackground(String... url) {
+ protected Bitmap doInBackground(URL... url) {
try {
- return new Net(url[0]).getBitmap();
+ return new Net(url[0].toString()).getBitmap();
} catch (Throwable e) {
e.printStackTrace();
mError = e.getMessage();
@@ -186,7 +206,7 @@ protected static void updateWidget(final Context ctxt, int widgetId,
Intent clickIntent = new Intent(ctxt, Main.class);
clickIntent.putExtra(AppWidgetManager.EXTRA_APPWIDGET_ID, widgetId);
PendingIntent pendingIntent = PendingIntent.getActivity(ctxt, widgetId,
- clickIntent, PendingIntent.FLAG_CANCEL_CURRENT);
+ clickIntent, PendingIntent.FLAG_CANCEL_CURRENT + getPendingIntentMutableFlag());
views.setOnClickPendingIntent(R.id.widget_image, pendingIntent);
manager.updateAppWidget(widgetId, views);
}
@@ -228,60 +248,50 @@ protected void onCancelled() {
}
@Override
- protected void onPostExecute(String result) {
+ protected void onPostExecute(String endpointJson) {
final Context ctxt = mCtxt.get();
if(ctxt == null) { Log.e(TAG, "Context error (postExecute)"); return; }
try {
- SharedPreferences prefs = PreferenceManager
- .getDefaultSharedPreferences(ctxt);
+ final SharedPreferences prefs = PreferenceManager.getDefaultSharedPreferences(ctxt);
+
+ final io.spaceapi.types.Status data = SpaceApiParser.parseString(endpointJson);
+
+ boolean statusBool = data.state != null && data.state.open;
- HashMap api = new ParseGeneric(result)
- .getData();
- boolean statusBool = (Boolean) api.get(ParseGeneric.API_STATUS);
// Update only if different than last status or not forced
if (prefs.contains(Main.PREF_LAST_WIDGET + mId)
&& prefs.getBoolean(Main.PREF_LAST_WIDGET + mId, false) == statusBool
- && !prefs.getBoolean(Main.PREF_FORCE_WIDGET + mId,
- false)) {
+ && !prefs.getBoolean(Main.PREF_FORCE_WIDGET + mId, false)) {
Log.d(TAG, "Nothing to update");
return;
}
// Mandatory fields
- String status = ParseGeneric.API_ICON
- + ParseGeneric.API_ICON_CLOSED;
- if (statusBool) {
- status = ParseGeneric.API_ICON + ParseGeneric.API_ICON_OPEN;
- }
- Editor edit = prefs.edit();
+ final Editor edit = prefs.edit();
edit.putBoolean(Main.PREF_LAST_WIDGET + mId, statusBool);
edit.apply();
String status_text = null;
- if (prefs.getBoolean(Prefs.KEY_WIDGET_TEXT,
- Prefs.DEFAULT_WIDGET_TEXT)) {
- if (api.containsKey(ParseGeneric.API_STATUS_TXT)) {
- status_text = (String) api
- .get(ParseGeneric.API_STATUS_TXT);
+ if (prefs.getBoolean(Prefs.KEY_WIDGET_TEXT, Prefs.DEFAULT_WIDGET_TEXT)) {
+ if (data.state != null && data.state.message != null) {
+ status_text = data.state.message;
} else {
- status_text = statusBool ? ctxt.getString(R.string.status_open) :
- ctxt.getString(R.string.status_closed);
+ status_text = statusBool
+ ? ctxt.getString(R.string.status_open)
+ : ctxt.getString(R.string.status_closed);
}
}
// Status icon or space icon
- if (api.containsKey(ParseGeneric.API_ICON
- + ParseGeneric.API_ICON_OPEN)
- && api.containsKey(ParseGeneric.API_ICON
- + ParseGeneric.API_ICON_CLOSED)) {
- new GetImage(ctxt, mId, status_text).execute((String) api
- .get(status));
+ if (data.state != null && data.state.icon != null) {
+ new GetImage(ctxt, mId, status_text).execute(
+ statusBool? new URL(data.state.icon.open) : new URL(data.state.icon.closed)
+ );
} else {
- new GetImage(ctxt, mId, status_text).execute((String) api
- .get(ParseGeneric.API_LOGO));
+ new GetImage(ctxt, mId, status_text).execute(new URL(data.logo));
}
- } catch (JSONException e) {
+ } catch (ParseError | MalformedURLException e) {
e.printStackTrace();
String msg = e.getMessage();
printMessage(ctxt, msg);
@@ -297,24 +307,26 @@ public UpdateService() {
@Override
protected void onHandleIntent(Intent intent) {
- final Context ctxt = UpdateService.this;
+ final Context context = UpdateService.this;
final int widgetId = intent.getIntExtra(
AppWidgetManager.EXTRA_APPWIDGET_ID,
AppWidgetManager.INVALID_APPWIDGET_ID);
- SharedPreferences prefs = PreferenceManager
- .getDefaultSharedPreferences(ctxt);
- if (Main.checkNetwork(ctxt) && prefs.contains(Main.PREF_API_URL_WIDGET + widgetId)) {
- final String url = prefs.getString(Main.PREF_API_URL_WIDGET
- + widgetId, ParseGeneric.API_DEFAULT);
- Log.i(TAG, "Update widgetid " + widgetId + " with url "
- + url);
- new Handler(Looper.getMainLooper())
- .post(() -> new GetApiTask(ctxt, widgetId).execute(url));
- }
+ Widget.startWidgetUpdateTask(context, widgetId);
stopSelf();
}
}
+ private static void startWidgetUpdateTask(Context context, int widgetId) {
+ SharedPreferences prefs = PreferenceManager
+ .getDefaultSharedPreferences(context);
+ if (Main.hasNetwork(context) && prefs.contains(Main.PREF_API_URL_WIDGET + widgetId)) {
+ final String url = prefs.getString(Main.PREF_API_URL_WIDGET + widgetId, Main.API_DEFAULT);
+ Log.i(TAG, "Update widgetId " + widgetId + " with url " + url);
+ new Handler(Looper.getMainLooper())
+ .post(() -> new GetApiTask(context, widgetId).execute(url));
+ }
+ }
+
public static void UpdateAllWidgets(final Context ctxt, boolean force) {
AppWidgetManager man = AppWidgetManager.getInstance(ctxt);
int[] ids = man.getAppWidgetIds(new ComponentName(ctxt, Widget.class));
diff --git a/app/src/main/java/ch/fixme/status/Widget_config.java b/app/src/main/java/io/spaceapi/community/myhackerspace/Widget_config.java
similarity index 98%
rename from app/src/main/java/ch/fixme/status/Widget_config.java
rename to app/src/main/java/io/spaceapi/community/myhackerspace/Widget_config.java
index f556e4a..c4789dc 100644
--- a/app/src/main/java/ch/fixme/status/Widget_config.java
+++ b/app/src/main/java/io/spaceapi/community/myhackerspace/Widget_config.java
@@ -1,9 +1,9 @@
/*
* Copyright (C) 2012-2017 Aubort Jean-Baptiste (Rorist)
+ * Copyright (C) 2020-2023 Danilo Bargen (dbrgn)
* Licensed under GNU's GPL 3, see README
*/
-
-package ch.fixme.status;
+package io.spaceapi.community.myhackerspace;
import android.app.Activity;
import android.app.AlertDialog;
diff --git a/app/src/main/res/drawable-hdpi/myhs.png b/app/src/main/res/drawable-hdpi/myhs.png
deleted file mode 100644
index 00bb648..0000000
Binary files a/app/src/main/res/drawable-hdpi/myhs.png and /dev/null differ
diff --git a/app/src/main/res/drawable-ldpi/myhs.png b/app/src/main/res/drawable-ldpi/myhs.png
deleted file mode 100644
index 2f686fe..0000000
Binary files a/app/src/main/res/drawable-ldpi/myhs.png and /dev/null differ
diff --git a/app/src/main/res/drawable-mdpi/myhs.png b/app/src/main/res/drawable-mdpi/myhs.png
deleted file mode 100644
index 436b4f9..0000000
Binary files a/app/src/main/res/drawable-mdpi/myhs.png and /dev/null differ
diff --git a/app/src/main/res/drawable-xhdpi/myhs.png b/app/src/main/res/drawable-xhdpi/myhs.png
deleted file mode 100644
index 3ac90ae..0000000
Binary files a/app/src/main/res/drawable-xhdpi/myhs.png and /dev/null differ
diff --git a/app/src/main/res/layout/about.xml b/app/src/main/res/layout/about.xml
new file mode 100644
index 0000000..121e203
--- /dev/null
+++ b/app/src/main/res/layout/about.xml
@@ -0,0 +1,56 @@
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/layout/entry_sensor.xml b/app/src/main/res/layout/entry_sensor.xml
index fea2769..5653719 100644
--- a/app/src/main/res/layout/entry_sensor.xml
+++ b/app/src/main/res/layout/entry_sensor.xml
@@ -1,6 +1,6 @@
+ android:text="Value" />
-
-
-
-
+ android:text="More details" />
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
new file mode 100644
index 0000000..036d09b
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
new file mode 100644
index 0000000..036d09b
--- /dev/null
+++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml
@@ -0,0 +1,5 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png
new file mode 100644
index 0000000..51a1765
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..7a1de26
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png
new file mode 100644
index 0000000..a872b3e
Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png
new file mode 100644
index 0000000..0b9b46a
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..ae330ed
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png
new file mode 100644
index 0000000..16a7fbf
Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png
new file mode 100644
index 0000000..4802fb7
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..3d8fc7d
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..208bf3a
Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png
new file mode 100644
index 0000000..db8b832
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..69ea51a
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..1cab33c
Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png
new file mode 100644
index 0000000..ea2662d
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png
new file mode 100644
index 0000000..4d8d80a
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_foreground.png differ
diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png
new file mode 100644
index 0000000..7d8bad1
Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ
diff --git a/app/src/main/res/values-da/strings.xml b/app/src/main/res/values-da/strings.xml
index 2b32228..33caa69 100644
--- a/app/src/main/res/values-da/strings.xml
+++ b/app/src/main/res/values-da/strings.xml
@@ -34,7 +34,7 @@
Netværket er ikke tilgængeligt\n\nKontroller at det aktive SpaceAPI overholder den revision det annoncerer!\n\nSe https://spaceapi.io/
- Rediger OpenSpaceDirectory-URL
+ Rediger SpaceAPI-Directory-URLURL til den globale SpaceAPI-fortegnelse der skal anvendes (directory.json fil)\nEksempler:\nhttps://directory.spaceapi.io/Rediger SpaceAPI-URLÆndr URL for det aktive SpaceAPI (mistes ved valg af hackerspace fra fortegnelsen)
@@ -51,7 +51,7 @@
time(r)LokationKontakt
- Videostrøm
+ WebkameraerSensorer
diff --git a/app/src/main/res/values-de/strings.xml b/app/src/main/res/values-de/strings.xml
index e863ae4..8aea1fc 100644
--- a/app/src/main/res/values-de/strings.xml
+++ b/app/src/main/res/values-de/strings.xml
@@ -13,8 +13,8 @@ Licensed under GNU's GPL 3, see README
OK
- Öffnen
- Abgeschlossen
+ Offen
+ GeschlossenUnbekanntLogo des Hackerspaces
@@ -35,17 +35,17 @@ Licensed under GNU's GPL 3, see README
Fehler: NetzwerkNetzwerk deaktiviert
- \n\nÜberprüfen Sie ob die API der Version entspricht, welche angeben wurde!\n\nsiehe https://spaceapi.io/
+ \n\nÜberprüfe, ob die API der Version entspricht, welche angegeben wurde!\n\nSiehe https://spaceapi.io/
- SpaceAPI Endpoint
+ SpaceAPI Directory-URLURL zum globalen SpaceAPI-Directory (directory.json)\nBeispiel:\nhttps://directory.spaceapi.io/
- Aktuelle Hackerspace-API
- Aktuelle Hackerspace-API überladen (geht verloren, wenn ein anderer Hackerspace in der Liste gewählt wird)
+ SpaceAPI Endpunkt-URL
+ Aktuelle SpaceAPI Endpunkt-URL überladen (geht verloren, wenn ein anderer Space in der Liste gewählt wird)Aktualisierungsintervall
- Zeitspanne in der das Widget den tatsächlichen Status überprüft.
+ Zeitspanne, in der das Widget den tatsächlichen Status überprüft.Transparentes Widget
- Das Hintengrundbild des Widgets wird transparent statt grau dargestellt.
- Widget Statustext
+ Das Hintergrundbild des Widgets wird transparent statt grau dargestellt.
+ Widget-StatustextStatustext unter dem Widget hinzufügenStatus
@@ -53,8 +53,31 @@ Licensed under GNU's GPL 3, see README
Dauer: Stunde(n)Ort
+ Postanschrift
+ KoordinatenKontakt
- Webcam
+ WebcamsSensoren
+ Umwelt
+ Space
+ Organisation
+ Tel
+ E-Mail
+ ML
+
+ Anwesende Personen
+ Türstatus
+ Geschlossen
+ Geöffnet
+ Getränkevorrat
+ Stromverbrauch
+ Netzwerkverbindungen
+ Temperatur
+ Luftfeuchtigkeit
+ Luftdruck
+ Wind
+ Radioaktivität
+ Mitgliederanzahl
+ Kontostand
diff --git a/app/src/main/res/values-fr/strings.xml b/app/src/main/res/values-fr/strings.xml
index 0d055bb..95a223d 100644
--- a/app/src/main/res/values-fr/strings.xml
+++ b/app/src/main/res/values-fr/strings.xml
@@ -50,7 +50,7 @@
heure(s)LocalisationContact
- Flux
+ WebcamCapteurs
diff --git a/app/src/main/res/values-nl/strings.xml b/app/src/main/res/values-nl/strings.xml
index 6e49e4c..714f799 100644
--- a/app/src/main/res/values-nl/strings.xml
+++ b/app/src/main/res/values-nl/strings.xml
@@ -50,7 +50,7 @@
u(u)r(en)LocatieKontakt
- Stream
+ WebcamsSensoren
diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml
new file mode 100644
index 0000000..c5d5899
--- /dev/null
+++ b/app/src/main/res/values/ic_launcher_background.xml
@@ -0,0 +1,4 @@
+
+
+ #FFFFFF
+
\ No newline at end of file
diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml
index b98b486..4f9690e 100644
--- a/app/src/main/res/values/strings.xml
+++ b/app/src/main/res/values/strings.xml
@@ -34,24 +34,66 @@ Licensed under GNU's GPL 3, see README
Network unreachable\n\nCheck that the current SpaceAPI conforms to the level it actually declares!\n\nSee https://spaceapi.io/
- Edit OpenSpaceDirectory URL
- URL for the global SpaceAPI directory to use (directory.json file)\nExamples:\nhttps://directory.spaceapi.io/
- Edit SpaceAPI URL
- Override current SpaceAPI URL (will be lost when choosing a hackerspace from the directory)
+ Edit SpaceAPI Directory URL
+ URL for the global SpaceAPI directory to use (directory.json file).\nExamples:\nhttps://directory.spaceapi.io/
+ Edit SpaceAPI Endpoint URL
+ Override current SpaceAPI endpoint URL (will be lost when choosing a hackerspace from the directory)Check intervalInterval in minutes to check the status in the widgetShow transparent backgroundWidget\'s background will be transparent instead of grayShow status textAdd a text with the status under the widget
+ About This AppStatusLast change: Duration: hour(s)Location
+ Postal Address
+ CoordinatesContact
- Stream + WebCam
+ WebcamsSensors
+ Environment
+ Space
+ Organization
+ Phone
+ SIP
+ IRC
+ Twitter
+ Mastodon
+ Facebook
+ Identica
+ Foursquare
+ Email
+ ML
+ XMPP
+ Gopher
+ Matrix
+ Mumble
+
+ People Present
+ Door Status
+ Locked
+ Unlocked
+ Beverage Supply
+ Power Consumption
+ Network Connections
+ Temperature
+ Humidity
+ Air Pressure
+ Wind
+ Radiation
+ Member Count
+ Account Balance
+
+
+ App Version:
+ License:
+ GNU GENERAL PUBLIC LICENSE Version 3
+ Visit Repository
+ https://github.com/spaceapi-community/my-hackerspace
diff --git a/app/src/main/res/xml/preferences.xml b/app/src/main/res/xml/preferences.xml
index 5153cf2..9104e23 100644
--- a/app/src/main/res/xml/preferences.xml
+++ b/app/src/main/res/xml/preferences.xml
@@ -12,7 +12,7 @@
android:key="api_endpoint"
android:title="@string/prefs_api_title"
android:summary="@string/prefs_api_summary"
- android:defaultValue="https://directory.spaceapi.io/"
+ android:defaultValue="https://raw.githubusercontent.com/SpaceApi/directory/master/directory.json"
android:inputType="textUri" />
+
+
+
diff --git a/app/src/main/res/xml/widget.xml b/app/src/main/res/xml/widget.xml
index 94b71ab..b6da0c6 100644
--- a/app/src/main/res/xml/widget.xml
+++ b/app/src/main/res/xml/widget.xml
@@ -3,10 +3,10 @@
Licensed under GNU's GPL 3, see README
-->
diff --git a/app/src/test/java/io/spaceapi/community/myhackerspace/UtilsTest.java b/app/src/test/java/io/spaceapi/community/myhackerspace/UtilsTest.java
new file mode 100644
index 0000000..9a0e36b
--- /dev/null
+++ b/app/src/test/java/io/spaceapi/community/myhackerspace/UtilsTest.java
@@ -0,0 +1,19 @@
+package io.spaceapi.community.myhackerspace;
+
+import org.junit.Assert;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+import org.junit.runners.JUnit4;
+
+@RunWith(JUnit4.class)
+public class UtilsTest {
+ @Test
+ public void testJoinStrings() {
+ Assert.assertEquals("foo / bar", Utils.joinStrings(" / ", "foo", "bar"));
+ Assert.assertEquals("foo.bar.baz", Utils.joinStrings(".", "foo", "bar", "baz"));
+ Assert.assertEquals("foo.baz", Utils.joinStrings(".", "foo", null, "baz"));
+ Assert.assertEquals("foo.baz", Utils.joinStrings(".", null, "foo", null, "baz"));
+ Assert.assertNull(Utils.joinStrings(" / ", null, null, null));
+ Assert.assertNull(Utils.joinStrings(" / "));
+ }
+}
diff --git a/build.gradle b/build.gradle
index be2c04b..207d49f 100644
--- a/build.gradle
+++ b/build.gradle
@@ -2,11 +2,14 @@
buildscript {
repositories {
- jcenter()
+ mavenCentral()
google()
}
+
dependencies {
- classpath 'com.android.tools.build:gradle:3.5.3'
+ // Android gradle integration
+ classpath 'com.android.tools.build:gradle:8.5.1'
+
// NOTE: Do not place your application dependencies here; they belong
// in the individual module build.gradle files
}
@@ -14,7 +17,7 @@ buildscript {
allprojects {
repositories {
- jcenter()
+ mavenCentral()
google()
}
}
diff --git a/gradle.properties b/gradle.properties
new file mode 100644
index 0000000..f927a0b
--- /dev/null
+++ b/gradle.properties
@@ -0,0 +1,4 @@
+android.defaults.buildfeatures.buildconfig=true
+android.nonFinalResIds=false
+android.nonTransitiveRClass=false
+android.useAndroidX=true
diff --git a/gradle/wrapper/gradle-wrapper.jar b/gradle/wrapper/gradle-wrapper.jar
index 13372ae..62d4c05 100644
Binary files a/gradle/wrapper/gradle-wrapper.jar and b/gradle/wrapper/gradle-wrapper.jar differ
diff --git a/gradle/wrapper/gradle-wrapper.properties b/gradle/wrapper/gradle-wrapper.properties
index f817df6..48c0a02 100644
--- a/gradle/wrapper/gradle-wrapper.properties
+++ b/gradle/wrapper/gradle-wrapper.properties
@@ -1,6 +1,5 @@
-#Fri Feb 28 13:50:08 CET 2020
distributionBase=GRADLE_USER_HOME
distributionPath=wrapper/dists
+distributionUrl=https\://services.gradle.org/distributions/gradle-8.7-bin.zip
zipStoreBase=GRADLE_USER_HOME
zipStorePath=wrapper/dists
-distributionUrl=https\://services.gradle.org/distributions/gradle-5.4.1-all.zip
diff --git a/gradlew b/gradlew
index 9d82f78..fbd7c51 100755
--- a/gradlew
+++ b/gradlew
@@ -1,4 +1,20 @@
-#!/usr/bin/env bash
+#!/usr/bin/env sh
+
+#
+# Copyright 2015 the original author or authors.
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# https://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+#
##############################################################################
##
@@ -6,20 +22,38 @@
##
##############################################################################
-# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
-DEFAULT_JVM_OPTS=""
+# Attempt to set APP_HOME
+# Resolve links: $0 may be a link
+PRG="$0"
+# Need this for relative symlinks.
+while [ -h "$PRG" ] ; do
+ ls=`ls -ld "$PRG"`
+ link=`expr "$ls" : '.*-> \(.*\)$'`
+ if expr "$link" : '/.*' > /dev/null; then
+ PRG="$link"
+ else
+ PRG=`dirname "$PRG"`"/$link"
+ fi
+done
+SAVED="`pwd`"
+cd "`dirname \"$PRG\"`/" >/dev/null
+APP_HOME="`pwd -P`"
+cd "$SAVED" >/dev/null
APP_NAME="Gradle"
APP_BASE_NAME=`basename "$0"`
+# Add default JVM options here. You can also use JAVA_OPTS and GRADLE_OPTS to pass JVM options to this script.
+DEFAULT_JVM_OPTS='"-Xmx64m" "-Xms64m"'
+
# Use the maximum available, or set MAX_FD != -1 to use that value.
MAX_FD="maximum"
-warn ( ) {
+warn () {
echo "$*"
}
-die ( ) {
+die () {
echo
echo "$*"
echo
@@ -30,6 +64,7 @@ die ( ) {
cygwin=false
msys=false
darwin=false
+nonstop=false
case "`uname`" in
CYGWIN* )
cygwin=true
@@ -40,28 +75,14 @@ case "`uname`" in
MINGW* )
msys=true
;;
+ NONSTOP* )
+ nonstop=true
+ ;;
esac
-# Attempt to set APP_HOME
-# Resolve links: $0 may be a link
-PRG="$0"
-# Need this for relative symlinks.
-while [ -h "$PRG" ] ; do
- ls=`ls -ld "$PRG"`
- link=`expr "$ls" : '.*-> \(.*\)$'`
- if expr "$link" : '/.*' > /dev/null; then
- PRG="$link"
- else
- PRG=`dirname "$PRG"`"/$link"
- fi
-done
-SAVED="`pwd`"
-cd "`dirname \"$PRG\"`/" >/dev/null
-APP_HOME="`pwd -P`"
-cd "$SAVED" >/dev/null
-
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
@@ -85,7 +106,7 @@ location of your Java installation."
fi
# Increase the maximum file descriptors if we can.
-if [ "$cygwin" = "false" -a "$darwin" = "false" ] ; then
+if [ "$cygwin" = "false" -a "$darwin" = "false" -a "$nonstop" = "false" ] ; then
MAX_FD_LIMIT=`ulimit -H -n`
if [ $? -eq 0 ] ; then
if [ "$MAX_FD" = "maximum" -o "$MAX_FD" = "max" ] ; then
@@ -105,10 +126,11 @@ if $darwin; then
GRADLE_OPTS="$GRADLE_OPTS \"-Xdock:name=$APP_NAME\" \"-Xdock:icon=$APP_HOME/media/gradle.icns\""
fi
-# For Cygwin, switch paths to Windows format before running java
-if $cygwin ; then
+# For Cygwin or MSYS, switch paths to Windows format before running java
+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
@@ -134,27 +156,30 @@ if $cygwin ; 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
-# Split up the JVM_OPTS And GRADLE_OPTS values into an array, following the shell quoting and substitution rules
-function splitJvmOpts() {
- JVM_OPTS=("$@")
+# Escape application args
+save () {
+ for i do printf %s\\n "$i" | sed "s/'/'\\\\''/g;1s/^/'/;\$s/\$/' \\\\/" ; done
+ echo " "
}
-eval splitJvmOpts $DEFAULT_JVM_OPTS $JAVA_OPTS $GRADLE_OPTS
-JVM_OPTS[${#JVM_OPTS[*]}]="-Dorg.gradle.appname=$APP_BASE_NAME"
+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"
-exec "$JAVACMD" "${JVM_OPTS[@]}" -classpath "$CLASSPATH" org.gradle.wrapper.GradleWrapperMain "$@"
+exec "$JAVACMD" "$@"
diff --git a/gradlew.bat b/gradlew.bat
index aec9973..a9f778a 100644
--- a/gradlew.bat
+++ b/gradlew.bat
@@ -1,3 +1,19 @@
+@rem
+@rem Copyright 2015 the original author or authors.
+@rem
+@rem Licensed under the Apache License, Version 2.0 (the "License");
+@rem you may not use this file except in compliance with the License.
+@rem You may obtain a copy of the License at
+@rem
+@rem https://www.apache.org/licenses/LICENSE-2.0
+@rem
+@rem Unless required by applicable law or agreed to in writing, software
+@rem distributed under the License is distributed on an "AS IS" BASIS,
+@rem WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+@rem See the License for the specific language governing permissions and
+@rem limitations under the License.
+@rem
+
@if "%DEBUG%" == "" @echo off
@rem ##########################################################################
@rem
@@ -8,14 +24,17 @@
@rem Set local scope for the variables with windows NT shell
if "%OS%"=="Windows_NT" setlocal
-@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=
-
set DIRNAME=%~dp0
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"
+
@rem Find java.exe
if defined JAVA_HOME goto findJavaFromJavaHome
@@ -46,10 +65,9 @@ echo location of your Java installation.
goto fail
:init
-@rem Get command-line arguments, handling Windowz variants
+@rem Get command-line arguments, handling Windows variants
if not "%OS%" == "Windows_NT" goto win9xME_args
-if "%@eval[2+2]" == "4" goto 4NT_args
:win9xME_args
@rem Slurp the command line arguments.
@@ -60,17 +78,13 @@ set _SKIP=2
if "x%~1" == "x" goto execute
set CMD_LINE_ARGS=%*
-goto execute
-
-:4NT_args
-@rem Get arguments from the 4NT Shell from JP Software
-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%
diff --git a/graphics/feature-graphic.png b/graphics/feature-graphic.png
new file mode 100644
index 0000000..4302100
Binary files /dev/null and b/graphics/feature-graphic.png differ
diff --git a/graphics/feature-graphic.svg b/graphics/feature-graphic.svg
new file mode 100644
index 0000000..71c0605
--- /dev/null
+++ b/graphics/feature-graphic.svg
@@ -0,0 +1,16936 @@
+
+
diff --git a/graphics/myhackerspace.png b/graphics/myhackerspace.png
new file mode 100644
index 0000000..d2435ba
Binary files /dev/null and b/graphics/myhackerspace.png differ
diff --git a/graphics/myhackerspace.svg b/graphics/myhackerspace.svg
new file mode 100644
index 0000000..2384691
--- /dev/null
+++ b/graphics/myhackerspace.svg
@@ -0,0 +1,1519 @@
+
+
diff --git a/graphics/screenshot-phone.png b/graphics/screenshot-phone.png
new file mode 100644
index 0000000..c002485
Binary files /dev/null and b/graphics/screenshot-phone.png differ
diff --git a/graphics/screenshot-tablet-10in.png b/graphics/screenshot-tablet-10in.png
new file mode 100644
index 0000000..5000322
Binary files /dev/null and b/graphics/screenshot-tablet-10in.png differ
diff --git a/graphics/screenshot-tablet-7in.png b/graphics/screenshot-tablet-7in.png
new file mode 100644
index 0000000..0d5cb26
Binary files /dev/null and b/graphics/screenshot-tablet-7in.png differ
diff --git a/metadata/en-US/changelogs/100.txt b/metadata/en-US/changelogs/100.txt
new file mode 100644
index 0000000..eca872b
--- /dev/null
+++ b/metadata/en-US/changelogs/100.txt
@@ -0,0 +1,14 @@
+- [info] App was re-released by the SpaceAPI project under a new package name
+- [info] GitHub is now at https://github.com/spaceapi-community/my-hackerspace/
+- [info] The app now requires at least Android 5 (API 21)
+- [feature] Support for SpaceAPI v14
+- [feature] New app launcher icon
+- [feature] More modern icons in app UI
+- [bug] Don't save empty data in application state
+- [change] Update all domains to spaceapi.io
+- [change] Switch to Java 8
+- [change] Remove MemorizingTrustManager
+- [change] Upgrade dependencies
+- [change] Switch to CircleCI
+- [change] Add support for annotations
+- [i18n] Improved translations
diff --git a/metadata/en-US/changelogs/101.txt b/metadata/en-US/changelogs/101.txt
new file mode 100644
index 0000000..a5da46c
--- /dev/null
+++ b/metadata/en-US/changelogs/101.txt
@@ -0,0 +1 @@
+- [bug] Fix refresh button
diff --git a/metadata/en-US/changelogs/102.txt b/metadata/en-US/changelogs/102.txt
new file mode 100644
index 0000000..74a5fca
--- /dev/null
+++ b/metadata/en-US/changelogs/102.txt
@@ -0,0 +1,3 @@
+- [change] Update dependencies
+- [change] Rename package to `io.spaceapi.community.myhackerspace`
+- [info] Add F-Droid metadata
diff --git a/metadata/en-US/changelogs/103.txt b/metadata/en-US/changelogs/103.txt
new file mode 100644
index 0000000..f31bc53
--- /dev/null
+++ b/metadata/en-US/changelogs/103.txt
@@ -0,0 +1,6 @@
+- [feature] Add information about the app to settings
+- [bug] Fix widget crashing on Android 12
+- [bug] Export widget config activity to fix crashing OpenLauncher
+- [change] Reverse latitude and longitude displayed on the screen
+- [change] Improvements for widget
+- [change] Upgrade dependencies, change TargetSDK to 33
diff --git a/metadata/en-US/changelogs/104.txt b/metadata/en-US/changelogs/104.txt
new file mode 100644
index 0000000..0638815
--- /dev/null
+++ b/metadata/en-US/changelogs/104.txt
@@ -0,0 +1,2 @@
+- [bug] Fix a bug when parsing v14 endpoints that contain a SpaceFED key without a "spacephone" field
+- [bug] Temporarily use directory from GitHub directly to speed up loading
diff --git a/metadata/en-US/changelogs/105.txt b/metadata/en-US/changelogs/105.txt
new file mode 100644
index 0000000..fffd32a
--- /dev/null
+++ b/metadata/en-US/changelogs/105.txt
@@ -0,0 +1,5 @@
+- [feature] Show state "last change" timestamp as localized datetime
+- [bug] Use correct mastodon property
+- [bug] Fix network error message
+- [change] Drop support for Android 5–7, require at least Android 8
+- [change] Remove old autolinking-workaround for HTC devices
diff --git a/metadata/en-US/full_description.txt b/metadata/en-US/full_description.txt
new file mode 100644
index 0000000..4183d52
--- /dev/null
+++ b/metadata/en-US/full_description.txt
@@ -0,0 +1,16 @@
+
View information about hackerspaces and makerspaces registered in the
+SpaceAPI directory. This includes:
+
+
+
Location and contact information
+
Opening status
+
Sensor values
+
A widget displaying the current open/closed status on the Android home screen
+
...and much more!
+
+
+History
+
+
This app was originally developed in 2012 by @rorist from the FIXME Lausanne hackspace.
+In 2021, the app was transferred to the SpaceAPI community repositories and is now
+mainly being developed by members of Coredump.