Sunday, April 21, 2024

Elevation profile app for Android, touching the base

Google is forcing me to update my Android apps, so after about three years of not even looking at it, I am returning to Elevation and Sea Depth. I've decided to "touch the base" and review the app history before preparing it for the next, 33rd release.

The app was initially launched on May 1, 2010, and it was the third Android app that I developed. I still consider it the most interesting one that I made. Believe it or not, the APK had a size of 57.8KB. I even kept some screenshots from that time.

Currently, the Play Store is reporting over 50,000 cumulative downloads. I am unable to access more detailed statistics for the period before July 25, 2019, as the Play Store does not display it, and Flurry Analytics, that app once used, has been sunset. But here is what I have.

Elevation and Sea Depth targets a very specific group of people in particular circumstances, so I can't really expect mind-blowing usage. However, 188 active installation out of those 50,000+ downloads is a bit ... disappointing? I believe the app probably peaked around Jul 2017, at least judging by the number of AdMob requests.

The total all-time revenue from ads was more than a whopping $1,080. You can't imagine how I spoiled myself with that money. I say "more than" because AdMob doesn't show statistics for the first 4 years of the app's lifetime.

According to the data, the golden era of AdMob revenue, at least for this app, was from March to June 2017.

The first license for the full app version was sold for $5 USD on August 7, 2010. At that time, merchant accounts from my country were not supported by the Google Play Store (then called Android Market), so I had to find an e-commerce provider and implement my own license server.

Overall, during that period, 186 licenses were sold. The sales stopped in September 2011 and started again in January 2012 when I dropped the price to $0.99 USD. In May, I completely switched to another e-commerce provider and stayed with them until 2019 when they stopped supporting Android. I managed to sell the first 34 licenses for $5 USD and the remaining 152 for $0.99 USD, totaling $320.48 before deducting all the fees.

In 2019, the Play Store started supporting my country as well, so I had to switch to their monetization platform and drop all third-party e-commerce providers. Three licenses were sold for $0.99 each. Next year, I decided not to sell the app at such a low price any more, so I switched to subscription payments, just like with the rest of my apps, with a yearly price of $15.99 USD. So far, two subscriptions have been sold.

To sum it all up, this app has made me less than $1,500 USD in the last 14 years. Far from what more successful apps of mine made, but still four times more than what I made with Weather Forecast USA.

The question is: can I squeeze more juice out of it? I definitely don't want to kill my baby, but obviously it's been in a kind of zombie state forever. Well, I guess we'll se if it picks up after the update.


Saturday, April 20, 2024

Mindfulness Meditation Android app redesign

The (free, no ads) app is available here: Mindfulness Meditation for Android.

Moodboard

First time using a moodboard for one of my apps!
 

Feature graphic

Before
After

Launch icon

Before
After

Screenshots

Before

After


Before


After


Before


After


Before


After

Sunday, April 14, 2024

Updating Healthy Battery Charging to Android API level 34

To prolong your Li-ion battery life, reduce e-waste, conserve rare minerals, prevent child labor, i.e. contribute saving the planet - or to save own money if it rings more with you, keep it charged between 40% and 80%, all the time. 

(Technical details about Li-ion batteries are available at the following link: BU-808: How to Prolong Lithium-based Batteries.)

I discovered these facts in 2017, and that's when I published a small Android app that alerts me when it's the right time to connect/disconnect the charger. Healthy Battery Charging was just a minimum viable product, a tool for my own use. I never planed to monetize it, so I released it as a free app, without ads, and I never updated it since.

Fast forward to the year 2024, I have decided to revisit the app, just for the sake of refreshing my knowledge about the way Android handles notifications. A lot of things changed over the years, and I ended up almost rewriting the complete app. And redesigning it, since I am already investing my time.

I've decided to utilize this blog post to track my own design updates and organize my thoughts around notification handling on android. So here we go ...

UI Updates

Launch icon before
Launch icon after
Feature graphic before


Feature graphic after


Screenshot before

Screenshot after

Not sure about you, but I can notice the difference in my own design skills after taking a UX/UI course. 😉

Technicalities

The app schedules a background repeating alarm to check battery and power connection status. The alarm repeats every 15 minutes, though all alarms on android are now inexact and the alarm can be batched with other alarms and triggered later, for example when the device wakes up from the sleep. But for this use case, an exact alarm isn't necessary.

There are situations where pausing the repeating alarm would make sense - once the notification is displayed to the user, it's not necessary to check the status again until the charger is connected/disconnected. The previous app version used to work this way - it had a receiver registered for android.intent.action.ACTION_POWER_CONNECTED and android.intent.action.ACTION_POWER_DISCONNECTED. However, starting from API level 26, these events are not broadcasted to receivers running in the background [docs, exceptions]. They are still broadcasted to foreground services.

When targeting API level 34 or higher, all foreground services must be declared with their service types using android:foregroundServiceType. Currently, the specialUse type seems most suitable for this use case. Additionally, you need to request FOREGROUND_SERVICE permission

Furthermore, apps targeting API level 31 or higher can't start foreground services while running in the background, except for a few special cases.

To maintain the same functionality, a foreground service needs to be started from the boot receiver. The service would need to permanently display a notification. It would not be possible to dismiss it (no notification = no service running), so perhaps it would just change it's text from "Monitoring the battery" to "Connect/Disconnect the charger". I find a permanent notification a bit too much for an app that is supposed to silently run in the background and be invisible most of the time. 

For this reason, at the moment I am preferring the simple approach:

  • Do not pause any alarms and keep them running every 15 minutes. 
  • Do not display notification if it hasn't been previously dismissed. 
  • Dismiss the notification on the next alarm run if the charger has been connected/disconnected. As previously described, notifications can not be dismissed automatically.

Other relevant questions and answers on StackOverflow:
How to listen for power connected Android > 8
How to check which notifications are active in status bar in Android Dev?
Android - AlarmManager is not working after app is closed


https://developer.android.com/reference/android/content/pm/ServiceInfo?hl=en#FOREGROUND_SERVICE_TYPE_SPECIAL_USE
https://developer.android.com/reference/android/content/pm/ServiceInfo?hl=en#FOREGROUND_SERVICE_TYPE_SPECIAL_USE

Source code syntax highlighting in WordPress

I was using SyntaxHighlighter Evolved, but after I updated the Enfold theme, it's not working anymore. At least not in the classic editor.

I have tried Code Block Pro, I haven't figured out how to make it work with classic editor either. Enlighter doesn't seem to have been updated for a year or so, so in the end, I chose Prismatic.

Friday, April 12, 2024

How to make nice gradients

  • Do not make gradients with white or black color
  • Keep the saturation the same for gradient colors
  • Change hue 15 - 30%
 

Sunday, April 7, 2024

How to hide __pycache__ directory in Visual Studio Code

File -> Preferences -> Settings (or Ctrl+,)
Search for term: files.exclude
Click on Add Pattern
Add: **/__pycache__
Click on OK

Done!

Tuesday, March 26, 2024

The very first elevator pitch

Did you know that first ever 'elevator pitch' was done in an elevator? And it was about - elevators? Elevator brakes, to be more precise.

In the year 1853, Elisha Graves Otis invented a system that would automatically halt an elevator if the hoisting rope snapped. The sales didn't go as Elisha expected, so he decided to publicly demonstrate his invention at the exhibition at the New York Crystal Palace in 1854. An elevator was installed, and while he was in it, Elisha asked his assistant to cut the hoisting rope. Demonstration was successful and demand for safe elevators began to rise.

https://en.wikipedia.org/wiki/Elisha_Otis



Sunday, March 24, 2024

What to do if android studio pairing device using wifi doesn't work?

Open the terminal windows inside Android Studio and type the following:

cd $Env:LOCALAPPDATA\Android\sdk\platform-tools
./adb connect 192.168.2.142:38207

Replace the IP address and port with the data that you see when you enable wireless debugging on your device.

Saturday, March 23, 2024

Configure Firefox to allow restricted ports for InstantWP

InstantWP is a complete standalone, portable WordPress development environment. By default, Firefox might block the ports (above 10000) it uses. You'll see the following error message if this happens:

This address is restricted 
This address uses a network port which is normally used for purposes other than Web browsing. Firefox has canceled the request for your protection.

In order to resolve the issue, you need to:

  1. In a new tab, type or paste about:config in the address bar and press Enter/Return. Click the button promising to be careful. 
  2. Search for network.security.ports.banned.override
  3. Open it (or create new, if it doesn’t exist), and insert 10080 (as string, not as number!) at the end.

Wednesday, March 13, 2024

Čokolada i klimatske promjene

Čokoholičari, pozor! Ovaj graf ne prikazuje cijenu bitcoina nego kakaa. A cijena kakaa će se uskoro odraziti i na cijenu vaše omiljene čokolade. Inače, rekao bih vam da odete do dućana i kupite veću količinu odmah sad, ali znam u kojoj mjeri (ne)možete kontrolirati svoju konzumaciju, pa to baš i ne bi imalo nekog smisla. 😉
 
Zašto je cijena otišla nebu pod oblake? Možda ste čuli za onu novu foru koju vam svjetski establišment pokušava uvaliti. One nekakve 'klimatske promjene'. E, pa radi se baš o tome! Područje gdje inače raste kakaovac pretvara se u pustinju, pa nasadi ne daju baš neki urod. Čokolada postaje luksuz.
Usput, ne radi se samo o čokoladi, slična sudbina čeka i kavu, ali o njoj nekom drugom prilikom. 🙂


Sunday, March 10, 2024

2023 recalibration of Limits to Growth World3 model

The paper is available here: https://onlinelibrary.wiley.com/doi/10.1111/jiec.13442 [archive]

 

The famous chart

The following table is extracted from the supporting data:


Variable LtG BAU
peak
Recalibration23
peak
Industrial Output 2018 (2.62T) 2020 (2.60T)
Population 2028 (7.65B) 2033 (8.16B)
Food Production 2017 (3.15T) 2023 (3.71T)
Persistent Pollution 2039 (11.1T) 2088 (13.9T)



Saturday, March 9, 2024

The last Andoid Studio version for Windows 7

'Officially' ...

The last version of Android Studio that supports Windows 7 was 4.1.3. Since version 4.2.1, at least according to the 'System requirements' section, Windows 8 (or newer) is required. Release notes ([1], [2]) do not mention any change in minimum OS requirements.

Actually ...

At the moment of writing this, the latest stable version of Android Studio is Iguana (2023.2.1.23). It can be installed on Windows 7, but if you run it, you will receive the following error message:

The procedure entry point CreateAppContainerProfile could not be located in the dynamic link library USERENV.dll

You can avoid this error by starting studio.bat instead of studio64.exe (both located in Android Studio\bin directory). If you desire more information regarding the cause of the error, further explanation is available here.

If you want to play it safe, the last Android Studio version that runs without such a workaround is Hedgehog 2023.1.1 Patch 2, released on January 23, 2024. It is available for download in the Android Studio download archives. (It supports gradle up to version 8.2.2.)

Also note ...

This might not be the only issue you'll experience with new Android Studio installations. Starting with the platform tools version 34.0.5, adb does not support Windows 7 either. Although not mentioned in the release notes, this limitation is documented in the issue tracker (no, the error won't be fixed). Therefore, if you've installed the latest platform tools, you'll need to uninstall them and manually install platform tools version 34.0.4. The archive is available here.


Thursday, February 22, 2024

Using Python to post to reddit programmatically

My reddit account was banned after testing, so I'll leave experimenting and integration aside for some time. I am posting this short code snippet here anyway, just for my own convenience when I decide to pull it out again.

import praw

from config import REDDIT_CLIENT_ID
from config import REDDIT_CLIENT_SECRET
from config import REDDIT_USERNAME
from config import REDDIT_PASSWORD

reddit = praw.Reddit(
    client_id     = REDDIT_CLIENT_ID,
    client_secret = REDDIT_CLIENT_SECRET,
    user_agent    = "web:com.example:v1.0.0 (by u/your_username)",
    username      = REDDIT_USERNAME,
    password      = REDDIT_PASSWORD,
)

submission = reddit.subreddit("sub_name").submit(
    title    = "Post (link) title",
    url      = "https://example.com/url")

print(submission.permalink)

Sunday, February 18, 2024

Google Search Console for keyword research

Awesome trick for finding your gaps in content. The original post on reddit is here:
Google search console for keyword research is super powerful [archive]

For my own copy-paste convenience, the regex expression is:
(?i)^(who|what|where|when|why|how)

Open-ended:
(?i)^(.*who.*|.*what.*|.*where.*|.*when.*|.*why.*|.*how.*)

Few notes:

  • I didn't find 'Search Results', instead I clicked on 'Performance', chose 12 month period and added query by clicking on the '+ New' button.
  • One of the commenters is giving an additional tip here to cross-reference the impression and click data from Search Console with Google Trends.
  • One of the commenters is suggesting to use an 'open-ended' variation of regex here. In my test case I haven't noticed any difference.
  • This will not show you the keywords that your content is not ranking for. For that kind of research you need to use other tools, e.g. Google Ads.

Wednesday, February 14, 2024

Novine.eu - web portal sa kratkim vijestima, dizajn odluke

Web stranica je dostupna ovdje: Novine.eu - kratke vijesti

Slijedi nekakav tok misli vezanih uz UX/UI design projekta, kopiran iz komunikacije sa kolegama, da ga imam na jednom mjestu, a ne u 100 različitih chat-ova.

2024.02.14:

Kako je sve work in progress, ovo nije konačna verzija footera. U principu ... odlučio sam se riješiti headera jer želim da u prvom fokusu bude kraćenje vijesti, a u drugom tek čitanje ostalih vijesti koje su već skraćene. Pa je header (zajedno sa mode toggle-om) završio u footeru. Tako da su glavne kategorije ostale u većem fontu. Ali ... morat ću dodati i linkove na društvene mreže u footer, oni će biti manji, tako da još moram vidjeti kako će se sve to uklopiti.

Da, stalno vidljiv mode toggle ... u početku sam mislio - pa ne mijenjaju ljudi valjda cijelo vrijeme dark/light mode, valjda imaju neku preferencu i to je to. Ali onda sam shvatio da se vijesti čitaju i na otvorenom i na zatvorenom i kad tuče sunce i sl. ... pa je to plus argument za stalno dostupan toggle. Inače ideja je da se footer pojavi na prvom scroll-u i da ostane sticky na dnu ekrana. Tako da bi bio dostupan gotovo uvijek. Moguće i uvjek, imam neke ideje koji bi mi oslobodile više vertikalnog prostora.

2024.02.17:

Razmišljao sam o sidebaru! I to iz razloga što onaj trenutni burger na svome mobitelu zapravo ne mogu dohvatiti palcem. Što je u suprotnosti sa infographicsima koje smo vidjeli na predavanjima. Tako da bi sidebar najvjerojatnije bio sa desne strane. Ali, za sada ostaje burger menu. Generalno, 70% surfera dolazi sa mobitela, a trenutni copy-paste linkova na mobitelima je prekompliciran za korištenje, pa sam se odlučio prioritet ipak dati razvoju nekog jednostavnog app-a koji bi skratio taj proces. Mislim da mi je to trenutno najslabija karika. 

Da, prebacio sam blok za skraćivanje na vrh. Bio je sa strane zbog nekakvog zamišljenog boljeg iskorištavanja prostora na širokim ekranima. Ali, kako sam dodao novu (i najelegantniju) opciju unosa linkova za kraćenje (možeš pogledat), trebalo mi je više prostora. Na accordion ne bi išao, kad već korisnik ne krati vijesti, neka barem klikne na neku od već skraćenih. Stoga ih ne bi sakrivao iza ekstra klika, pogotovo što mi je ideja dodati stupac 'Najčitanije' ... ili čak zamijeniti 'Posljednje skraćeno' sa njim. Ali da, nisam se još odlučio bi li koristio klasičnu paginaciju, 'učitaj više' ili infinite scroll za ostatak vijesti.

Da, menu je trenutno prisutan samo prvoj stranici. Više razloga ... kategorije i pod-kategorije još nisu do kraja razrađene. Svaki portal ih ima nešto drugačije definirane, ja ih još nisam ni 'popisao' sve, pa sam ostavio to za kasnije. Inače, negdje sam pročitao da su mega-menu-i loši za SEO. Nisam siguran da li je to istina ili ne. Kao, nije dobro kad svaka stranica linka na svaku drugu. Pa sam kao neki eksperiment odlučio da 'leaf' stranice (u mom slučaju - članci) na dnu zapravo uopće nemaju menu sa rubrikama. U protivnom, npr. članak iz rubrike 'Znanost' bi imao link na rubriku 'Crna kronika'. Google-ov ranking algoritam je tajna, tako da nitko zapravo ne zna da li to znači šta ili ne.

Tuesday, February 13, 2024

UserWarning: Detected filter using positional arguments. Prefer using the 'filter' keyword argument instead.

If you are receiving that warning with your google datastore queries, you can resolve the it by using google.cloud.datastore.query.PropertyFilter(property_name, operator, value) (docs). The code snippet would be the following:

from google.cloud import datastore
from google.cloud.datastore.query import PropertyFilter

query = datastore.Client().query(kind="MyKind")
query.add_filter(filter=PropertyFilter("property", "=", value))

Sunday, February 11, 2024

OpenAI Whisper i hrvatski jezik

Virtualni seks i zamjena za obitelj, naziv je posljednje epizode Explore [1] na kojoj sam odlučio isprobati OpenAI-ov Whisper [2] za prepoznavanje hrvatskog jezika. Moj zaključak: neupotrebljivo. Neupotrebljivo u smislu da se opet treba poslušati audio kako bi se korigirao prepoznati dialog i, u nekim slučajevima, da bi se shvatilo o čemu se uopće govori.

Kod prepoznavanja govora do sada sam najbolje rezultate imao s Google Transcribe-om, ali on postoji samo na mobilnim uređajima. Google Docs također ima voice input, ali on ne podržava naš jezik. Ako poznajete neki speech-to-text konverter koji ima besplatan API ili je open-source pa se može instalirati na vlastito računalo, a da radi s hrvatskim jezikom - molim javite. Ne radim ništa ozbiljno, samo eksperimentiram.

Ostaje mi da nekom drugom prilikom isprobam text-to-speech, ali o tom po tom.

Za ilustraciju Whisper-a, slijedi dio 'prepoznate' konverzacije (isti timestamp kojim počinje i [1]):

I ovi zepla koji sad stavili na tržište svoje VR? Ima nekoliko proizvođaćali bez niz ozemaca, japanaca, amerikanaca sa tenoradi. Znači Kina pokušava naprajeti svoj uređaj tog tipa, ali to je tehnologija, ono kaj zkažo, ovo je absolutno vrh tehnologije, su uređaj za litografiju mikroprocesore. Dobro, silicija? Silicija vrti šte čestice su daraši i gleda šta se desi. Mi om problem sa atomskom fizikom, da mi nemožemo doznaš šta je nutra. I onda zamisli se... U nutra gdje? U atomu. Zamislimo sad, mislimo netko koje gleda auti se voze po cesti, je zanimena skako ti automobili su napravljene, ali ti ne možeš sad doći razmontirati auto. I onda šta se radi ciclotron? Uzme dan auto s jedne strane, uzme auto s jedne strane, onda ih sudari velikom brzinom i gledaš koji komadi lete. Zagljuče, ha, kotac je dio auto. Onda leti karboraтор, karboraтор je dio auto, ali kako karboraтора, da treba moži jaće zaletiti te auto, da se bolje razbija, onda je zaletiti, i onda umisto karboraтора sad leti vijak, leti membrana, leti je opruga i ti svatiš kako funkcionira auto, tako da bezkonašno puta sudaraš auto i gledaš, da leti je ciclotron, naprosto zalječeš atome jednog drugoga ili čestice i gledaš, da letio. Dobro, taj boson Hicksov još. No i dalje, samost je trebalo zašto je to sve skupa zaljepljeno. Znači ko je ljepilo drži skupa, to sve skupa gravitacija, i onda ideš još ja će razbiti, da vidiš da lete je zavareni komadi, kažem, vidi ovo je zavareno, tu je ljepilo. Došli smo do kraja ovog audiodijela, hvala vam ljepa, poštavan i slušati ili eksplore i do slušanja čujemo se opeti i moćemo interaktivno uživati jedni u drugima sljedeći gutirka. Hvala ljepa i do slušanja. Do slušanja.

[1] https://www.youtube.com/watch?v=9QUQBYyUels&t=2829
[2] https://openai.com/research/whisper

Tuesday, February 6, 2024

Plus hosting alternativa

06:22 AM Pokušavam unijeti TXT record kako bi u Google Search Console-i autentificirao vlastitu domenu. Plus Hosting DNS editor daje poruku: "Greška pri spremanju zapisa!". Šaljem support request. Prilikom klika na "Send" pomislim kako su prošli dani kada su promptno reagirali i kako odgovor sigurno neću dobiti prije 09:00 AM.

10:45 AM Stiže odgovor. Da ja njima pošaljem DNS zapis i da će ga oni podesiti. Nope. Nemam namjeru čekati satima kad god poželim nešto ažurirati. Pitam zar ne mogu sam podesiti vlastitu domenu.

10:54 AM Da oni moraju vidjeti koja se pogreška javlja i da je svakako jednostavnije da oni podese domenu. Šaljem odgovor da sam im već rekao koja se pogreška javlja.

11:04 Stiže odgovor da im moram poslati DNS zapis kojeg želim podesiti, jer da možda postoji već isti pa da se promjene ne mogu spremiti. Inače, DNS editor nema niti jedan meni vidljiv zapis. Šaljem odgovor da otključaju domenu i da je prenosim kod nekog drugog registrara.

Sljedeći dan: odabrao sam spaceship. Domena prebačena u jednoj minuti, u drugoj minuti sam podesio DNS record. eu domene u spaceship-u su €4.64 dok su u Plus-u €9.29.

Plus Hosting, hvala još jednom što odluku o prelasku kod drugog registrara činite tako jednostavnom!

Monday, February 5, 2024

Planet.com partners in agriculture sector

Agrograph

Actionable Data for Managing Ag Risk
  • Standardized Credit Risk Analysis
  • Automated Farmland Valuation
  • Verified Sustainability Reporting
  • Modern Crop Insurance Solutions

https://agrograph.com/
https://www.linkedin.com/company/agrograph/
https://twitter.com/agrograph

Arva intelligence

Profitable sustainability through data-driven agronomics.

https://arvaintelligence.com/
https://www.linkedin.com/company/arva-intelligence/
https://twitter.com/ArvaIntel
https://www.facebook.com/arvaintelligence
https://www.instagram.com/arvaintelligence

Earth PBC

We’re a public benefit corporation that’s built a social network for the protection of forests, oceans, biodiversity and human rights.

https://earthpbc.com/
https://www.linkedin.com/company/earthpbc/about/
https://twitter.com/earthpbc
https://www.instagram.com/earthpbc

ERM

Shaping a sustainable future with the world’s leading organizations.

https://www.erm.com/
https://www.linkedin.com/company/erm/

Eye in the sky

https://eits.in/
https://twitter.com/EYEINTHESKYTEC1

farnQA

FarmQA provides nuts-n-bolts digital tools for agronomists and other agribusiness. See all aspects of your operation in a single view including your fields, crop scouting reports, weather data, spray records, satellite imagery and more.

https://www.farmqa.com/
https://www.linkedin.com/company/farmqa/
https://twitter.com/farm_qa
https://www.youtube.com/@farmqa2421
https://www.instagram.com/farmqa

Global green monitoring

Global Green Monitoring uses NASA Satellites to capture detailed photos of greenery, including dense forest cover, and converts those images to uncover deforestation, logging, disease, poor water flow and other disturbance events. This innovation takes urban planning, forestry and agriculture to the next level.

https://www.globalgreenmonitoring.org/

i-Cultivier

Making advanced research and technology accessible to improve food and support agriculture

https://i-cultiver.com/
https://www.linkedin.com/company/i-cultiver/about/
https://twitter.com/i_cultiver
https://www.instagram.com/icultiver

Interpine Innovation

Interpine Innovation is a forest consulting and data management company specializing in information technology and optimal decision making across the forest industry. We have a committed management team of professional foresters and backed up by qualified technicians and data analysts that provide credible solutions for forestry-based companies. By basing our core business in the central hub of New Zealand forestry since 1983, Interpine has been able to develop contemporary and applicable ideas, systems and services that have greatly benefited the local, national and international forest industries.

https://interpine.nz/
https://www.linkedin.com/company/interpine-innovation/
https://twitter.com/interpine_nz
https://www.youtube.com/@InterpineInnovation

Nave Analytics, Inc.

Nave Analytics helps agriculture professionals recommend the correct amount of water at the correct time to their clients by fusing satellite, weather, and telemetry data into a singular data stream. Nave Analytics generates 3-D maps of soil water content across the entire field instead of a single point and offers an API integration with the customer's existing user interface.

https://www.naveanalytics.com/
https://www.linkedin.com/company/nave-analytics-inc/

QUDEX

QUDEX is a Land Management SaaS and Business ESG AI SaaS platform that uses AI to convert environmental data into actionable insights. Our platform is revolutionizing land conservation and the revaluation of degraded lands. By leveraging the pioneering technology of Quantitative Environmental Data Evaluation (QUDE), we provide businesses across industries with a tool to mitigate their environmental impact. QUDEX offers an advanced, yet intuitive solution for companies seeking to seamlessly incorporate sustainable practices into their core operations, contributing to a healthier and more sustainable world.

https://qudex.ai/
https://www.instagram.com/qudexai

Swift Geospatial

Swift Geospatial is a GIS and Remote Sensing company specializing in Agriculture, Mining, Forestry and Sustainability monitoring solutions.

https://swiftgeospatial.solutions/
https://www.linkedin.com/company/swift-geospatial/?viewAsMember=true
https://www.youtube.com/@swiftgeospatial5228

Vulcan AI

We are a Singapore-based AI company focused on building AgTech solutions utilizing Vision and Generative AI with remote sensing technology to help improve yield sustainably. Our AI solutions have been trained and calibrated on millions of ground- validated tree level data points, thus allowing a quick-start for our clients without having to go through extensive data collection and training exercise. We help clients detect yield risks, health and nutrient issues across crops such as forestry, maize, oil palm and cassava.

https://www.vulcan-ai.com/
https://www.linkedin.com/company/vulcan-agritech/



Sunday, February 4, 2024

How to switch between minified and non-minified javascript/css/html in Flask (Python)?

You can separate your development (human readable) and deployment (minified) files into distinct folders, as illustrated below:

source/
├─ static/
├─ static_dev/
├─ templates/
├─ templates_dev/
...

In your Python code, make your Flask app use the root URL as a static path:

app = Flask(__name__, static_url_path="")

You can switch between development and deployment folders based on your environment or global variable. That really depends on your system and your preferences, so the following code assumes that variable check is abstracted by a function:

if is_development_environment():
    app.static_folder   = "static_dev"
    app.template_folder = "templates_dev"

I am often using Google App engine for my own projects, so the code in my case is:

if not os.getenv("GAE_ENV", "").startswith("standard"):
    ...

Finally, you need to instruct your minify scripts to pick source files from development folders and use deployment folders as output destination. Also make sure that only deployment folders are uploaded to your server, and that they are ignored by your source code control system. 

In my case, I need to add the following two lines to .gitignore:

/static/
/templates/
And the following to .gcloudignore:
/static_dev
/templates_dev

Let me know if you are interested in ant script that does copying/minifying.

Tuesday, January 30, 2024

CSS on hover icon pulse animation

It took me some time to settle on the appropriate animation. However, the following day, I decided to discard it. I noticed that it gave my website a slightly childish appearance. To avoid losing the code in the depths of source control version history, I chose to showcase it here instead [video]. So, here it is...

The concept was to make the RSS icon pulse in the direction of the feed signal, specifically, the top right:

h5 > a > svg {
    vertical-align: text-bottom;
    margin-left:    0.5em;
    fill:           var(--accent);
    transition:     all 0.3s linear;
}

h5 > a > svg:hover {
    animation: hover-rss 0.2s alternate ease-in-out;
}

@keyframes hover-rss {
    from {
        transform: scale(1, 1);
        transform-origin: bottom left;
    }
    to {
        transform: scale(1.1, 1.1);
        transform-origin: bottom left;
    }
}

And, to have the light mode icon pulse from the center:

#mode-toggle-button:hover {
    animation: hover 0.2s alternate ease-in-out;
}

@keyframes hover {
    from {
        transform: scale(1, 1);
    }
    to {
        transform: scale(1.1, 1.1);
    }
}

There they are. Perhaps they'll come in handy for some other project.

Friday, January 26, 2024

Google App Engine - migrating from Python 2.7 to Python 3.x

Migration should be done in two phases. First, we'll switch from the webapp2 framework to Flask without upgrading the Python version. We'll check that everything is running smoothly, and then proceed switching to Python 3.x.

Before we start, we'll ensure that our existing code is intact in case we want to revert to it for some reason. So:
  1. create a separate git branch with old code

    If you are using Eclipse, right-click on the project, open the Team submenu, select Switch To, and then choose New Branch. Give the branch a name (e.g. webapp2_p27) and click on Finish. Commit and push your new branch.

    We won't be touching this code, so we can now:

  2. switch back to the main branch.

    Right-click on the project, open the Team submenu, select Switch To, and then choose main.

Migrating to Flask (Python 2.7)

  1. create a separate git branch [flask_p27]

    Use the same procedure as above, but now we are not switching back to main branch.

    In your source directory:

  2. create a static directory and move your index.html there
  3. add a simple main.py [! the link is only for my own convenience] that renders only index.html

    Example:

    from flask import Flask, send_file

    app = Flask(__name__)

    @app.route("/")
    def root():
        return send_file("static/index.html")

    if __name__ == "__main__":
        app.run(host="127.0.0.1", port=8080, debug=True)


  4. add a requirements.txt [!] file containing Flask
  5. create lib directory
  6. pip install -t lib/ Flask
  7. add an appengine_config.py [!] file (if you don't have it already) with the following content:

    from google.appengine.ext import vendor
    vendor.add("lib")


  8.  update app.yaml with the following (replace existing handler):

    - url: /
      script: main.app
      secure: always

  9. run the app to make sure it works

    On Eclipse: right click on source directory, Run As, PyDev: Google App Run.

  10. you can even deploy it (better not as an default version) and make sure that everything works on production as well:

    gcloud app deploy --no-promote app.yaml

It's time to start committing to the branch and to replace handlers one by one.

It might be that the development datastore admin view will start crashing with the following error message:

raise exception('%s must not be empty.' % name)
BadArgumentError: app must not be empty.

The fix is to export this environment variable BEFORE running your dev_appserver.py.

export APPLICATION_ID=dev~None

See the following StackOverflow question. If you are using Eclipse, you can add this variable by editing Run configuration, tab Environment.

Test your app thoroughly, including both API directly plus cross-origin requests. Once tested deploy as default version and test once again from the live website. 

Merge branch to main (and leave it, do not delete it). 

In Eclipse: right click on the project select Team menu item, then Switch To, and then main. Once you switched, right click on the project again, Team, Merge, and then select flask_p27.

We are ready for the next step, and that is ...

Migrating to Python 3.x

  1. create a separate git branch [flask_p3]
  2. switch python to version 3.x (google)

    In Eclipse, right click on the project name, open Properties, PyDev - Interpreter/Gramar, change Interpreter, click on Apply and Close.

  3. edit app.yaml
    • change runtime from python27 to python38
    • remove api_version
    • add app_engine_apis: true
    • remove threadsafe

      The app can not be run through "Google App run" as before, run it from the console in python 3.x virtual environment.

  4. add appengine-python-standard and other necessary libraries to requirements.txt

    (and install them using pip install -r requirements.txt in python virtual environment).

  5. delete the file appengine_config.py and lib directory
  6. add the following to main.py

    app.wsgi_app = wrap_wsgi_app(app.wsgi_app)

  7. run python .\main.py
  8. Resolve all python errors.
  9. replace (if used) os.environ["SERVER_SOFTWARE"] with another method of detecting development environment.
  10. Test service by service.
  11. Deploy and enjoy!
  12. Yes! Once everything runs smooth, don't forget to merge that branch with the main one.

Friday, January 12, 2024

How to get rich writing WordPress Plugins?

How to get rich writing WordPress Plugins, as an indie developer? If you know, let me know. 😉

Yesterday, another yearly subscription of SEO Meta Description AI got sold. 🎉 That makes it the second one, about three months after the very first one was purchased. In any case, that is a reason for celebration! The project is sustainable since it makes more money than it uses in OpenAI credits. 😎 The current conversion rate (downloads to paying customers) is 0.84%.

The total time invested in the project is 97.5 hours. This includes front and back-end development, all marketing activities and everything else around it. So, my rate is... $0.31/hr. Oh, don't worry, I am not one of those guys who acts like they don't know you once they make it. 😇

Switching from Eclipse IDE to Visual Studio Code

After about 20 years of Eclipse IDE I have decided to gradually switch to Visual Studio Code. I am planing to adopt new keybindings as well. As an alternative, I have started playing with Neovim. From StackOverflow developer survey 2023 (I'm surprised by Notepad++, though I use it too):