diff --git a/.gitignore b/.gitignore index dda9ca6..f7119c1 100644 --- a/.gitignore +++ b/.gitignore @@ -1,18 +1,5 @@ -.DS_Store -node_modules /dist -/npm - - -# local env files -.env.local -.env.*.local - -# Log files -npm-debug.log* -yarn-debug.log* -yarn-error.log* -pnpm-debug.log* +*.log # Editor directories and files .idea @@ -22,3 +9,13 @@ pnpm-debug.log* *.njsproj *.sln *.sw? + +*.iml +.gradle +/local.properties +.DS_Store +/build +/captures +.externalNativeBuild +.cxx +local.properties diff --git a/app/.gitignore b/app/.gitignore new file mode 100644 index 0000000..42afabf --- /dev/null +++ b/app/.gitignore @@ -0,0 +1 @@ +/build \ No newline at end of file diff --git a/app/build.gradle b/app/build.gradle new file mode 100644 index 0000000..5c7d067 --- /dev/null +++ b/app/build.gradle @@ -0,0 +1,47 @@ +plugins { + id 'com.android.application' + id 'kotlin-android' + id 'kotlin-android-extensions' +} + +android { + compileSdkVersion 29 + buildToolsVersion "30.0.3" + + defaultConfig { + applicationId "tech.platypush.app" + minSdkVersion 23 + targetSdkVersion 29 + versionCode 1 + versionName "1.0" + + testInstrumentationRunner "androidx.test.runner.AndroidJUnitRunner" + } + + buildTypes { + release { + minifyEnabled false + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.pro' + } + } + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + kotlinOptions { + jvmTarget = '1.8' + } +} + +dependencies { + implementation "org.jetbrains.kotlin:kotlin-stdlib:$kotlin_version" + implementation 'androidx.core:core-ktx:1.3.2' + implementation 'androidx.appcompat:appcompat:1.2.0' + implementation 'com.google.android.material:material:1.3.0' + implementation 'androidx.constraintlayout:constraintlayout:2.0.4' + implementation 'androidx.navigation:navigation-fragment-ktx:2.3.5' + implementation 'androidx.navigation:navigation-ui-ktx:2.3.5' + testImplementation 'junit:junit:4.+' + androidTestImplementation 'androidx.test.ext:junit:1.1.2' + androidTestImplementation 'androidx.test.espresso:espresso-core:3.3.0' +} \ No newline at end of file diff --git a/app/proguard-rules.pro b/app/proguard-rules.pro new file mode 100644 index 0000000..481bb43 --- /dev/null +++ b/app/proguard-rules.pro @@ -0,0 +1,21 @@ +# Add project specific ProGuard rules here. +# You can control the set of applied configuration files using the +# proguardFiles setting in build.gradle. +# +# For more details, see +# http://developer.android.com/guide/developing/tools/proguard.html + +# If your project uses WebView with JS, uncomment the following +# and specify the fully qualified class name to the JavaScript interface +# class: +#-keepclassmembers class fqcn.of.javascript.interface.for.webview { +# public *; +#} + +# Uncomment this to preserve the line number information for +# debugging stack traces. +#-keepattributes SourceFile,LineNumberTable + +# If you keep the line number information, uncomment this to +# hide the original source file name. +#-renamesourcefileattribute SourceFile \ No newline at end of file diff --git a/app/src/main/AndroidManifest.xml b/app/src/main/AndroidManifest.xml new file mode 100644 index 0000000..0ce317c --- /dev/null +++ b/app/src/main/AndroidManifest.xml @@ -0,0 +1,26 @@ + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/assets/css/style.css b/app/src/main/assets/web/css/style.css similarity index 90% rename from assets/css/style.css rename to app/src/main/assets/web/css/style.css index 0ea67c6..7c22ab2 100644 --- a/assets/css/style.css +++ b/app/src/main/assets/web/css/style.css @@ -75,6 +75,7 @@ html, body { position: fixed; display: flex; justify-content: center; + align-items: center; z-index: 2; } @@ -87,13 +88,13 @@ html, body { } .add-modal { - width: 90%; + width: 80%; height: max-content; max-width: 22.5em; background: #f0f0f0; display: flex; flex-direction: column; - margin-top: 3em; + font-size: 1.2em; padding: 1em; border-radius: 1em; z-index: 3; @@ -111,11 +112,18 @@ html, body { width: 100%; } +.add-modal form input { + border-radius: .25em; + padding: .25em; +} + .add-modal form input[type=text], .add-modal form input[type=number] { width: 95%; + font-size: 1.05em; + margin-bottom: .5em; } .add-modal form input[type=submit] { - margin-top: .5em; + font-size: 1.05em; } diff --git a/assets/icon/icon.png b/app/src/main/assets/web/icon/icon.png similarity index 100% rename from assets/icon/icon.png rename to app/src/main/assets/web/icon/icon.png diff --git a/assets/icon/plus.svg b/app/src/main/assets/web/icon/plus.svg similarity index 100% rename from assets/icon/plus.svg rename to app/src/main/assets/web/icon/plus.svg diff --git a/app/src/main/assets/web/index.html b/app/src/main/assets/web/index.html new file mode 100644 index 0000000..8ba1231 --- /dev/null +++ b/app/src/main/assets/web/index.html @@ -0,0 +1,94 @@ + + + + Platypush + + + + + + +
+
+
+
Loading...
+
No Platypush web services found on the network
+
+ +
+ + on + : +
+
+ +
+
+
+
+ Connect to a Platypush web service +
+ +
+
+ + + + + +
+
+
+
+ +
+
+ + + + diff --git a/assets/js/vue.min.js b/app/src/main/assets/web/js/vue.min.js similarity index 100% rename from assets/js/vue.min.js rename to app/src/main/assets/web/js/vue.min.js diff --git a/app/src/main/ic_launcher-playstore.png b/app/src/main/ic_launcher-playstore.png new file mode 100644 index 0000000..c255832 Binary files /dev/null and b/app/src/main/ic_launcher-playstore.png differ diff --git a/app/src/main/java/tech/platypush/app/MainActivity.kt b/app/src/main/java/tech/platypush/app/MainActivity.kt new file mode 100644 index 0000000..0f4c084 --- /dev/null +++ b/app/src/main/java/tech/platypush/app/MainActivity.kt @@ -0,0 +1,28 @@ +package tech.platypush.app + +import android.annotation.SuppressLint +import android.os.Bundle +import androidx.appcompat.app.AppCompatActivity +import kotlinx.android.synthetic.main.activity_main.* + + +class MainActivity : AppCompatActivity() { + @SuppressLint("SetJavaScriptEnabled") + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + setContentView(R.layout.activity_main) + webview.webViewClient = WebView() + webview.settings.javaScriptEnabled = true + webview.settings.javaScriptCanOpenWindowsAutomatically = true + webview.clearCache(true) + webview.addJavascriptInterface(WebAppInterface(this), "app") + webview.loadUrl("file:///android_asset/web/index.html") + } + + override fun onBackPressed() { + if (webview != null && webview.canGoBack()) + webview.goBack() + else + super.onBackPressed() + } +} diff --git a/app/src/main/java/tech/platypush/app/Services.kt b/app/src/main/java/tech/platypush/app/Services.kt new file mode 100644 index 0000000..8232201 --- /dev/null +++ b/app/src/main/java/tech/platypush/app/Services.kt @@ -0,0 +1,125 @@ +package tech.platypush.app + +import android.content.ContentValues.TAG +import android.content.Context +import android.net.nsd.NsdManager +import android.net.nsd.NsdServiceInfo +import android.util.Log +import java.util.* +import java.util.concurrent.locks.ReentrantLock + + +/** + * Service data class + */ +data class Service(val host: String, val port: Int, val name: String?) { + fun toMap(): Map { + return mapOf( + "host" to host, + "port" to port, + "name" to name + ) + } +} + + +/** + * ZeroConf/Bonjour/mDNS event listener + */ +class Listener(private val scanner: Scanner): NsdManager.DiscoveryListener { + val serviceType = "_platypush-http._tcp." + + override fun onDiscoveryStarted(regType: String) { + Log.d(TAG, "Service discovery started") + } + + override fun onServiceFound(service: NsdServiceInfo) { + Log.d(TAG, "Service discovery succeeded: $service") + if (service.serviceType != serviceType) { + Log.d(TAG, "Unknown service type: ${service.serviceType}") + return + } + + scanner.resolveService(service) + } + + override fun onServiceLost(service: NsdServiceInfo) { + Log.w(TAG, "Service lost: $service") + } + + override fun onDiscoveryStopped(serviceType: String) { + Log.i(TAG, "Discovery stopped: $serviceType") + } + + override fun onStartDiscoveryFailed(serviceType: String, errorCode: Int) { + Log.e(TAG, "Discovery start failed: Error code: $errorCode") + scanner.stopScan() + } + + override fun onStopDiscoveryFailed(serviceType: String, errorCode: Int) { + Log.e(TAG, "Discovery stop failed: Error code: $errorCode") + scanner.stopScan() + } +} + + +/** + * ZeroConf/Bonjour/mDNS service scanner and resolver + */ +class Scanner(context: Context) { + private val nsdManager = context.getSystemService(Context.NSD_SERVICE) as NsdManager + private val listener = Listener(this) + private val services = HashMap, Service>() + private val resolverLock = ReentrantLock() + + fun startScan() { + nsdManager.discoverServices(listener.serviceType, NsdManager.PROTOCOL_DNS_SD, listener) + } + + fun stopScan() { + nsdManager.stopServiceDiscovery(listener) + } + + fun getServices(): Collection { + return services.values + } + + fun resolveService(service: NsdServiceInfo) { + // Service resolution is a critical section + resolverLock.lock() + val scanner = this + + nsdManager.resolveService(service, object : NsdManager.ResolveListener { + override fun onResolveFailed(serviceInfo: NsdServiceInfo, errorCode: Int) { + resolverLock.unlock() + val msg = "Resolve of service ${serviceInfo.serviceName} failed" + + // Retry logic + when (errorCode) { + NsdManager.FAILURE_ALREADY_ACTIVE -> { + Thread.sleep(100) + scanner.resolveService(serviceInfo) + Log.w(TAG, "$msg: Resolver already active") + } + + NsdManager.FAILURE_MAX_LIMIT -> { + Thread.sleep(5000) + scanner.resolveService(serviceInfo) + Log.e(TAG, "$msg: Maximum number of resolve requests reached") + } + + NsdManager.FAILURE_INTERNAL_ERROR -> { + Log.e(TAG, "$msg: Internal error") + } + } + } + + override fun onServiceResolved(serviceInfo: NsdServiceInfo) { + services[Pair(serviceInfo.host.hostAddress, serviceInfo.port)] = Service( + serviceInfo.host.hostAddress, serviceInfo.port, serviceInfo.serviceName) + resolverLock.unlock() + Log.i(TAG, "Resolve succeeded: $serviceInfo") + } + }) + } +} diff --git a/app/src/main/java/tech/platypush/app/WebAppInterface.kt b/app/src/main/java/tech/platypush/app/WebAppInterface.kt new file mode 100644 index 0000000..0ad6937 --- /dev/null +++ b/app/src/main/java/tech/platypush/app/WebAppInterface.kt @@ -0,0 +1,32 @@ +package tech.platypush.app + +import android.content.Context +import android.webkit.JavascriptInterface +import org.json.JSONArray +import java.util.* + + +class WebAppInterface(context: Context) { + private val serviceScanner = Scanner(context) + + @Suppress("unused") + @JavascriptInterface + fun startServicesPoll() { + serviceScanner.startScan() + } + + @Suppress("unused") + @JavascriptInterface + fun stopServicesPoll() { + serviceScanner.stopScan() + } + + @Suppress("unused") + @JavascriptInterface + fun pollServices(): String { + val services = LinkedList>() + for (srv in serviceScanner.getServices()) + services.add(srv.toMap()) + return JSONArray(services).toString() + } +} diff --git a/app/src/main/java/tech/platypush/app/WebView.kt b/app/src/main/java/tech/platypush/app/WebView.kt new file mode 100644 index 0000000..975ee03 --- /dev/null +++ b/app/src/main/java/tech/platypush/app/WebView.kt @@ -0,0 +1,27 @@ +package tech.platypush.app + +import android.webkit.WebResourceRequest +import android.webkit.WebView +import android.webkit.WebViewClient + +class WebView : WebViewClient() { + private fun shouldOverrideUrlLoadingInner(view: WebView, url: String): Boolean { + view.loadUrl(url) + return true + } + + override fun shouldOverrideUrlLoading(view: WebView?, request: WebResourceRequest?): Boolean { + if (view == null || request == null) + return super.shouldOverrideUrlLoading(view, request) + + return this.shouldOverrideUrlLoadingInner(view, request.url.toString()) + } + + override fun shouldOverrideUrlLoading(view: WebView?, url: String?): Boolean { + @Suppress("DEPRECATION") + if (view == null || url == null) + return super.shouldOverrideUrlLoading(view, url) + + return this.shouldOverrideUrlLoadingInner(view, url) + } +} diff --git a/app/src/main/res/drawable/ic_launcher_foreground.xml b/app/src/main/res/drawable/ic_launcher_foreground.xml new file mode 100644 index 0000000..230b730 --- /dev/null +++ b/app/src/main/res/drawable/ic_launcher_foreground.xml @@ -0,0 +1,26 @@ + + + + + + + + + diff --git a/app/src/main/res/layout/activity_main.xml b/app/src/main/res/layout/activity_main.xml new file mode 100644 index 0000000..edad1d1 --- /dev/null +++ b/app/src/main/res/layout/activity_main.xml @@ -0,0 +1,20 @@ + + + + + + + diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml new file mode 100644 index 0000000..7353dbd --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml new file mode 100644 index 0000000..7353dbd --- /dev/null +++ b/app/src/main/res/mipmap-anydpi-v26/ic_launcher_round.xml @@ -0,0 +1,5 @@ + + + + + \ No newline at end of file diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher.png b/app/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000..260a0e2 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-hdpi/ic_launcher_round.png b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png new file mode 100644 index 0000000..abf3082 Binary files /dev/null and b/app/src/main/res/mipmap-hdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher.png b/app/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000..aba2828 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-mdpi/ic_launcher_round.png b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png new file mode 100644 index 0000000..4ade1e1 Binary files /dev/null and b/app/src/main/res/mipmap-mdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher.png b/app/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000..d784fa4 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png new file mode 100644 index 0000000..d177f06 Binary files /dev/null and b/app/src/main/res/mipmap-xhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000..65d177b Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..41ad949 Binary files /dev/null and b/app/src/main/res/mipmap-xxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000..4e0c669 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png new file mode 100644 index 0000000..6e4cba6 Binary files /dev/null and b/app/src/main/res/mipmap-xxxhdpi/ic_launcher_round.png differ diff --git a/app/src/main/res/navigation/nav_graph.xml b/app/src/main/res/navigation/nav_graph.xml new file mode 100644 index 0000000..7775c01 --- /dev/null +++ b/app/src/main/res/navigation/nav_graph.xml @@ -0,0 +1,4 @@ + + + \ No newline at end of file diff --git a/app/src/main/res/values-night/themes.xml b/app/src/main/res/values-night/themes.xml new file mode 100644 index 0000000..1b1f0ea --- /dev/null +++ b/app/src/main/res/values-night/themes.xml @@ -0,0 +1,16 @@ + + + + \ No newline at end of file diff --git a/app/src/main/res/values/colors.xml b/app/src/main/res/values/colors.xml new file mode 100644 index 0000000..f8c6127 --- /dev/null +++ b/app/src/main/res/values/colors.xml @@ -0,0 +1,10 @@ + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/dimens.xml b/app/src/main/res/values/dimens.xml new file mode 100644 index 0000000..125df87 --- /dev/null +++ b/app/src/main/res/values/dimens.xml @@ -0,0 +1,3 @@ + + 16dp + \ No newline at end of file diff --git a/app/src/main/res/values/ic_launcher_background.xml b/app/src/main/res/values/ic_launcher_background.xml new file mode 100644 index 0000000..c5d5899 --- /dev/null +++ b/app/src/main/res/values/ic_launcher_background.xml @@ -0,0 +1,4 @@ + + + #FFFFFF + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml new file mode 100644 index 0000000..489382d --- /dev/null +++ b/app/src/main/res/values/strings.xml @@ -0,0 +1,6 @@ + + Platypush + Settings + Next + Previous + \ No newline at end of file diff --git a/app/src/main/res/values/themes.xml b/app/src/main/res/values/themes.xml new file mode 100644 index 0000000..32ab82f --- /dev/null +++ b/app/src/main/res/values/themes.xml @@ -0,0 +1,25 @@ + + + + + + +