Easily notify a user with a simple alert, inform them of an optional update, and in dire situations block the user from accessing older versions of the application completely.

Overview

Gandalf Build Status Download Android Arsenal

In the lifetime of any application there will come a time where you need to drop support for a feature, end of life a product, notify about maintenance, any number of other reasons, Gandalf is here to help!

Gandalf will easily add a check to a remote file that can notify a user with a simple alert, inform them of an optional update, and in dire situations block the user from accessing older versions of the application completely (ex: security vulnerability has been found).

Need an iOS version?

You're in luck! Gandalf was built in parallel with its iOS counterpart, LaunchGate.

Download

Gandalf is hosted on the jCenter repository and can be downloaded via Gradle:

compile 'com.btkelly:gandalf:{latest_version}'

Usage

The goal of Gandalf was to add this basic boiler plate code to any application quickly. You will need to add the following code to your application as well as host a JSON file on a publicly accessible server.

Application Class

Extend the Android Application class and add the following to the onCreate()

public class CustomApplication extends Application {

    @Override
    public void onCreate() {
        super.onCreate();
        new Gandalf.Installer()
                .setContext(this)
                .setPackageName("com.my.package")
                .setBootstrapUrl("http://www.example.com/bootstrap.json")
                .install();
    }
}

Splash Activity

Extend GandalfActivity for use as your main "Splash" type activity, this is where the magic will happen. Just provide a layout resource id to display while the bootstrap file is being checked and implement the youShallPass() method with what should happen after a successful check.

public class SplashActivity extends GandalfActivity {

    @Override
    public void youShallPass() {
        //After a successful bootstrap check we change the content view, you may also load a new activity or do whatever logic you want after the check is complete.
        setContentView(R.layout.activity_splash_finished_loading);
    }

    @Override
    public int contentView() {
        //While the bootstrap check is running we provide a layout to be displayed
        return R.layout.activity_splash_loading;
    }
}

Manifest Changes

Add the android:name attribute to the application tag and specify the path to your custom Application class from above and set your SplashActivity as the entry point for your app.

<manifest xmlns:android="http://schemas.android.com/apk/res/android"
    package="io.github.btkelly.gandalf.example">

    <application
        android:name=".CustomApplication"
        android:icon="@mipmap/ic_launcher"
        android:label="@string/app_name"
        android:theme="@style/AppTheme">

        <activity
            android:name=".SplashActivity">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>

    </application>

</manifest>

JSON File

You must host a JSON file remotely and set the URL of this file in the the Gandalf installer. The JSON file use the Android versionCode not the versionName for version information. By default the format must match the file included below, if you would like to use custom JSON you can provide a custom deserializer.

{
  "android": {
    "alert": {
      "message": "We are currently performing server maintenance. Please try again later.",
      "blocking": true
    },
    "optionalUpdate": {
      "optionalVersion": "6",
      "message": "A new version of the application is available, please click below to update to the latest version."
    },
    "requiredUpdate": {
      "minimumVersion": "7",
      "message": "A new version of the application is available and is required to continue, please click below to update to the latest version."
    }
  }
}

That's all that's needed to get Gandalf up and running using the basic settings.

If extending GandalfActivity doesn't work for you the Gandalf class can be used directly by calling shallIPass(GandalfCallback callback). In this case make sure you respond to the callback methods and make a call to gandalf.save(Alert alert) and gandalf.save(OptionalUpdate optionalUpdate) if not using the BootstrapDialogUtil for your UI.

Custom titles, buttons and messages

By default, Gandalf provides default title and button text, and gets the message to display to the user from the JSON file.

However, you are able to use your own strings. To do so, you should use the DialogStringHolder class when installing Gandalf.

  1. If you do not provide a DialogStringHolder during installation, a default instance will be used.
  2. If you do not provide message strings in the DialogStringHolder, the message from the JSON file will be used.
  3. If you provide DialogStringHolder but do not set some field manually, default values will be used for all unset strings.
  4. You could either pass a String instance or a string resource id.

Remember: you are not forced to set every string : default values will be used for unset string.

DialogStringsHolder dialogStringsHolder = new DialogStringsHolder(this);

// Defines custom dialog titles
dialogStringsHolder.setAlertTitle(R.string.alert_title);
dialogStringsHolder.setUpdateAvailableTitle(R.string.update_available_title);
dialogStringsHolder.setUpdateRequiredTitle(R.string.update_required_title);

// Defines custom button text
dialogStringsHolder.setCloseAppButtonText(R.string.close_app_button);
dialogStringsHolder.setDownloadUpdateButtonText(R.string.download_update_button);
dialogStringsHolder.setOkButtonText(R.string.ok_button);
dialogStringsHolder.setSkipUpdateButtonText(R.string.skip_update_button);

// Defines custom messages
dialogStringsHolder.setUpdateAvailableMessage(R.string.optional_update_message);
dialogStringsHolder.setUpdateRequiredMessage(R.string.required_update_message);
dialogStringsHolder.setAlertMessage(R.string.required_update_message);

new Gandalf.Installer()
        .setContext(this)
        .setPackageName("com.my.package")
        .setBootstrapUrl("http://www.example.com/bootstrap.json")
        .setDialogStringsHolder(dialogStringsHolder) // Set the custom DialogStringsHolder
        .install();

Custom OnUpdateSelectedListener

You may provide a custom listener to be invoked when the user selects to update their app. This can be helpful if you are not hosting your application on Google Play and would like to download an APK from another source. Two default listeners are already provided, the PlayStoreUpdateListener which opens Google Play to the specified package name and the FileDownloadUpdateListener which will download a file specified by the Uri provided.

new Gandalf.Installer()
        .setContext(this)
        .setOnUpdateSelectedListener(new OnUpdateSelectedListener() {
            @Override
            public void onUpdateSelected(@NonNull Activity activity) {
                //Perform some action when the user would like to update
            }
        })
        .setBootstrapUrl("http://www.example.com/bootstrap.json")
        .install();

Custom JSON Deserializer

You may have a different JSON format for the bootstrap file, no problem! To do this you must provide a JsonDeserializer<Bootstrap> during the Gandalf installation.

new Gandalf.Installer()
        .setContext(this)
        .setPackageName("com.my.package")
        .setBootstrapUrl("http://www.example.com/bootstrap.json")
        .setCustomDeserializer(new JsonDeserializer<Bootstrap>() {
             @Override
             public Bootstrap deserialize(JsonElement json, Type typeOfT, JsonDeserializationContext context) throws JsonParseException {

                //Inspect the JsonElement object to retrieve the pieces of the Bootstrap file and return using the builder like below
                 return new Bootstrap.Builder()
                         .setAlertBlocking(false)
                         .setAlertMessage("Down for maintenance.")
                         .setOptionalVersion("8")
                         .setOptionalMessage("There is a newer version of the app, please update below.")
                         .setMinimumVersion("6")
                         .setRequiredMessage("You must update to the latest version of the app.")
                         .build();
             }
         })
        .install();

Example App

Included in the source is a simple example application showing four different launch states based on a remote file. You can load and relaunch the app with each scenario by selecting an option in the Android menu.

Screenshots

License

Copyright 2020 Bryan Kelly

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 http://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.

Comments
  • Proposed update/message JSON structure

    Proposed update/message JSON structure

    {
        "android": {
            "alert": {
                "message": "We are currently performing server maintenance. Please try again later.",
                "blocking": true
            },
            "optionalUpdate": {
                "version": "3.9.0",
                "message": "A new version of the application is available, please click below to update to the latest version."
            },
            "requiredUpdate": {
                "version": "3.8.0",
                "message": "A new version of the application is available and is required to continue, please click below to update to the latest version."
            }
        }
    }
    
    discussion 
    opened by dtrenz 23
  • Check for alerts before update

    Check for alerts before update

    This PR fixes #65. The optional update check was moved to be after the alert check. This satisfies the business logic of any blocking condition checks should take precedences over non-blocking checks.

    opened by kyhule 10
  • Auto install apk when using FileDownloadUpdateListener

    Auto install apk when using FileDownloadUpdateListener

    Have you consider initiating the apk install after downloading an apk using the FileDownloadUpdateListener? I have seen a few apps do this in the past so I know it can be done. If you wouldn't want to do this in the library, I can understand that, but maybe we could look into giving a callback instead of finishing the activity in onUpdateSelected. Let me know your thoughts.

    opened by kyhule 9
  • Add setters for entities messages to allow custom messages

    Add setters for entities messages to allow custom messages

    I needed to set custom messages instead of displaying the one received from JSON. So I added some setters in the models to be able to customize the message, if needed.

    However, the message remains by default the one received from JSON.

    opened by albinpoignot 9
  • Application label causing manifest merger issues

    Application label causing manifest merger issues

    Gradle is failing when trying to merge manifest files. I try to use the replace attribute as suggested but it does not work. Have you come across this at all? What is the purpose of the label being defined in your library's manifest? Comparing to other popular third-party android libraries, the label attribute is not usually defined in the library manifest.

    Attribute application@label value=(ClinicalKey DEBUG) from AndroidManifest.xml:20:9-41 is also present at [com.btkelly:gandalf:1.3.0] AndroidManifest.xml:15:9-41 value=(@string/app_name). Suggestion: add 'tools:replace="android:label"' to element at AndroidManifest.xml:15:5-19:11 to override.

    bug 
    opened by kyhule 8
  • Add version checking logic

    Add version checking logic

    The library should be able to look at various forms of version strings or version codes and deduce which version is higher / lower. This will involve major and minor numbers, ex "1.2.1 vs 1.3.0" and "1.5 vs 2.1" as well as a version code that may be only an integer. The consumer should be able to configure what the library is evaluating, the version name or the version code.

    code difficulty-medium enhancement 
    opened by btkelly 7
  • The library is outdated in jcenter repository

    The library is outdated in jcenter repository

    Hey, I wasn't able to download the latest version of the library, the sources seem to be outdated. Here's my setup: compile 'com.btkelly:gandalf:1.2.1'

    question 
    opened by vase4kin 6
  • Custom deserializer

    Custom deserializer

    Added the ability to provide a custom deserializer to accommodate any JSON structure a consumer might have. I have also updated the README to demonstrate how to do this. Resolves #4

    opened by btkelly 6
  • Dialog util

    Dialog util

    • Added dialog util that can be used to easily show dialogs related to the bootstrap check.
    • Added a simple logging class that still needs the LogLevel connected and set from the Gandalf installer.
    • Added the ability to set the package name from the Gandalf installer
    opened by btkelly 6
  • Network call to pull down JSON for version information

    Network call to pull down JSON for version information

    Just the network code that will pull down the JSON from the endpoint specified. This should include the ability for the consumer to configure the endpoint from which the JSON is pulled from.

    code difficulty-easy enhancement 
    opened by btkelly 6
  • Blocking alert not shown when user skips optional update

    Blocking alert not shown when user skips optional update

    If there is a blocking alert and an optional version set, the user is presented with the optional update but can skip and continue using the app without ever seeing the alert.

    opened by kyhule 5
  • Consider adding an dependency other than jCenter?

    Consider adding an dependency other than jCenter?

    jCenter has been shut down since 2021/05/01: https://jfrog.com/blog/into-the-sunset-bintray-jcenter-gocenter-and-chartcenter/ And Gradle suggests to stop using it (and its dependency): https://blog.gradle.org/jcenter-shutdown

    Would you consider publish the library to other platform, like JitPack for example?

    opened by samuelchou 0
  • Installer: add setter for HistoryChecker and VersionChecker.

    Installer: add setter for HistoryChecker and VersionChecker.

    Gandalf is great, I really liked it! It provides the most features that my team-project needs. However, I'd like to add a custom HistoryChecker setter (and VersionChecker btw) to make it more customizable.

    Issue

    In my case, our team hopes that the optional-update-window show up every time when user launches the app. However, the DefaultHistoryChecker has made the rule: when launched Gandalf, it will check the optional-update version last time saved in local preference, and if it is identical to the version this time, it will make GateKeeper.updateIsOptional returns false, causing Gandalf to not show up the window.

    Proposal of Solution

    After a little dig into the code, I think it is appropriate to add setter function in Gandalf.Installer, as most of other members has been allowed to re-set here, and HistoryChecker is designed as an interface. By adding this, I can implement my custom HistoryChecker, and hence solve the issue above.

    I'm willing to discuss. If you have any thought or suggestion about this, please let me know :)

    opened by samuelchou 2
  • StrictMode policy violation found when doing Gson mapping from onResponse() call

    StrictMode policy violation found when doing Gson mapping from onResponse() call

    I have added the following strict mode policy checks in my application onCreate() method.

    private fun setStrictMode() {
            StrictMode.setThreadPolicy(
                StrictMode.ThreadPolicy.Builder()
                    .detectDiskReads()
                    .detectDiskWrites()
                    .detectAll()   // or .detectAll() for all detectable problems
                    .penaltyLog()
                    .build()
            )
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            StrictMode.setVmPolicy(
                 StrictMode.VmPolicy.Builder()
                     .detectNonSdkApiUsage()
                     .detectLeakedSqlLiteObjects()
                     .detectLeakedClosableObjects()
                     .penaltyLog()
                     .build()
             )
        }
    }
    

    and found NonSdkApiUsedViolation when trying to map the results from the BootstrapApi.onResponse() callback.

    2019-11-01 16:18:42.856 19210-19276/com.sample.test.ui D/StrictMode: StrictMode policy violation: android.os.strictmode.NonSdkApiUsedViolation: Lsun/misc/Unsafe;->theUnsafe:Lsun/misc/Unsafe;
            at android.os.StrictMode.lambda$static$1(StrictMode.java:428)
            at android.os.-$$Lambda$StrictMode$lu9ekkHJ2HMz0jd3F8K8MnhenxQ.accept(Unknown Source:2)
            at java.lang.Class.getDeclaredField(Native Method)
            at com.google.gson.internal.UnsafeAllocator.create(UnsafeAllocator.java:41)
            at com.google.gson.internal.ConstructorConstructor$14.<init>(ConstructorConstructor.java:221)
            at com.google.gson.internal.ConstructorConstructor.newUnsafeAllocator(ConstructorConstructor.java:220)
            at com.google.gson.internal.ConstructorConstructor.get(ConstructorConstructor.java:96)
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:101)
            at com.google.gson.Gson.getAdapter(Gson.java:458)
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:117)
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:166)
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
            at com.google.gson.Gson.getAdapter(Gson.java:458)
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.createBoundField(ReflectiveTypeAdapterFactory.java:117)
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.getBoundFields(ReflectiveTypeAdapterFactory.java:166)
            at com.google.gson.internal.bind.ReflectiveTypeAdapterFactory.create(ReflectiveTypeAdapterFactory.java:102)
            at com.google.gson.Gson.getAdapter(Gson.java:458)
            at com.google.gson.Gson.fromJson(Gson.java:926)
            at com.google.gson.Gson.fromJson(Gson.java:892)
            at com.google.gson.Gson.fromJson(Gson.java:841)
            at com.google.gson.Gson.fromJson(Gson.java:813)
            at io.github.btkelly.gandalf.network.BootstrapApi$1.onResponse(BootstrapApi.java:118)
            at okhttp3.RealCall$AsyncCall.run(RealCall.kt:138)
            at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1167)
            at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:641)
            at java.lang.Thread.run(Thread.java:764)
    
    opened by jshaikql 3
  • Found DiskReadViolation when strict mode enabled

    Found DiskReadViolation when strict mode enabled

    In my sample app's onCreate() method i am trying to get gandalf instance as follows:

    this.gandalf = Gandalf.get()
    this.gandalf.shallIPass(this) 
    

    then i found the following disk read violation and it's happening more frequently whenever i am coming back to app either from background or from back-stack.

    Please find the stack trace when we enable strict mode policy in our app as follows:

    2019-11-01 14:44:54.701 17750-17750/com.sample.test.ui D/StrictMode: StrictMode policy violation; ~duration=119 ms: android.os.strictmode.DiskReadViolation
            at android.os.StrictMode$AndroidBlockGuardPolicy.onReadFromDisk(StrictMode.java:1504)
            at java.io.UnixFileSystem.checkAccess(UnixFileSystem.java:251)
            at java.io.File.exists(File.java:815)
            at android.app.ContextImpl.ensurePrivateDirExists(ContextImpl.java:605)
            at android.app.ContextImpl.ensurePrivateCacheDirExists(ContextImpl.java:601)
            at android.app.ContextImpl.getCacheDir(ContextImpl.java:694)
            at android.content.ContextWrapper.getCacheDir(ContextWrapper.java:269)
            at io.github.btkelly.gandalf.network.BootstrapApi.<init>(BootstrapApi.java:70)
            at io.github.btkelly.gandalf.Gandalf.shallIPass(Gandalf.java:140)
            at com.sample.test.ui.SplashActivity.onCreate(SplashActivity.kt:54)
            at android.app.Activity.performCreate(Activity.java:7136)
            at android.app.Activity.performCreate(Activity.java:7127)
            at android.app.Instrumentation.callActivityOnCreate(Instrumentation.java:1271)
            at android.app.ActivityThread.performLaunchActivity(ActivityThread.java:2893)
            at android.app.ActivityThread.handleLaunchActivity(ActivityThread.java:3048)
            at android.app.servertransaction.LaunchActivityItem.execute(LaunchActivityItem.java:78)
            at android.app.servertransaction.TransactionExecutor.executeCallbacks(TransactionExecutor.java:108)
            at android.app.servertransaction.TransactionExecutor.execute(TransactionExecutor.java:68)
            at android.app.ActivityThread$H.handleMessage(ActivityThread.java:1808)
            at android.os.Handler.dispatchMessage(Handler.java:106)
            at android.os.Looper.loop(Looper.java:193)
            at android.app.ActivityThread.main(ActivityThread.java:6669)
            at java.lang.reflect.Method.invoke(Native Method)
            at com.android.internal.os.RuntimeInit$MethodAndArgsCaller.run(RuntimeInit.java:493)
            at com.android.internal.os.ZygoteInit.main(ZygoteInit.java:858)
    
    opened by jshaikql 1
  • Add check when application is foregrounded.

    Add check when application is foregrounded.

    This would help further lock down the app when needed. The use case is a user that keeps the application backgrounded on the device for a long period of time.

    opened by jguest8226 0
  • Gradle Error: Cannot create variant 'android-lint' after configuration ':gandalf:debugRuntimeElements' has been resolved

    Gradle Error: Cannot create variant 'android-lint' after configuration ':gandalf:debugRuntimeElements' has been resolved

    Hello i have android studio 3.1.3 and dependencies classpath 'com.android.tools.build:gradle:3.1.3' and am trying to run the code but im getting this error: Cannot create variant 'android-lint' after configuration ':gandalf:debugRuntimeElements' has been resolved.

    I've checked with old gradle version (4.1) and it worked. Seems, the current gradle 4.4 is not supported by your build scripts

    any idea, how to solve it?

    gandalf error

    opened by Arun1811 2
Releases(v1.4.0)
  • v1.4.0(Sep 9, 2019)

  • v1.3.5(Mar 6, 2017)

  • V1.3.4(Jan 19, 2017)

  • v1.3.3(Oct 24, 2016)

  • v1.3.2(Aug 24, 2016)

    • Changed the priority of Gandalf actions, an alert will now show before an optional update
    • Added consumer proguard rules to fix Gson parsing when minify is enabled
    Source code(tar.gz)
    Source code(zip)
  • v1.3.1(Aug 24, 2016)

  • v1.3.0(Aug 4, 2016)

    • Added ability to set a custom listener when the user opts to update.
    • Included a basic FileDownloadUpdateListener for specifying a custom apk to download.
    Source code(tar.gz)
    Source code(zip)
  • v1.2.1(Mar 29, 2016)

  • v1.2.0(Mar 29, 2016)

    Library is a bit more error tolerant and will silently fail on malformed JSON. New class DialogStringsHolder which allows users to customize titles, buttons, and messages. This adds the ability to optionally override the JSON response and display a specific message.

    Source code(tar.gz)
    Source code(zip)
Framework for dispatching various procedure on update application.

Fit Framework for dispatching various procedure on update application. Photo License CC by NC-ND Attention This library is under development so API ma

Keishin Yokomaku 59 Jun 21, 2022
A Mindustry mod adding turrets from older versions of Mindustry, specifically Mindustry Classic

Mindustry Kotlin Mod Template A Kotlin Mindustry mod that works on Android and PC. This is equivalent to the Java version, except in Kotlin. Building

null 4 Sep 3, 2022
Alert Dialog - You can use this extension instead of creating a separate Alert Dialog for each Activity or Fragment.

We show a warning message (Alert Dialog) to the user in many parts of our applications. You can use this extension instead of creating a separate Alert Dialog for each Activity or Fragment. Thanks to this extension, you can create a Dialog and call it in the Activity or Fragment you want and customize the component you want.

Gökmen Bayram 0 Jan 9, 2022
Notify users when a new version of your Android app is available, and prompt them with the Play Store link. A port of the iOS library of the same name.

Siren for Android Notify users when a new version of your Android app is available, and prompt them with the Play Store link. This is a port of the iO

Quality Mobile Puzzle Apps 133 Nov 22, 2022
This tool patches the CVE-2021-44228 Log4J vulnerability present in all minecraft versions NOTE THIS TOOL MUST BE RE-RUN after downloading or updating versions of minecraft as its not a perminent patch

WARNING THIS EXPLOIT EFFECTS BOTH CLIENTS AND SERVERS There is currently a exploit going around that affects all versions of Minecraft this exploit ab

Jacobtread 6 Aug 23, 2022
Okuki is a simple, hierarchical navigation bus and back stack for Android, with optional Rx bindings, and Toothpick DI integration.

Okuki A simple, hierarchical navigation bus and back stack for Android, with optional Rx bindings, and Toothpick integration for automatic dependency-

Cain Wong 143 Nov 25, 2022
Validator - Notify type based validation for input fields.

Validator - Notify type based validation for input fields.

Mustafa Yiğit 57 Dec 8, 2022
🚀📒📍 Indicators for Horizontal or Vertical Pager with different orientation, color, size options and optional touch feature.

Compose Pager Indicator Indicators for Horizontal or Vertical pager with different orientation, color, size options and optional touch feature. indica

Smart Tool Factory 5 Oct 7, 2022
Lightweight android library for highlighting sections of a textview, with optional callbacks.

Linker Lightweight android library for highlighting Strings inside of a textview (ignoring case), with optional callbacks. Language: Java MinSDK: 17 J

Josh Gainey 35 Apr 30, 2022
My Maps displays a list of maps, each of which show user-defined markers with a title, description, and location. The user can also create a new map. The user can save maps and load them in from previous usages of the app.

My Maps Bryant Jimenez My Maps displays a list of maps, each of which show user-defined markers with a title, description, and location. The user can

null 0 Nov 1, 2021
An application that allows the user to update variety of smartphones that are used such as iPhone and Android

PhoneApplication An application that allows the user to update variety of smartphones such as iPhone and Android. This application allows users to add

Pankaj Singh 1 Nov 28, 2021
Easy-Note - Easy Note Application will help user to add and update their important notes

Easy-Note ??️ Easy Note App helps you to create your notes. You can ?? edit and

Ade Amit 0 Jan 30, 2022
NetGuard provides simple and advanced ways to block access to the internet

NetGuard NetGuard provides simple and advanced ways to block access to the internet - no root required. Applications and addresses can individually be

Marcel Bokhorst 598 Dec 31, 2022
WaxedNotWaxed - Adds a simple indicator to know if a copper block is waxed or not

Waxed Not Waxed Adds a simple indicator to know if a copper block is waxed or no

Mateusz 2 Nov 11, 2022
This Kotlin Multiplatform library is for accessing the TMDB API to get movie and TV show content. Using for Android, iOS, and JS projects.

Website | Forum | Documentation | TMDb 3 API Get movie and TV show content from TMDb in a fast and simple way. TMDb API This library gives access to T

Moviebase 37 Dec 29, 2022
SweetAlert for Android, a beautiful and clever alert dialog

Sweet Alert Dialog SweetAlert for Android, a beautiful and clever alert dialog 中文版 Inspired by JavaScript SweetAlert Demo Download ScreenShot Setup Th

书呆子 7.3k Dec 30, 2022
AlertDialog for Android, a beautiful and material alert dialog to use in your android app.

AlertDialog for Android, a beautiful and material alert dialog to use in your android app. Older verion of this library has been removed

Akshay Masram 124 Dec 28, 2022
An Android app that scans images or human faces in real time and detects whether the mask is worn or not, with the ability to set an audible alert

Swift Mask Real time face mask detection Brief overview Swift Mask scans images or human faces in real time and detects whether the mask is worn or no

Giorgio Cantoni 4 Sep 22, 2022
ionalert 1.3 1.6 Java Sweetalert, Dialog, Alert Dialog

ionalert - Android Alert Dialog A beautiful design Android Alert Dialog, alternative of Sweet Alert Dialog based on KAlertDialog using MaterialCompone

Excel Dwi Oktavianto 23 Sep 17, 2022