├── .circleci
└── config.yml
├── .gitignore
├── .gitmodules
├── CHANGELOG.md
├── MAINTENANCE.md
├── README.md
├── RELEASING.md
├── app
├── build.gradle
├── proguard-rules.txt
└── src
│ ├── main
│ ├── AndroidManifest.xml
│ ├── ic_launcher-playstore.png
│ ├── java
│ │ ├── com
│ │ │ └── woozzu
│ │ │ │ └── android
│ │ │ │ ├── util
│ │ │ │ └── StringMatcher.java
│ │ │ │ └── widget
│ │ │ │ ├── IndexScroller.java
│ │ │ │ └── IndexableListView.java
│ │ └── io
│ │ │ └── spaceapi
│ │ │ └── community
│ │ │ └── myhackerspace
│ │ │ ├── AboutLayout.java
│ │ │ ├── Main.java
│ │ │ ├── Net.java
│ │ │ ├── Network.java
│ │ │ ├── Prefs.java
│ │ │ ├── Utils.java
│ │ │ ├── Widget.java
│ │ │ └── Widget_config.java
│ └── res
│ │ ├── drawable
│ │ ├── ic_refresh_black_24dp.xml
│ │ ├── ic_settings_black_24dp.xml
│ │ └── ic_view_list_black_24dp.xml
│ │ ├── layout
│ │ ├── about.xml
│ │ ├── base.xml
│ │ ├── entry.xml
│ │ ├── entry_sensor.xml
│ │ ├── hs_choose.xml
│ │ ├── hs_entry.xml
│ │ ├── main.xml
│ │ ├── separator.xml
│ │ ├── subtitle.xml
│ │ ├── title.xml
│ │ ├── widget.xml
│ │ └── widget_config.xml
│ │ ├── menu
│ │ └── main.xml
│ │ ├── mipmap-anydpi-v26
│ │ ├── ic_launcher.xml
│ │ └── ic_launcher_round.xml
│ │ ├── mipmap-hdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-mdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── mipmap-xxxhdpi
│ │ ├── ic_launcher.png
│ │ ├── ic_launcher_foreground.png
│ │ └── ic_launcher_round.png
│ │ ├── values-da
│ │ └── strings.xml
│ │ ├── values-de
│ │ └── strings.xml
│ │ ├── values-fr
│ │ └── strings.xml
│ │ ├── values-nl
│ │ └── strings.xml
│ │ ├── values-v11
│ │ └── styles.xml
│ │ ├── values
│ │ ├── ic_launcher_background.xml
│ │ ├── strings.xml
│ │ └── styles.xml
│ │ └── xml
│ │ ├── locales_config.xml
│ │ ├── network_security_config.xml
│ │ ├── preferences.xml
│ │ └── widget.xml
│ └── test
│ └── java
│ └── io
│ └── spaceapi
│ └── community
│ └── myhackerspace
│ └── UtilsTest.java
├── build.gradle
├── gpl-3.0.txt
├── gradle.properties
├── gradle
└── wrapper
│ ├── gradle-wrapper.jar
│ └── gradle-wrapper.properties
├── gradlew
├── gradlew.bat
├── graphics
├── feature-graphic.png
├── feature-graphic.svg
├── get-it-on-accrescent.png
├── myhackerspace.png
├── myhackerspace.svg
├── screenshot-phone.png
├── screenshot-tablet-10in.png
└── screenshot-tablet-7in.png
├── local.properties.example
├── metadata
└── en-US
│ ├── changelogs
│ ├── 100.txt
│ ├── 101.txt
│ ├── 102.txt
│ ├── 103.txt
│ ├── 104.txt
│ ├── 105.txt
│ ├── 106.txt
│ └── 107.txt
│ ├── full_description.txt
│ ├── images
│ ├── featureGraphic.png
│ ├── icon.png
│ ├── phoneScreenshots
│ │ ├── 1.png
│ │ └── 2.png
│ ├── sevenInchScreenshots
│ │ └── 1.png
│ └── tenInchScreenshots
│ │ └── 1.png
│ ├── short_description.txt
│ └── title.txt
└── settings.gradle
/.circleci/config.yml:
--------------------------------------------------------------------------------
1 | version: '2.1'
2 | orbs:
3 | android: circleci/android@2.5.0
4 | jobs:
5 | build:
6 | executor:
7 | name: android/android-machine
8 | resource-class: medium
9 | tag: 2024.04.1
10 | steps:
11 | - checkout
12 | - restore_cache:
13 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
14 | - run:
15 | name: Download Dependencies
16 | command: ./gradlew androidDependencies
17 | - save_cache:
18 | paths:
19 | - ~/.gradle
20 | key: jars-{{ checksum "build.gradle" }}-{{ checksum "app/build.gradle" }}
21 | - run:
22 | name: Run Tests
23 | command: ./gradlew lint test
24 | - store_artifacts: # for display in Artifacts: https://circleci.com/docs/2.0/artifacts/
25 | path: app/build/reports
26 | destination: reports
27 | - store_test_results: # for display in Test Summary: https://circleci.com/docs/2.0/collect-test-data/
28 | path: app/build/test-results
29 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | bin
2 | gen
3 | .project
4 | .classpath
5 | project.properties
6 | local.properties
7 | build.properties
8 | .settings
9 | *.swp
10 | *.iml
11 | .idea
12 | out
13 | .gradle
14 | /local.properties
15 | .DS_Store
16 | build
17 | /captures
18 | apks/
19 | app/release/
20 |
21 | # Release config
22 | /release.properties
23 | /release.keystore
24 |
--------------------------------------------------------------------------------
/.gitmodules:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spaceapi-community/my-hackerspace/07f765319be6c9f104ba22f2df1bb2653e8d7939/.gitmodules
--------------------------------------------------------------------------------
/CHANGELOG.md:
--------------------------------------------------------------------------------
1 | # CHANGELOG
2 |
3 | Possible tags:
4 |
5 | - [info] An information not directly related to code changes
6 | - [feature] A new feature or an improvement
7 | - [bug] A bugfix
8 | - [change] A change that's neither a feature nor a bugfix
9 | - [i18n] Internationalization / translation
10 |
11 |
12 | # Unreleased
13 |
14 |
15 | # v2.2.0 (2025-01-20)
16 |
17 | - [feature] Add support for SpaceAPI v15 ([#54])
18 |
19 | Contributors to this version:
20 |
21 | - @s3lph
22 |
23 | Thanks!
24 |
25 | [#54]: https://github.com/spaceapi-community/my-hackerspace/pull/54
26 |
27 |
28 | # v2.1.3 (2024-07-21)
29 |
30 | - [bug] Fix a parsing error for certain endpoints ([#50])
31 | - [change] Upgrade dependencies, switch to Java 17, switch to Gradle 8, change
32 | TargetSDK to 34 ([#53])
33 |
34 | Contributors to this version:
35 |
36 | - Danilo Bargen (@dbrgn)
37 |
38 | Thanks!
39 |
40 | [#50]: https://github.com/spaceapi-community/my-hackerspace/pull/50
41 | [#53]: https://github.com/spaceapi-community/my-hackerspace/pull/53
42 |
43 |
44 | # v2.1.2 (2023-10-02)
45 |
46 | - [feature] Show state "last change" timestamp as localized datetime ([#47])
47 | - [bug] Use correct mastodon property ([#49])
48 | - [bug] Fix network error message
49 | - [change] Drop support for Android 5–7, require at least Android 8
50 | - [change] Remove old autolinking-workaround for HTC devices ([#48])
51 |
52 | Contributors to this version:
53 |
54 | - cyroxx (@cyroxx)
55 | - Danilo Bargen (@dbrgn)
56 |
57 | Thanks!
58 |
59 | [#47]: https://github.com/spaceapi-community/my-hackerspace/pull/47
60 | [#48]: https://github.com/spaceapi-community/my-hackerspace/pull/48
61 | [#49]: https://github.com/spaceapi-community/my-hackerspace/pull/49
62 |
63 |
64 | ## v2.1.1 (2023-04-27)
65 |
66 | - [bug] Fix a bug when parsing v14 endpoints that contain a SpaceFED key
67 | without a "spacephone" field ([#44])
68 | - [bug] Temporarily use directory from GitHub directly to speed up loading
69 | ([#45])
70 |
71 | Contributors to this version:
72 |
73 | - Danilo Bargen (@dbrgn)
74 |
75 | Thanks!
76 |
77 | [#44]: https://github.com/spaceapi-community/my-hackerspace/pull/44
78 | [#45]: https://github.com/spaceapi-community/my-hackerspace/pull/45
79 |
80 |
81 | ## v2.1.0 (2023-04-16)
82 |
83 | - [feature] Add information about the app to settings ([#18])
84 | - [bug] Fix widget crashing on Android 12 ([#24])
85 | - [bug] Export widget config activity to fix crashing OpenLauncher ([#33])
86 | - [change] Reverse latitude and longitude displayed on the screen ([#37])
87 | - [change] Improvements for widget ([#34])
88 | - [change] Upgrade dependencies, change TargetSDK to 33 ([#42])
89 |
90 | Contributors to this version:
91 |
92 | - Danilo Bargen (@dbrgn)
93 | - Nicco Kunzmann (@niccokunzmann)
94 | - Adrian Pascu (@adipascu)
95 | - Frieder Hannenheim (@FriederHannenheim)
96 |
97 | Thanks!
98 |
99 | [#18]: https://github.com/spaceapi-community/my-hackerspace/pull/18
100 | [#24]: https://github.com/spaceapi-community/my-hackerspace/pull/24
101 | [#33]: https://github.com/spaceapi-community/my-hackerspace/pull/33
102 | [#34]: https://github.com/spaceapi-community/my-hackerspace/pull/34
103 | [#37]: https://github.com/spaceapi-community/my-hackerspace/pull/37
104 | [#42]: https://github.com/spaceapi-community/my-hackerspace/pull/42
105 | [#44]: https://github.com/spaceapi-community/my-hackerspace/pull/44
106 |
107 |
108 | ## v2.0.2 (2022-08-07)
109 |
110 | Unfortunately the app was pulled down from Google Play by Google due to
111 | "developer inactivity". Due to the lack of e-mail forwarding, we never noticed
112 | the warnings, so the app is now gone.
113 |
114 | To fix this, we had to change the app ID. Additionally, there will be an F-Droid release.
115 |
116 | Changes:
117 |
118 | - [change] Update dependencies
119 | - [change] Rename package to `io.spaceapi.community.myhackerspace`
120 | - [info] Add F-Droid metadata
121 |
122 |
123 | ## v2.0.1 (2021-05-14)
124 |
125 | - [bug] Fix refresh button ([#5][i5])
126 |
127 | [i5]: https://github.com/spaceapi-community/my-hackerspace/pull/5
128 |
129 |
130 | ## v2.0.0 (2021-02-20)
131 |
132 | - [info] App was re-released by the SpaceAPI project under a new package name ([#1][i1])
133 | - [info] GitHub is now at https://github.com/spaceapi-community/my-hackerspace/
134 | - [info] The app now requires at least Android 5 (API 21) ([#75][i75])
135 | - [feature] Support for SpaceAPI v14 ([#85][i85])
136 | - [feature] New app launcher icon ([#3][i3])
137 | - [feature] More modern icons in app UI ([#74][i74])
138 | - [bug] Don't save empty data in application state ([#64][i64])
139 | - [change] Update all domains to spaceapi.io ([#65][i65], [#71][i71])
140 | - [change] Switch to Java 8 ([#73][i73])
141 | - [change] Remove MemorizingTrustManager ([#65][i65])
142 | - [change] Upgrade dependencies ([#69][i69])
143 | - [change] Switch to CircleCI ([#69][i69])
144 | - [change] Add support for annotations ([#77][i77])
145 | - [i18n] Improved translations
146 |
147 | [i1]: https://github.com/spaceapi-community/my-hackerspace/pull/1
148 | [i3]: https://github.com/spaceapi-community/my-hackerspace/pull/3
149 | [i64]: https://github.com/fixme-lausanne/MyHackerspace/pull/64
150 | [i65]: https://github.com/fixme-lausanne/MyHackerspace/pull/65
151 | [i69]: https://github.com/fixme-lausanne/MyHackerspace/pull/69
152 | [i71]: https://github.com/fixme-lausanne/MyHackerspace/pull/71
153 | [i73]: https://github.com/fixme-lausanne/MyHackerspace/pull/73
154 | [i74]: https://github.com/fixme-lausanne/MyHackerspace/pull/74
155 | [i75]: https://github.com/fixme-lausanne/MyHackerspace/pull/75
156 | [i77]: https://github.com/fixme-lausanne/MyHackerspace/pull/77
157 | [i85]: https://github.com/fixme-lausanne/MyHackerspace/pull/85
158 |
159 |
160 | ## v1.8.3 (2017-01-XX)
161 |
162 | - Change links from SpaceAPI.net to SpaceDirectory.org
163 | - Display all webcams
164 |
165 |
166 | ## v1.8.2 (2016-07-02)
167 |
168 | - Fix camera and stream url being displayed
169 | - Fix twitter link to the new url format
170 |
171 |
172 | ## v1.8.1 (2016-05-06)
173 |
174 | - Uses custom API directory end point (https://spaceapi.fixme.ch/directory.json)
175 | - Allow editing of the API directory end point and the current hackerspace API
176 | - Add Danish translation (thanks Mikkel)
177 |
178 |
179 | ## v1.8 (2016-04-14)
180 |
181 | - Supports invalid SSL certificates
182 | - Allow widget to be resized
183 | - Add Dutch translation
184 | - Fix http to https redirection
185 | - General fixes
186 |
187 |
188 | ## v1.7.4.1 (2014-08-26)
189 |
190 | - Fix crash when there's no error message
191 |
192 |
193 | ## v1.7.4 (2014-08-07)
194 |
195 | - German translation (thanks to Lokke and Phervieux)
196 | - Better hs list with alphabetical index
197 | - Better errors messages
198 | - Caching for http requests (images, hs directory)
199 | - Add status message to the widget (thanks Fpletz)
200 | - Fix bugs: widget updates, ignore ext fields, click from widget
201 |
202 |
203 | ## v1.7.3 (2013-10-25)
204 |
205 | - Fix regression with widget custom open/close logo
206 | - Fix order of hackerspaces with different cases
207 |
208 |
209 | ## v1.7.2 (2013-09-09)
210 |
211 | - Better layout for sensors
212 | - Support more fields for sensors (machines, names, properties)
213 |
214 |
215 | ## v1.7.1 (2013-09-06)
216 |
217 | - Faster http requests (Use DefaultHttpClient instead of HttpURLConnection)
218 |
219 |
220 | ## v1.7 (2013-09-05)
221 |
222 | - Full support of SpaceAPI 0.13, drops mixed api definition: hackerspaces must comply to the level they declare!
223 | - Widget transparency preference added (by default transparency is deactivated)
224 |
225 |
226 | ## v1.6.1 (2013-06-04)
227 |
228 | - French translation
229 | - Fix the widget's image not updating
230 | - Change to the new spaceapi url
231 |
232 |
233 | ## v1.6 (2013-01-02)
234 |
235 | - Better layout in general
236 | - Use Holo light theme for Android >=3
237 | - Refresh the current hackerspace
238 | - Default to 15mn for the Widget
239 | - Settings button to change the widget interval
240 | - Fix lat/lon link
241 | - Fix crash when maps/email app not found
242 |
243 |
244 | ## v1.5.1 (2012-10-29)
245 |
246 | - Bug fixes
247 | - Add a spinner when loading image
248 | - Faster download
249 |
250 |
251 | ## v1.5 (2012-05-19)
252 |
253 | - Only download image if there is a change of state (better battery live and reduce network usage)
254 |
255 |
256 | ## v1.4 (2012-05-15)
257 |
258 | - Add Cam and Stream links if present
259 | - Link for adresses opening GMaps
260 | - Sort Hackerspaces by name
261 | - Accept untrusted SSL certificates
262 | - Better error reporting
263 | - BUGFIX: Theme shoud be correct on all devices/versions
264 | - BUGFIX: Should work after reboot correctly
265 |
266 |
267 | ## v1.3 (2012-05-08)
268 |
269 | - White theme by default (may break on samsung devices)
270 | - Check if network is enabled
271 | - Handle rotation correctly
272 |
273 |
274 | ## v1.2 (2012-05-06)
275 |
276 |
277 | ## v1.1 (2012-05-04)
278 |
279 |
280 | ## v1.0 (2012-04-29)
281 |
282 | - Initial release
283 |
--------------------------------------------------------------------------------
/MAINTENANCE.md:
--------------------------------------------------------------------------------
1 | # Maintenance
2 |
3 | This project is maintained by the SpaceAPI community.
4 |
5 | ## Current Maintainers
6 |
7 | Maintainers (may review and merge):
8 |
9 | - @dbrgn
10 | - @niccokunzmann
11 |
12 | People with publication rights on Google Play:
13 |
14 | - @dbrgn
15 |
16 | People with publication rights on Accrescent:
17 |
18 | - @dbrgn
19 |
20 | ## Review and Merge Policy
21 |
22 | In case there are multiple maintainers, non-trivial changes should be reviewed
23 | by another team member. A review is requested through the GitHub UI.
24 |
25 | If a change is not reviewed within 1-2 weeks, the merge request may be merged
26 | without a review.
27 |
28 | For urgent bugfixes, the review may be skipped.
29 |
30 | ## Non-Responsive Maintainers
31 |
32 | If maintainers are not responding, send a reminder and/or try to contact them
33 | through other communication channels. If this situation persists for a longer
34 | time period, other members of the SpaceAPI community can and should take over
35 | the maintenance of the project.
36 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 |
2 |
3 |

4 |
5 |
6 |
7 | # My Hackerspace
8 |
9 | [](https://circleci.com/gh/spaceapi-community/my-hackerspace)
10 |
11 | This is an Android app with the following features:
12 |
13 | - Show the opening status of hacker- and makerspaces using the [SpaceAPI](https://spaceapi.io/)
14 | - Show information about the space (contact, location, sensors, ...)
15 | - Status widget, multiple widgets supported
16 |
17 | The app was originally developed in 2012 by [@rorist] from [FIXME Lausanne]. In
18 | 2021, the app was transferred to the [SpaceAPI community repositories] and is
19 | now mainly being developed by members of [Coredump].
20 | You can join our [Matrix](https://matrix.org/) chat at `#spaceapi:matrix.coredump.ch`.
21 |
22 | [@rorist]: https://github.com/rorist
23 | [FIXME Lausanne]: https://fixme.ch/
24 | [SpaceAPI community repositories]: https://github.com/spaceapi-community/
25 | [Coredump]: https://www.coredump.ch/
26 |
27 |
28 |
29 |
30 |
31 | ## How it works
32 |
33 | The app will get the list of hackspaces from [https://directory.spaceapi.io](https://directory.spaceapi.io).
34 | You can then choose the space by its name from a list.
35 | When the space is chosen, the associated data is retrieved from the space's
36 | SpaceAPI endpoint (which is registered in the SpaceAPI directory).
37 | If you would like to add your space to the directory, have a look at
38 | [the SpaceAPI website](https://spaceapi.io/provide-an-endpoint/).
39 |
40 | ### The Widget
41 |
42 | The image for the widget is specified in the SpaceAPI endpoint JSON.
43 | Have a look at the [schema documentation](https://spaceapi.io/docs/) to make your
44 | widget more pretty!
45 |
46 | 1. `open.icon` - if present, the widget chooses the specific open/closed images
47 | 2. `logo` - the widget chooses the logo of the hackspace to display
48 |
49 | ## How to Compile
50 |
51 | First, get the sources.
52 |
53 | git clone --recursive https://github.com/spaceapi-community/my-hackerspace.git
54 | cd my-hackerspace
55 |
56 | ### Android Studio
57 |
58 | With Android Studio, simply open the project directory and you should be set.
59 |
60 | ### Command Line
61 |
62 | You can build the project using Gradle.
63 |
64 | The following examples use the gradle wrapper script which will automatically
65 | download gradle to your local directory. If you want to use your own
66 | system-wide installation instead, simply replace `./gradlew` commands with
67 | `gradle`.
68 |
69 | First, copy `local.properties.example` to `local.properties` and adjust the
70 | path to your Android SDK installation.
71 |
72 | To build a debug APK:
73 |
74 | ./gradlew assembleDebug
75 |
76 | You will find your APK file in the ` app/build/outputs/apk/` directory.
77 |
78 | You can also build and directly install the file to your connected smartphone:
79 |
80 | ./gradlew assembleDebug installDebug
81 |
82 | To build a release APK, create file `release.properties` in the project root
83 | looking like this:
84 |
85 | keystoreFile=release.keystore
86 | keystorePassword=***
87 | keyAlias=the-key-alias
88 | keyPassword=***
89 |
90 | Make sure that the `keystoreFile` path exists. Then run:
91 |
92 | ./gradlew clean assembleRelease
93 |
94 |
95 | To see other tasks that gradle offers, run
96 |
97 | ./gradlew tasks
98 |
99 |
100 |
--------------------------------------------------------------------------------
/RELEASING.md:
--------------------------------------------------------------------------------
1 | # Releasing
2 |
3 | Set variables:
4 |
5 | $ export VERSION=X.Y.Z
6 | $ export GPG_KEY=20EE002D778AE197EF7D0D2CB993FF98A90C9AB1 # Danilo
7 |
8 | Update version numbers:
9 |
10 | $ vim app/build.gradle
11 |
12 | Update changelog:
13 |
14 | $ vim CHANGELOG.md
15 |
16 | Add the changelog to `metadata/en-US/changelogs/.txt` as well.
17 |
18 | Commit & tag:
19 |
20 | $ git commit -S${GPG_KEY} -m "Release v${VERSION}"
21 | $ git tag -s -u ${GPG_KEY} v${VERSION} -m "Version ${VERSION}"
22 |
23 | Generate signed release artifacts:
24 |
25 | ./gradlew clean assembleRelease buildApksRelease
26 |
27 | For the releases:
28 |
29 | - GitHub: Upload the signed release APK file to GitHub releases
30 | - Accrescent: Upload the signed release APKS file
31 | - F-Droid: Will be updated automatically (might take a few days)
32 |
--------------------------------------------------------------------------------
/app/build.gradle:
--------------------------------------------------------------------------------
1 | plugins {
2 | id "com.android.application"
3 | id "app.accrescent.tools.bundletool" version "0.2.4"
4 | }
5 |
6 | def getReleaseProperties() {
7 | final Properties props = new Properties()
8 | final File propertiesFile = file("${projectDir.getParent()}/release.properties")
9 | if (propertiesFile.exists()) {
10 | props.load(new FileInputStream(propertiesFile))
11 | }
12 | return props
13 | }
14 |
15 | final Properties properties = getReleaseProperties()
16 |
17 | android {
18 | namespace 'io.spaceapi.community.myhackerspace'
19 |
20 | defaultConfig {
21 | applicationId "io.spaceapi.community.myhackerspace"
22 | minSdkVersion 26
23 | compileSdk 34
24 | targetSdkVersion 34
25 | versionCode 107
26 | versionName "2.2.0"
27 | }
28 |
29 | buildFeatures {
30 | buildConfig = true
31 | }
32 |
33 | signingConfigs {
34 | if (releaseProperties['keystoreFile'] != null) {
35 | release {
36 | storeFile file(releaseProperties['keystoreFile'])
37 | storePassword releaseProperties['keystorePassword']
38 | keyAlias releaseProperties['keyAlias']
39 | keyPassword releaseProperties['keyPassword']
40 | }
41 | } else {
42 | logger.warn('No release config found. In case you want to create a release build, please ensure that `release.properties` exists. See README.md for more information.')
43 | }
44 | }
45 |
46 | buildTypes {
47 | release {
48 | minifyEnabled false
49 | proguardFiles getDefaultProguardFile('proguard-android.txt'), 'proguard-rules.txt'
50 | if (releaseProperties['keystoreFile'] != null) {
51 | signingConfig signingConfigs.release
52 | }
53 | }
54 | }
55 |
56 | compileOptions {
57 | sourceCompatibility = JavaVersion.VERSION_17
58 | targetCompatibility = JavaVersion.VERSION_17
59 | }
60 |
61 | lint {
62 | warning 'MissingTranslation'
63 | }
64 | }
65 |
66 | dependencies {
67 | implementation "androidx.annotation:annotation:1.9.1"
68 |
69 | // SpaceAPI Library
70 | implementation "io.github.spaceapi-community:spaceapi-kt:0.7.0"
71 |
72 | // JUnit for testing
73 | testImplementation 'junit:junit:4.13.2'
74 | }
75 |
76 | bundletool {
77 | if (releaseProperties['keystoreFile'] != null) {
78 | signingConfig {
79 | storeFile = file(releaseProperties['keystoreFile'])
80 | storePassword = releaseProperties['keystorePassword']
81 | keyAlias = releaseProperties['keyAlias']
82 | keyPassword = releaseProperties['keyPassword']
83 | }
84 | }
85 | }
--------------------------------------------------------------------------------
/app/proguard-rules.txt:
--------------------------------------------------------------------------------
1 | # To enable ProGuard in your project, edit project.properties
2 | # to define the proguard.config property as described in that file.
3 | #
4 | # Add project specific ProGuard rules here.
5 | # By default, the flags in this file are appended to flags specified
6 | # in ${sdk.dir}/tools/proguard/proguard-android.txt
7 | # You can edit the include path and order by changing the ProGuard
8 | # include property in project.properties.
9 | #
10 | # For more details, see
11 | # http://developer.android.com/guide/developing/tools/proguard.html
12 |
13 | # Add any project specific keep options here:
14 |
15 | # If your project uses WebView with JS, uncomment the following
16 | # and specify the fully qualified class name to the JavaScript interface
17 | # class:
18 | #-keepclassmembers class fqcn.of.javascript.interface.for.webview {
19 | # public *;
20 | #}
21 |
--------------------------------------------------------------------------------
/app/src/main/AndroidManifest.xml:
--------------------------------------------------------------------------------
1 |
2 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
20 |
21 |
26 |
27 |
28 |
29 |
30 |
31 |
32 |
36 |
37 |
38 |
39 |
40 |
41 |
42 |
46 |
47 |
48 |
49 |
50 |
51 |
54 |
55 |
56 |
57 |
58 |
59 |
60 |
63 |
64 |
65 |
66 |
67 |
68 |
69 |
70 |
71 |
74 |
75 |
76 |
77 |
78 |
79 |
80 |
--------------------------------------------------------------------------------
/app/src/main/ic_launcher-playstore.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/spaceapi-community/my-hackerspace/07f765319be6c9f104ba22f2df1bb2653e8d7939/app/src/main/ic_launcher-playstore.png
--------------------------------------------------------------------------------
/app/src/main/java/com/woozzu/android/util/StringMatcher.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 woozzu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.woozzu.android.util;
18 |
19 | public class StringMatcher {
20 |
21 | private final static char KOREAN_UNICODE_START = '가';
22 | private final static char KOREAN_UNICODE_END = '힣';
23 | private final static char KOREAN_UNIT = '까' - '가';
24 | private final static char[] KOREAN_INITIAL = {'ㄱ', 'ㄲ', 'ㄴ', 'ㄷ', 'ㄸ', 'ㄹ', 'ㅁ', 'ㅂ', 'ㅃ', 'ㅅ'
25 | , 'ㅆ', 'ㅇ', 'ㅈ', 'ㅉ', 'ㅊ', 'ㅋ', 'ㅌ', 'ㅍ', 'ㅎ'};
26 |
27 | public static boolean match(String value, String keyword) {
28 | if (value == null || keyword == null)
29 | return false;
30 | if (keyword.length() > value.length())
31 | return false;
32 |
33 | int i = 0, j = 0;
34 | do {
35 | if (isKorean(value.charAt(i)) && isInitialSound(keyword.charAt(j))) {
36 | if (keyword.charAt(j) == getInitialSound(value.charAt(i))) {
37 | i++;
38 | j++;
39 | } else if (j > 0)
40 | break;
41 | else
42 | i++;
43 | } else {
44 | if (keyword.charAt(j) == value.charAt(i)) {
45 | i++;
46 | j++;
47 | } else if (j > 0)
48 | break;
49 | else
50 | i++;
51 | }
52 | } while (i < value.length() && j < keyword.length());
53 |
54 | return (j == keyword.length())? true : false;
55 | }
56 |
57 | private static boolean isKorean(char c) {
58 | if (c >= KOREAN_UNICODE_START && c <= KOREAN_UNICODE_END)
59 | return true;
60 | return false;
61 | }
62 |
63 | private static boolean isInitialSound(char c) {
64 | for (char i : KOREAN_INITIAL) {
65 | if (c == i)
66 | return true;
67 | }
68 | return false;
69 | }
70 |
71 | private static char getInitialSound(char c) {
72 |
73 | if(!isKorean(c)){
74 | return c;
75 | }
76 |
77 | return KOREAN_INITIAL[(c - KOREAN_UNICODE_START) / KOREAN_UNIT];
78 | }
79 | }
80 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woozzu/android/widget/IndexScroller.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 woozzu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.woozzu.android.widget;
18 |
19 | import android.content.Context;
20 | import android.graphics.Canvas;
21 | import android.graphics.Color;
22 | import android.graphics.Paint;
23 | import android.graphics.RectF;
24 | import android.os.Handler;
25 | import android.os.Message;
26 | import android.os.SystemClock;
27 | import android.view.MotionEvent;
28 | import android.widget.Adapter;
29 | import android.widget.ListView;
30 | import android.widget.SectionIndexer;
31 |
32 | public class IndexScroller {
33 |
34 | private float mIndexbarWidth;
35 | private float mIndexbarMargin;
36 | private float mPreviewPadding;
37 | private float mDensity;
38 | private float mScaledDensity;
39 | private float mAlphaRate;
40 | private int mState = STATE_HIDDEN;
41 | private int mListViewWidth;
42 | private int mListViewHeight;
43 | private int mCurrentSection = -1;
44 | private boolean mIsIndexing = false;
45 | private ListView mListView = null;
46 | private SectionIndexer mIndexer = null;
47 | private String[] mSections = null;
48 | private RectF mIndexbarRect;
49 |
50 | private static final int STATE_HIDDEN = 0;
51 | private static final int STATE_SHOWING = 1;
52 | private static final int STATE_SHOWN = 2;
53 | private static final int STATE_HIDING = 3;
54 |
55 | public IndexScroller(Context context, ListView lv) {
56 | mDensity = context.getResources().getDisplayMetrics().density;
57 | mScaledDensity = context.getResources().getDisplayMetrics().scaledDensity;
58 | mListView = lv;
59 | setAdapter(mListView.getAdapter());
60 |
61 | mIndexbarWidth = 20 * mDensity;
62 | mIndexbarMargin = 10 * mDensity;
63 | mPreviewPadding = 5 * mDensity;
64 | }
65 |
66 | public void draw(Canvas canvas) {
67 | if (mState == STATE_HIDDEN)
68 | return;
69 |
70 | // mAlphaRate determines the rate of opacity
71 | Paint indexbarPaint = new Paint();
72 | indexbarPaint.setColor(Color.BLACK);
73 | indexbarPaint.setAlpha((int) (64 * mAlphaRate));
74 | indexbarPaint.setAntiAlias(true);
75 | canvas.drawRoundRect(mIndexbarRect, 5 * mDensity, 5 * mDensity, indexbarPaint);
76 |
77 | if (mSections != null && mSections.length > 0) {
78 | // Preview is shown when mCurrentSection is set
79 | if (mCurrentSection >= 0) {
80 | Paint previewPaint = new Paint();
81 | previewPaint.setColor(Color.BLACK);
82 | previewPaint.setAlpha(96);
83 | previewPaint.setAntiAlias(true);
84 | previewPaint.setShadowLayer(3, 0, 0, Color.argb(64, 0, 0, 0));
85 |
86 | Paint previewTextPaint = new Paint();
87 | previewTextPaint.setColor(Color.WHITE);
88 | previewTextPaint.setAntiAlias(true);
89 | previewTextPaint.setTextSize(50 * mScaledDensity);
90 |
91 | float previewTextWidth = previewTextPaint.measureText(mSections[mCurrentSection]);
92 | float previewSize = 2 * mPreviewPadding + previewTextPaint.descent() - previewTextPaint.ascent();
93 | RectF previewRect = new RectF((mListViewWidth - previewSize) / 2
94 | , (mListViewHeight - previewSize) / 2
95 | , (mListViewWidth - previewSize) / 2 + previewSize
96 | , (mListViewHeight - previewSize) / 2 + previewSize);
97 |
98 | canvas.drawRoundRect(previewRect, 5 * mDensity, 5 * mDensity, previewPaint);
99 | canvas.drawText(mSections[mCurrentSection], previewRect.left + (previewSize - previewTextWidth) / 2 - 1
100 | , previewRect.top + mPreviewPadding - previewTextPaint.ascent() + 1, previewTextPaint);
101 | }
102 |
103 | Paint indexPaint = new Paint();
104 | indexPaint.setColor(Color.WHITE);
105 | indexPaint.setAlpha((int) (255 * mAlphaRate));
106 | indexPaint.setAntiAlias(true);
107 | indexPaint.setTextSize(12 * mScaledDensity);
108 |
109 | float sectionHeight = (mIndexbarRect.height() - 2 * mIndexbarMargin) / mSections.length;
110 | float paddingTop = (sectionHeight - (indexPaint.descent() - indexPaint.ascent())) / 2;
111 | for (int i = 0; i < mSections.length; i++) {
112 | float paddingLeft = (mIndexbarWidth - indexPaint.measureText(mSections[i])) / 2;
113 | canvas.drawText(mSections[i], mIndexbarRect.left + paddingLeft
114 | , mIndexbarRect.top + mIndexbarMargin + sectionHeight * i + paddingTop - indexPaint.ascent(), indexPaint);
115 | }
116 | }
117 | }
118 |
119 | public boolean onTouchEvent(MotionEvent ev) {
120 | switch (ev.getAction()) {
121 | case MotionEvent.ACTION_DOWN:
122 | // If down event occurs inside index bar region, start indexing
123 | if (mState != STATE_HIDDEN && contains(ev.getX(), ev.getY())) {
124 | setState(STATE_SHOWN);
125 |
126 | // It demonstrates that the motion event started from index bar
127 | mIsIndexing = true;
128 | // Determine which section the point is in, and move the list to that section
129 | mCurrentSection = getSectionByPoint(ev.getY());
130 | mListView.setSelection(mIndexer.getPositionForSection(mCurrentSection));
131 | return true;
132 | }
133 | break;
134 | case MotionEvent.ACTION_MOVE:
135 | if (mIsIndexing) {
136 | // If this event moves inside index bar
137 | if (contains(ev.getX(), ev.getY())) {
138 | // Determine which section the point is in, and move the list to that section
139 | mCurrentSection = getSectionByPoint(ev.getY());
140 | mListView.setSelection(mIndexer.getPositionForSection(mCurrentSection));
141 | }
142 | return true;
143 | }
144 | break;
145 | case MotionEvent.ACTION_UP:
146 | if (mIsIndexing) {
147 | mIsIndexing = false;
148 | mCurrentSection = -1;
149 | }
150 | if (mState == STATE_SHOWN)
151 | setState(STATE_HIDING);
152 | break;
153 | }
154 | return false;
155 | }
156 |
157 | public void onSizeChanged(int w, int h, int oldw, int oldh) {
158 | mListViewWidth = w;
159 | mListViewHeight = h;
160 | mIndexbarRect = new RectF(w - mIndexbarMargin - mIndexbarWidth
161 | , mIndexbarMargin
162 | , w - mIndexbarMargin
163 | , h - mIndexbarMargin);
164 | }
165 |
166 | public void show() {
167 | if (mState == STATE_HIDDEN)
168 | setState(STATE_SHOWING);
169 | else if (mState == STATE_HIDING)
170 | setState(STATE_HIDING);
171 | }
172 |
173 | public void hide() {
174 | if (mState == STATE_SHOWN)
175 | setState(STATE_HIDING);
176 | }
177 |
178 | public void setAdapter(Adapter adapter) {
179 | if (adapter instanceof SectionIndexer) {
180 | mIndexer = (SectionIndexer) adapter;
181 | mSections = (String[]) mIndexer.getSections();
182 | }
183 | }
184 |
185 | private void setState(int state) {
186 | if (state < STATE_HIDDEN || state > STATE_HIDING)
187 | return;
188 |
189 | mState = state;
190 | switch (mState) {
191 | case STATE_HIDDEN:
192 | // Cancel any fade effect
193 | mHandler.removeMessages(0);
194 | break;
195 | case STATE_SHOWING:
196 | // Start to fade in
197 | mAlphaRate = 0;
198 | fade(0);
199 | break;
200 | case STATE_SHOWN:
201 | // Cancel any fade effect
202 | mHandler.removeMessages(0);
203 | break;
204 | case STATE_HIDING:
205 | // Start to fade out after three seconds
206 | mAlphaRate = 1;
207 | fade(3000);
208 | break;
209 | }
210 | }
211 |
212 | public boolean contains(float x, float y) {
213 | // Determine if the point is in index bar region, which includes the right margin of the bar
214 | return (x >= mIndexbarRect.left && y >= mIndexbarRect.top && y <= mIndexbarRect.top + mIndexbarRect.height());
215 | }
216 |
217 | private int getSectionByPoint(float y) {
218 | if (mSections == null || mSections.length == 0)
219 | return 0;
220 | if (y < mIndexbarRect.top + mIndexbarMargin)
221 | return 0;
222 | if (y >= mIndexbarRect.top + mIndexbarRect.height() - mIndexbarMargin)
223 | return mSections.length - 1;
224 | return (int) ((y - mIndexbarRect.top - mIndexbarMargin) / ((mIndexbarRect.height() - 2 * mIndexbarMargin) / mSections.length));
225 | }
226 |
227 | private void fade(long delay) {
228 | mHandler.removeMessages(0);
229 | mHandler.sendEmptyMessageAtTime(0, SystemClock.uptimeMillis() + delay);
230 | }
231 |
232 | private Handler mHandler = new Handler() {
233 |
234 | @Override
235 | public void handleMessage(Message msg) {
236 | super.handleMessage(msg);
237 |
238 | switch (mState) {
239 | case STATE_SHOWING:
240 | // Fade in effect
241 | mAlphaRate += (1 - mAlphaRate) * 0.2;
242 | if (mAlphaRate > 0.9) {
243 | mAlphaRate = 1;
244 | setState(STATE_SHOWN);
245 | }
246 |
247 | mListView.invalidate();
248 | fade(10);
249 | break;
250 | case STATE_SHOWN:
251 | // If no action, hide automatically
252 | setState(STATE_HIDING);
253 | break;
254 | case STATE_HIDING:
255 | // Fade out effect
256 | mAlphaRate -= mAlphaRate * 0.2;
257 | if (mAlphaRate < 0.1) {
258 | mAlphaRate = 0;
259 | setState(STATE_HIDDEN);
260 | }
261 |
262 | mListView.invalidate();
263 | fade(10);
264 | break;
265 | }
266 | }
267 |
268 | };
269 | }
270 |
--------------------------------------------------------------------------------
/app/src/main/java/com/woozzu/android/widget/IndexableListView.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright 2011 woozzu
3 | *
4 | * Licensed under the Apache License, Version 2.0 (the "License");
5 | * you may not use this file except in compliance with the License.
6 | * You may obtain a copy of the License at
7 | *
8 | * http://www.apache.org/licenses/LICENSE-2.0
9 | *
10 | * Unless required by applicable law or agreed to in writing, software
11 | * distributed under the License is distributed on an "AS IS" BASIS,
12 | * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 | * See the License for the specific language governing permissions and
14 | * limitations under the License.
15 | */
16 |
17 | package com.woozzu.android.widget;
18 |
19 | import android.content.Context;
20 | import android.graphics.Canvas;
21 | import android.util.AttributeSet;
22 | import android.view.GestureDetector;
23 | import android.view.MotionEvent;
24 | import android.widget.ListAdapter;
25 | import android.widget.ListView;
26 |
27 | public class IndexableListView extends ListView {
28 |
29 | private boolean mIsFastScrollEnabled = false;
30 | private IndexScroller mScroller = null;
31 | private GestureDetector mGestureDetector = null;
32 |
33 | public IndexableListView(Context context) {
34 | super(context);
35 | }
36 |
37 | public IndexableListView(Context context, AttributeSet attrs) {
38 | super(context, attrs);
39 | }
40 |
41 | public IndexableListView(Context context, AttributeSet attrs, int defStyle) {
42 | super(context, attrs, defStyle);
43 | }
44 |
45 | @Override
46 | public boolean isFastScrollEnabled() {
47 | return mIsFastScrollEnabled;
48 | }
49 |
50 | @Override
51 | public void setFastScrollEnabled(boolean enabled) {
52 | mIsFastScrollEnabled = enabled;
53 | if (mIsFastScrollEnabled) {
54 | if (mScroller == null)
55 | mScroller = new IndexScroller(getContext(), this);
56 | } else {
57 | if (mScroller != null) {
58 | mScroller.hide();
59 | mScroller = null;
60 | }
61 | }
62 | }
63 |
64 | @Override
65 | public void draw(Canvas canvas) {
66 | super.draw(canvas);
67 |
68 | // Overlay index bar
69 | if (mScroller != null)
70 | mScroller.draw(canvas);
71 | }
72 |
73 | @Override
74 | public boolean onTouchEvent(MotionEvent ev) {
75 | // Intercept ListView's touch event
76 | if (mScroller != null && mScroller.onTouchEvent(ev))
77 | return true;
78 |
79 | if (mGestureDetector == null) {
80 | mGestureDetector = new GestureDetector(getContext(), new GestureDetector.SimpleOnGestureListener() {
81 |
82 | @Override
83 | public boolean onFling(MotionEvent e1, MotionEvent e2,
84 | float velocityX, float velocityY) {
85 | // If fling happens, index bar shows
86 | if (mScroller != null)
87 | mScroller.show();
88 | return super.onFling(e1, e2, velocityX, velocityY);
89 | }
90 |
91 | });
92 | }
93 | mGestureDetector.onTouchEvent(ev);
94 |
95 | return super.onTouchEvent(ev);
96 | }
97 |
98 | @Override
99 | public boolean onInterceptTouchEvent(MotionEvent ev) {
100 | if(mScroller.contains(ev.getX(), ev.getY()))
101 | return true;
102 |
103 | return super.onInterceptTouchEvent(ev);
104 | }
105 |
106 | @Override
107 | public void setAdapter(ListAdapter adapter) {
108 | super.setAdapter(adapter);
109 | if (mScroller != null)
110 | mScroller.setAdapter(adapter);
111 | }
112 |
113 | @Override
114 | protected void onSizeChanged(int w, int h, int oldw, int oldh) {
115 | super.onSizeChanged(w, h, oldw, oldh);
116 | if (mScroller != null)
117 | mScroller.onSizeChanged(w, h, oldw, oldh);
118 | }
119 |
120 | }
121 |
--------------------------------------------------------------------------------
/app/src/main/java/io/spaceapi/community/myhackerspace/AboutLayout.java:
--------------------------------------------------------------------------------
1 | package io.spaceapi.community.myhackerspace;
2 |
3 | import android.content.Context;
4 | import android.util.AttributeSet;
5 | import android.view.LayoutInflater;
6 | import android.widget.LinearLayout;
7 | import android.widget.TextView;
8 |
9 | import androidx.annotation.Nullable;
10 |
11 | public class AboutLayout extends LinearLayout {
12 |
13 | public AboutLayout(Context context) {
14 | super(context);
15 | }
16 |
17 | public AboutLayout(Context context, @Nullable AttributeSet attrs) {
18 | super(context, attrs);
19 | }
20 |
21 | public AboutLayout(Context context, @Nullable AttributeSet attrs, int defStyleAttr) {
22 | super(context, attrs, defStyleAttr);
23 | }
24 |
25 | public AboutLayout(Context context, AttributeSet attrs, int defStyleAttr, int defStyleRes) {
26 | super(context, attrs, defStyleAttr, defStyleRes);
27 | }
28 |
29 | public void init() {
30 | TextView version = findViewById(R.id.about_version_text);
31 | version.setText(BuildConfig.VERSION_NAME + " (" + Integer.toString(BuildConfig.VERSION_CODE) + ")");
32 | }
33 |
34 | public static AboutLayout create(Context context) {
35 | LayoutInflater layoutInflater = LayoutInflater.from(context);
36 | AboutLayout about = (AboutLayout) layoutInflater.inflate(R.layout.about, null, false);
37 | about.init();
38 | return about;
39 | }
40 | }
41 |
--------------------------------------------------------------------------------
/app/src/main/java/io/spaceapi/community/myhackerspace/Net.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012-2017 Aubort Jean-Baptiste (Rorist)
3 | * Copyright (C) 2020-2025 Danilo Bargen (dbrgn)
4 | * Licensed under GNU's GPL 3, see README
5 | */
6 | package io.spaceapi.community.myhackerspace;
7 |
8 | import android.graphics.Bitmap;
9 | import android.graphics.BitmapFactory;
10 | import android.os.Build;
11 | import android.util.Log;
12 |
13 | import java.io.BufferedReader;
14 | import java.io.FilterInputStream;
15 | import java.io.IOException;
16 | import java.io.InputStream;
17 | import java.io.InputStreamReader;
18 | import java.net.HttpURLConnection;
19 | import java.net.URL;
20 |
21 | import androidx.annotation.NonNull;
22 | import androidx.annotation.WorkerThread;
23 |
24 | // From CommonsWare and Android Blog
25 | // https://github.com/commonsguy/cw-android/tree/master/Internet
26 | // http://android-developers.blogspot.ch/2010/07/multithreading-for-performance.html
27 | public class Net {
28 |
29 | private final String USERAGENT = "Android/" + Build.VERSION.RELEASE + " ("
30 | + Build.MODEL + ") MyHackerspace/" + BuildConfig.VERSION_NAME;
31 |
32 | private static final String TAG = "MyHackerspace_Net";
33 |
34 | private HttpURLConnection mUrlConnection;
35 | private InputStream mInputStream;
36 |
37 | @WorkerThread
38 | public Net(@NonNull String urlStr) throws Throwable {
39 | this(urlStr, true);
40 | }
41 |
42 | @WorkerThread
43 | public Net(@NonNull String urlStr, boolean useCache) throws Throwable {
44 | // Connect to URL
45 | URL url;
46 | int responseCode;
47 | int redirect_limt = 10;
48 | do {
49 | Log.v(TAG, "fetching " + urlStr);
50 | url = new URL(urlStr);
51 | mUrlConnection = (HttpURLConnection) url.openConnection();
52 | mUrlConnection.setRequestProperty("User-Agent", USERAGENT);
53 | mUrlConnection.setUseCaches(useCache);
54 |
55 | mUrlConnection.connect();
56 | responseCode = mUrlConnection.getResponseCode();
57 |
58 | // HttpsURLConnection does not support redirect with protocol switch,
59 | // so we take care of that here:
60 | if(responseCode == HttpURLConnection.HTTP_MOVED_TEMP
61 | || responseCode == HttpURLConnection.HTTP_MOVED_PERM) {
62 | urlStr = mUrlConnection.getHeaderField("Location");
63 | redirect_limt -= 1;
64 | } else {
65 | break;
66 | }
67 | } while(redirect_limt > 0);
68 |
69 | if (responseCode != HttpURLConnection.HTTP_OK) {
70 | String msg = mUrlConnection.getResponseMessage();
71 | mUrlConnection.disconnect();
72 | throw new Throwable(msg);
73 | }
74 |
75 | mInputStream = mUrlConnection.getInputStream();
76 | }
77 |
78 | @WorkerThread
79 | @NonNull
80 | public String getString() throws Throwable {
81 | try {
82 | BufferedReader r = new BufferedReader(new InputStreamReader(mInputStream));
83 | StringBuilder str = new StringBuilder();
84 | String line;
85 | while ((line = r.readLine()) != null) {
86 | str.append(line);
87 | }
88 | return str.toString();
89 | } finally {
90 | if (mInputStream != null) {
91 | mInputStream.close();
92 | }
93 | mUrlConnection.disconnect();
94 | }
95 | }
96 |
97 | @WorkerThread
98 | @NonNull
99 | public Bitmap getBitmap() throws Throwable {
100 | try {
101 | return BitmapFactory.decodeStream(new FlushedInputStream(mInputStream));
102 | } finally {
103 | if (mInputStream != null) {
104 | mInputStream.close();
105 | }
106 | mUrlConnection.disconnect();
107 | }
108 | }
109 |
110 | static class FlushedInputStream extends FilterInputStream {
111 | public FlushedInputStream(InputStream inputStream) {
112 | super(inputStream);
113 | }
114 |
115 | @Override
116 | public long skip(long n) throws IOException {
117 | long totalBytesSkipped = 0L;
118 | while (totalBytesSkipped < n) {
119 | long bytesSkipped = in.skip(n - totalBytesSkipped);
120 | if (bytesSkipped == 0L) {
121 | int b = read();
122 | if (b < 0) {
123 | break; // we reached EOF
124 | } else {
125 | bytesSkipped = 1; // we read one byte
126 | }
127 | }
128 | totalBytesSkipped += bytesSkipped;
129 | }
130 | return totalBytesSkipped;
131 | }
132 | }
133 |
134 | }
135 |
--------------------------------------------------------------------------------
/app/src/main/java/io/spaceapi/community/myhackerspace/Network.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012-2017 Aubort Jean-Baptiste (Rorist)
3 | * Copyright (C) 2020-2025 Danilo Bargen (dbrgn)
4 | * Licensed under GNU's GPL 3, see README
5 | */
6 | package io.spaceapi.community.myhackerspace;
7 |
8 | import android.content.BroadcastReceiver;
9 | import android.content.Context;
10 | import android.content.Intent;
11 | import android.util.Log;
12 |
13 | public class Network extends BroadcastReceiver {
14 |
15 | @Override
16 | public void onReceive(Context ctxt, Intent intent) {
17 | if (Main.hasNetwork(ctxt)) {
18 | Log.i(Main.TAG, "Update widget on " + intent.getAction());
19 | Widget.UpdateAllWidgets(ctxt, true);
20 | } else {
21 | Log.e(Main.TAG, "Network not ready on " + intent.getAction());
22 | }
23 | }
24 |
25 | }
26 |
--------------------------------------------------------------------------------
/app/src/main/java/io/spaceapi/community/myhackerspace/Prefs.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012-2017 Aubort Jean-Baptiste (Rorist)
3 | * Copyright (C) 2020-2025 Danilo Bargen (dbrgn)
4 | * Licensed under GNU's GPL 3, see README
5 | */
6 | package io.spaceapi.community.myhackerspace;
7 |
8 | import static android.view.ViewGroup.LayoutParams.*;
9 |
10 | import android.content.SharedPreferences;
11 | import android.content.SharedPreferences.OnSharedPreferenceChangeListener;
12 | import android.os.Bundle;
13 | import android.preference.PreferenceActivity;
14 | import android.preference.PreferenceScreen;
15 | import android.view.LayoutInflater;
16 | import android.view.ViewGroup;
17 | import android.widget.LinearLayout;
18 |
19 | public class Prefs extends PreferenceActivity implements
20 | OnSharedPreferenceChangeListener {
21 |
22 | public static final String KEY_API_ENDPOINT = "api_endpoint";
23 | public static final String DEFAULT_API_ENDPOINT = "https://raw.githubusercontent.com/SpaceApi/directory/master/directory.json";
24 |
25 | public static final String KEY_API_URL = "apiurl";
26 |
27 | public static final String KEY_CHECK_INTERVAL = "check_interval";
28 | public static final String DEFAULT_CHECK_INTERVAL = "30"; // minutes
29 |
30 | public static final String KEY_WIDGET_TRANSPARENCY = "widget_transparency";
31 | public static final boolean DEFAULT_WIDGET_TRANSPARENCY = false;
32 |
33 | public static final String KEY_WIDGET_TEXT = "widget_text";
34 | public static final boolean DEFAULT_WIDGET_TEXT = false;
35 |
36 | public void onCreate(Bundle savedInstanceState) {
37 | super.onCreate(savedInstanceState);
38 | addPreferencesFromResource(R.xml.preferences);
39 | this.getListView().addFooterView(AboutLayout.create(this)); // tried addContentView
40 | PreferenceScreen ps = getPreferenceScreen();
41 | ps.getSharedPreferences()
42 | .registerOnSharedPreferenceChangeListener(this);
43 | }
44 |
45 | public void onSharedPreferenceChanged(SharedPreferences prefs, String key) {
46 | if (key.equals(KEY_WIDGET_TRANSPARENCY) || key.equals(KEY_WIDGET_TEXT)
47 | || key.equals(KEY_CHECK_INTERVAL)) {
48 | Widget.UpdateAllWidgets(getApplicationContext(), true);
49 | }
50 | }
51 |
52 | }
53 |
--------------------------------------------------------------------------------
/app/src/main/java/io/spaceapi/community/myhackerspace/Utils.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2020-2025 Danilo Bargen (dbrgn)
3 | * Licensed under GNU's GPL 3, see README
4 | */
5 | package io.spaceapi.community.myhackerspace;
6 |
7 | import androidx.annotation.NonNull;
8 | import androidx.annotation.Nullable;
9 |
10 | public class Utils {
11 | /**
12 | * Join the strings using the specified separator.
13 | */
14 | public static @Nullable String joinStrings(@NonNull String separator, String... strings) {
15 | final StringBuilder builder = new StringBuilder();
16 | boolean empty = true;
17 | for (String string : strings) {
18 | if (string != null) {
19 | if (empty) {
20 | builder.append(string);
21 | empty = false;
22 | } else {
23 | builder.append(separator).append(string);
24 | }
25 | }
26 | }
27 | return empty ? null : builder.toString();
28 | }
29 | }
30 |
--------------------------------------------------------------------------------
/app/src/main/java/io/spaceapi/community/myhackerspace/Widget.java:
--------------------------------------------------------------------------------
1 | /*
2 | * Copyright (C) 2012-2017 Aubort Jean-Baptiste (Rorist)
3 | * Copyright (C) 2020-2025 Danilo Bargen (dbrgn)
4 | * Licensed under GNU's GPL 3, see README
5 | */
6 | package io.spaceapi.community.myhackerspace;
7 |
8 | import android.app.AlarmManager;
9 | import android.app.IntentService;
10 | import android.app.PendingIntent;
11 | import android.appwidget.AppWidgetManager;
12 | import android.appwidget.AppWidgetProvider;
13 | import android.content.ComponentName;
14 | import android.content.Context;
15 | import android.content.Intent;
16 | import android.content.SharedPreferences;
17 | import android.content.SharedPreferences.Editor;
18 | import android.graphics.Bitmap;
19 | import android.os.AsyncTask;
20 | import android.os.Build;
21 | import android.os.Handler;
22 | import android.os.Looper;
23 | import android.preference.PreferenceManager;
24 | import android.util.Log;
25 | import android.view.View;
26 | import android.widget.RemoteViews;
27 | import android.widget.Toast;
28 |
29 | import java.lang.ref.WeakReference;
30 | import java.net.MalformedURLException;
31 | import java.net.URL;
32 |
33 | import io.spaceapi.ParseError;
34 | import io.spaceapi.SpaceApiParser;
35 |
36 | public class Widget extends AppWidgetProvider {
37 |
38 | static final String TAG ="MyHackerspace_Widget";
39 | static final String WIDGET_IDS = "widget_ids";
40 | static final String WIDGET_FORCE = "widget_force";
41 |
42 | public void onUpdate(Context context, AppWidgetManager appWidgetManager, int[] appWidgetIds) {
43 | // see https://intrepidgeeks.com/tutorial/android-app-widgets
44 | final int N = appWidgetIds.length;
45 |
46 | // Perform this loop procedure for each App Widget that belongs to this provider
47 | for (int i=0; i