Add Capacitor Android project and native Tiptoi plugin
Checked-in android/ Capacitor project plus the native TiptoiPlugin (Kotlin): SAF directory picker for the pen, listing/copying/deleting .gme files on the pen, and downloading GME/cover files into app-private storage.
@@ -0,0 +1,26 @@
|
||||
package com.getcapacitor.myapp;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import android.content.Context;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import androidx.test.platform.app.InstrumentationRegistry;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
|
||||
/**
|
||||
* Instrumented test, which will execute on an Android device.
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class ExampleInstrumentedTest {
|
||||
|
||||
@Test
|
||||
public void useAppContext() throws Exception {
|
||||
// Context of the app under test.
|
||||
Context appContext = InstrumentationRegistry.getInstrumentation().getTargetContext();
|
||||
|
||||
assertEquals("com.getcapacitor.app", appContext.getPackageName());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,44 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
|
||||
<application
|
||||
android:allowBackup="true"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:roundIcon="@mipmap/ic_launcher_round"
|
||||
android:supportsRtl="true"
|
||||
android:usesCleartextTraffic="true"
|
||||
android:theme="@style/AppTheme">
|
||||
|
||||
<activity
|
||||
android:configChanges="orientation|keyboardHidden|keyboard|screenSize|locale|smallestScreenSize|screenLayout|uiMode|navigation"
|
||||
android:name=".MainActivity"
|
||||
android:label="@string/title_activity_main"
|
||||
android:theme="@style/AppTheme.NoActionBarLaunch"
|
||||
android:launchMode="singleTask"
|
||||
android:exported="true">
|
||||
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
|
||||
</activity>
|
||||
|
||||
<provider
|
||||
android:name="androidx.core.content.FileProvider"
|
||||
android:authorities="${applicationId}.fileprovider"
|
||||
android:exported="false"
|
||||
android:grantUriPermissions="true">
|
||||
<meta-data
|
||||
android:name="android.support.FILE_PROVIDER_PATHS"
|
||||
android:resource="@xml/file_paths"></meta-data>
|
||||
</provider>
|
||||
</application>
|
||||
|
||||
<!-- Permissions -->
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<uses-permission android:name="android.permission.CAMERA" />
|
||||
<uses-feature android:name="android.hardware.usb.host" android:required="false"/>
|
||||
</manifest>
|
||||
@@ -0,0 +1,31 @@
|
||||
package com.tiptoisync.app
|
||||
|
||||
import com.getcapacitor.Plugin
|
||||
import com.getcapacitor.PluginCall
|
||||
import com.getcapacitor.PluginMethod
|
||||
import com.getcapacitor.annotation.CapacitorPlugin
|
||||
|
||||
@CapacitorPlugin(name = "Dev")
|
||||
class DevPlugin : Plugin() {
|
||||
|
||||
private val prefs get() = context.getSharedPreferences("CapacitorStorage", 0)
|
||||
|
||||
/** Speichert die Dev-Server-URL und startet die Activity neu,
|
||||
* damit die Bridge mit der neuen URL initialisiert wird. */
|
||||
@PluginMethod
|
||||
fun activate(call: PluginCall) {
|
||||
val url = call.getString("url") ?: return call.reject("url required")
|
||||
prefs.edit().putString("devServerUrl", url).apply()
|
||||
activity.runOnUiThread { activity.recreate() }
|
||||
call.resolve()
|
||||
}
|
||||
|
||||
/** Entfernt die Dev-Server-URL und startet die Activity neu,
|
||||
* damit die Bridge wieder die gebundelte App lädt. */
|
||||
@PluginMethod
|
||||
fun deactivate(call: PluginCall) {
|
||||
prefs.edit().remove("devServerUrl").apply()
|
||||
activity.runOnUiThread { activity.recreate() }
|
||||
call.resolve()
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
package com.tiptoisync.app
|
||||
|
||||
import android.os.Bundle
|
||||
import androidx.core.view.WindowCompat
|
||||
import com.getcapacitor.BridgeActivity
|
||||
|
||||
class MainActivity : BridgeActivity() {
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
registerPlugin(TiptoiPlugin::class.java)
|
||||
super.onCreate(savedInstanceState)
|
||||
WindowCompat.setDecorFitsSystemWindows(window, false)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,264 @@
|
||||
package com.tiptoisync.app
|
||||
|
||||
import android.content.Intent
|
||||
import android.net.Uri
|
||||
import android.provider.DocumentsContract
|
||||
import androidx.activity.result.ActivityResult
|
||||
import com.getcapacitor.JSArray
|
||||
import com.getcapacitor.JSObject
|
||||
import com.getcapacitor.Plugin
|
||||
import com.getcapacitor.PluginCall
|
||||
import com.getcapacitor.PluginMethod
|
||||
import com.getcapacitor.annotation.ActivityCallback
|
||||
import com.getcapacitor.annotation.CapacitorPlugin
|
||||
import java.io.File
|
||||
import java.io.FileInputStream
|
||||
import java.io.FileOutputStream
|
||||
import java.io.IOException
|
||||
import java.net.HttpURLConnection
|
||||
import java.net.URL
|
||||
|
||||
@CapacitorPlugin(name = "Tiptoi")
|
||||
class TiptoiPlugin : Plugin() {
|
||||
|
||||
private val downloadsDir get() = File(context.filesDir, "downloads").also { it.mkdirs() }
|
||||
private val prefs get() = context.getSharedPreferences("tiptoi", 0)
|
||||
|
||||
// ── Stift (SAF) ──────────────────────────────────────────────────────────
|
||||
|
||||
@PluginMethod
|
||||
fun requestPenAccess(call: PluginCall) {
|
||||
val intent = Intent(Intent.ACTION_OPEN_DOCUMENT_TREE).apply {
|
||||
addFlags(
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_WRITE_URI_PERMISSION or
|
||||
Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION
|
||||
)
|
||||
}
|
||||
startActivityForResult(call, intent, "onPenDirectoryPicked")
|
||||
}
|
||||
|
||||
@ActivityCallback
|
||||
private fun onPenDirectoryPicked(call: PluginCall?, result: ActivityResult) {
|
||||
val uri = result.data?.data ?: run { call?.reject("Abgebrochen"); return }
|
||||
context.contentResolver.takePersistableUriPermission(
|
||||
uri,
|
||||
Intent.FLAG_GRANT_READ_URI_PERMISSION or Intent.FLAG_GRANT_WRITE_URI_PERMISSION
|
||||
)
|
||||
prefs.edit().putString("pen_uri", uri.toString()).apply()
|
||||
call?.resolve(JSObject().put("uri", uri.toString()))
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun getPenUri(call: PluginCall) {
|
||||
val uri = prefs.getString("pen_uri", null)
|
||||
val result = JSObject()
|
||||
if (uri != null) result.put("uri", uri)
|
||||
call.resolve(result)
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun listPenFiles(call: PluginCall) {
|
||||
val uriStr = call.getString("uri") ?: return call.reject("uri fehlt")
|
||||
val treeUri = Uri.parse(uriStr)
|
||||
val childUri = DocumentsContract.buildChildDocumentsUriUsingTree(
|
||||
treeUri, DocumentsContract.getTreeDocumentId(treeUri)
|
||||
)
|
||||
val cursor = context.contentResolver.query(
|
||||
childUri,
|
||||
arrayOf(DocumentsContract.Document.COLUMN_DISPLAY_NAME),
|
||||
null, null, null
|
||||
) ?: return call.reject("Verzeichnis nicht lesbar")
|
||||
|
||||
val files = JSArray()
|
||||
cursor.use {
|
||||
while (it.moveToNext()) {
|
||||
val name = it.getString(0)
|
||||
if (name.endsWith(".gme", ignoreCase = true)) files.put(name)
|
||||
}
|
||||
}
|
||||
call.resolve(JSObject().put("files", files))
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun copyToPen(call: PluginCall) {
|
||||
val uriStr = call.getString("uri") ?: return call.reject("uri fehlt")
|
||||
val filename = call.getString("filename") ?: return call.reject("filename fehlt")
|
||||
val src = File(downloadsDir, filename)
|
||||
if (!src.exists()) return call.reject("Datei nicht im Cache: $filename")
|
||||
|
||||
Thread {
|
||||
try {
|
||||
val treeUri = Uri.parse(uriStr)
|
||||
val treeDocUri = DocumentsContract.buildDocumentUriUsingTree(
|
||||
treeUri, DocumentsContract.getTreeDocumentId(treeUri)
|
||||
)
|
||||
|
||||
// Existierende Datei löschen (z. B. 0-Byte-Rest eines Fehlversuchs)
|
||||
val existingUri = findDocInTree(treeUri, filename)
|
||||
if (existingUri != null) {
|
||||
DocumentsContract.deleteDocument(context.contentResolver, existingUri)
|
||||
}
|
||||
|
||||
// Neue, leere Datei anlegen
|
||||
val targetUri = DocumentsContract.createDocument(
|
||||
context.contentResolver,
|
||||
treeDocUri,
|
||||
"application/octet-stream",
|
||||
filename
|
||||
) ?: throw IOException("Konnte Datei nicht anlegen: $filename")
|
||||
|
||||
val total = src.length()
|
||||
var bytesCopied = 0L
|
||||
val buffer = ByteArray(256 * 1024)
|
||||
|
||||
// openFileDescriptor statt openOutputStream: gibt echten fd zurück,
|
||||
// damit wir sync() = fsync() aufrufen können
|
||||
val pfd = context.contentResolver.openFileDescriptor(targetUri, "wt")
|
||||
?: throw IOException("Konnte Datei nicht öffnen: $filename")
|
||||
|
||||
pfd.use { descriptor ->
|
||||
FileOutputStream(descriptor.fileDescriptor).use { out ->
|
||||
FileInputStream(src).use { input ->
|
||||
var read: Int
|
||||
while (input.read(buffer).also { read = it } != -1) {
|
||||
out.write(buffer, 0, read)
|
||||
bytesCopied += read
|
||||
notifyListeners("copyProgress", JSObject()
|
||||
.put("filename", filename)
|
||||
.put("bytes", bytesCopied)
|
||||
.put("total", total))
|
||||
}
|
||||
out.flush()
|
||||
}
|
||||
}
|
||||
// fsync: Kernel-Pagecache → physische SD-Karte flushen.
|
||||
// Für MTP-Geräte wirft dies (kein echtes fd) – ignorieren,
|
||||
// MTP committed bereits beim close() des Streams.
|
||||
try { descriptor.fileDescriptor.sync() } catch (_: Exception) {}
|
||||
}
|
||||
|
||||
// Verifikation: tatsächlich geschriebene Bytes prüfen
|
||||
val written = context.contentResolver.query(
|
||||
targetUri,
|
||||
arrayOf(DocumentsContract.Document.COLUMN_SIZE),
|
||||
null, null, null
|
||||
)?.use { c -> if (c.moveToFirst()) c.getLong(0) else -1L } ?: -1L
|
||||
|
||||
if (written != total) {
|
||||
throw IOException("Schreibfehler: $written von $total Bytes angekommen")
|
||||
}
|
||||
|
||||
call.resolve()
|
||||
} catch (e: Exception) {
|
||||
call.reject(e.message ?: "Fehler beim Kopieren")
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
private fun findDocInTree(treeUri: Uri, filename: String): Uri? {
|
||||
val childUri = DocumentsContract.buildChildDocumentsUriUsingTree(
|
||||
treeUri, DocumentsContract.getTreeDocumentId(treeUri)
|
||||
)
|
||||
val cursor = context.contentResolver.query(
|
||||
childUri,
|
||||
arrayOf(
|
||||
DocumentsContract.Document.COLUMN_DOCUMENT_ID,
|
||||
DocumentsContract.Document.COLUMN_DISPLAY_NAME
|
||||
),
|
||||
null, null, null
|
||||
) ?: return null
|
||||
|
||||
cursor.use {
|
||||
while (it.moveToNext()) {
|
||||
if (it.getString(1) == filename) {
|
||||
return DocumentsContract.buildDocumentUriUsingTree(treeUri, it.getString(0))
|
||||
}
|
||||
}
|
||||
}
|
||||
return null
|
||||
}
|
||||
|
||||
// ── Downloads (App-Cache) ────────────────────────────────────────────────
|
||||
|
||||
@PluginMethod
|
||||
fun listDownloads(call: PluginCall) {
|
||||
val gmeFiles = downloadsDir
|
||||
.listFiles { f -> f.extension.equals("gme", ignoreCase = true) }
|
||||
?: emptyArray()
|
||||
|
||||
val books = JSArray()
|
||||
for (gme in gmeFiles) {
|
||||
val base = gme.nameWithoutExtension
|
||||
val cover = downloadsDir.listFiles { f ->
|
||||
f.nameWithoutExtension == base && !f.extension.equals("gme", ignoreCase = true)
|
||||
}?.firstOrNull()
|
||||
books.put(JSObject().put("filename", gme.name).put("coverPath", cover?.absolutePath).put("size", gme.length()))
|
||||
}
|
||||
call.resolve(JSObject().put("books", books))
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun downloadGme(call: PluginCall) {
|
||||
val url = call.getString("url") ?: return call.reject("url fehlt")
|
||||
val filename = call.getString("filename") ?: return call.reject("filename fehlt")
|
||||
|
||||
Thread {
|
||||
try {
|
||||
downloadToFile(url, File(downloadsDir, filename))
|
||||
call.resolve(JSObject().put("filename", filename))
|
||||
} catch (e: Exception) {
|
||||
call.reject(e.message ?: "Download fehlgeschlagen")
|
||||
}
|
||||
}.start()
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun downloadCover(call: PluginCall) {
|
||||
val url = call.getString("url") ?: return call.reject("url fehlt")
|
||||
val filename = call.getString("filename") ?: return call.reject("filename fehlt")
|
||||
|
||||
Thread {
|
||||
try { downloadToFile(url, File(downloadsDir, filename)) }
|
||||
catch (_: Exception) { /* Cover ist optional */ }
|
||||
call.resolve()
|
||||
}.start()
|
||||
}
|
||||
|
||||
// ── Löschen ────────────────────────────────────────────────────────────
|
||||
|
||||
@PluginMethod
|
||||
fun deleteDownload(call: PluginCall) {
|
||||
val filename = call.getString("filename") ?: return call.reject("filename fehlt")
|
||||
val gme = File(downloadsDir, File(filename).name)
|
||||
if (!gme.exists()) return call.reject("Datei nicht gefunden: $filename")
|
||||
val base = gme.nameWithoutExtension
|
||||
gme.delete()
|
||||
downloadsDir.listFiles { f ->
|
||||
f.nameWithoutExtension == base && !f.extension.equals("gme", ignoreCase = true)
|
||||
}?.forEach { it.delete() }
|
||||
call.resolve()
|
||||
}
|
||||
|
||||
@PluginMethod
|
||||
fun deletePenFile(call: PluginCall) {
|
||||
val uriStr = call.getString("uri") ?: return call.reject("uri fehlt")
|
||||
val filename = call.getString("filename") ?: return call.reject("filename fehlt")
|
||||
val treeUri = Uri.parse(uriStr)
|
||||
val docUri = findDocInTree(treeUri, filename)
|
||||
?: return call.reject("Datei nicht auf Stift gefunden: $filename")
|
||||
val deleted = DocumentsContract.deleteDocument(context.contentResolver, docUri)
|
||||
if (deleted) call.resolve() else call.reject("Löschen fehlgeschlagen")
|
||||
}
|
||||
|
||||
// ── Hilfsfunktionen ──────────────────────────────────────────────────────
|
||||
|
||||
private fun downloadToFile(url: String, dest: File) {
|
||||
val conn = URL(url).openConnection() as HttpURLConnection
|
||||
conn.connectTimeout = 15_000
|
||||
conn.readTimeout = 60_000
|
||||
conn.connect()
|
||||
if (conn.responseCode != 200) throw IOException("HTTP ${conn.responseCode}")
|
||||
conn.inputStream.use { input -> FileOutputStream(dest).use { input.copyTo(it) } }
|
||||
}
|
||||
}
|
||||
|
After Width: | Height: | Size: 7.5 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 9.0 KiB |
|
After Width: | Height: | Size: 14 KiB |
|
After Width: | Height: | Size: 17 KiB |
|
After Width: | Height: | Size: 7.7 KiB |
|
After Width: | Height: | Size: 4.0 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 13 KiB |
|
After Width: | Height: | Size: 17 KiB |
@@ -0,0 +1,34 @@
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:aapt="http://schemas.android.com/aapt"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillType="evenOdd"
|
||||
android:pathData="M32,64C32,64 38.39,52.99 44.13,50.95C51.37,48.37 70.14,49.57 70.14,49.57L108.26,87.69L108,109.01L75.97,107.97L32,64Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1">
|
||||
<aapt:attr name="android:fillColor">
|
||||
<gradient
|
||||
android:endX="78.5885"
|
||||
android:endY="90.9159"
|
||||
android:startX="48.7653"
|
||||
android:startY="61.0927"
|
||||
android:type="linear">
|
||||
<item
|
||||
android:color="#44000000"
|
||||
android:offset="0.0" />
|
||||
<item
|
||||
android:color="#00000000"
|
||||
android:offset="1.0" />
|
||||
</gradient>
|
||||
</aapt:attr>
|
||||
</path>
|
||||
<path
|
||||
android:fillColor="#FFFFFF"
|
||||
android:fillType="nonZero"
|
||||
android:pathData="M66.94,46.02L66.94,46.02C72.44,50.07 76,56.61 76,64L32,64C32,56.61 35.56,50.11 40.98,46.06L36.18,41.19C35.45,40.45 35.45,39.3 36.18,38.56C36.91,37.81 38.05,37.81 38.78,38.56L44.25,44.05C47.18,42.57 50.48,41.71 54,41.71C57.48,41.71 60.78,42.57 63.68,44.05L69.11,38.56C69.84,37.81 70.98,37.81 71.71,38.56C72.44,39.3 72.44,40.45 71.71,41.19L66.94,46.02ZM62.94,56.92C64.08,56.92 65,56.01 65,54.88C65,53.76 64.08,52.85 62.94,52.85C61.8,52.85 60.88,53.76 60.88,54.88C60.88,56.01 61.8,56.92 62.94,56.92ZM45.06,56.92C46.2,56.92 47.13,56.01 47.13,54.88C47.13,53.76 46.2,52.85 45.06,52.85C43.92,52.85 43,53.76 43,54.88C43,56.01 43.92,56.92 45.06,56.92Z"
|
||||
android:strokeColor="#00000000"
|
||||
android:strokeWidth="1" />
|
||||
</vector>
|
||||
@@ -0,0 +1,170 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:width="108dp"
|
||||
android:height="108dp"
|
||||
android:viewportHeight="108"
|
||||
android:viewportWidth="108">
|
||||
<path
|
||||
android:fillColor="#26A69A"
|
||||
android:pathData="M0,0h108v108h-108z" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M9,0L9,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,0L19,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,0L29,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,0L39,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,0L49,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,0L59,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,0L69,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,0L79,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M89,0L89,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M99,0L99,108"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,9L108,9"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,19L108,19"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,29L108,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,39L108,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,49L108,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,59L108,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,69L108,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,79L108,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,89L108,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M0,99L108,99"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,29L89,29"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,39L89,39"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,49L89,49"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,59L89,59"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,69L89,69"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M19,79L89,79"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M29,19L29,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M39,19L39,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M49,19L49,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M59,19L59,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M69,19L69,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
<path
|
||||
android:fillColor="#00000000"
|
||||
android:pathData="M79,19L79,89"
|
||||
android:strokeColor="#33FFFFFF"
|
||||
android:strokeWidth="0.8" />
|
||||
</vector>
|
||||
|
After Width: | Height: | Size: 3.9 KiB |
@@ -0,0 +1,12 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<androidx.coordinatorlayout.widget.CoordinatorLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
tools:context=".MainActivity">
|
||||
|
||||
<WebView
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent" />
|
||||
</androidx.coordinatorlayout.widget.CoordinatorLayout>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<background android:drawable="@color/ic_launcher_background"/>
|
||||
<foreground android:drawable="@mipmap/ic_launcher_foreground"/>
|
||||
</adaptive-icon>
|
||||
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.4 KiB |
|
After Width: | Height: | Size: 4.2 KiB |
|
After Width: | Height: | Size: 1.8 KiB |
|
After Width: | Height: | Size: 2.1 KiB |
|
After Width: | Height: | Size: 2.7 KiB |
|
After Width: | Height: | Size: 3.9 KiB |
|
After Width: | Height: | Size: 4.9 KiB |
|
After Width: | Height: | Size: 6.4 KiB |
|
After Width: | Height: | Size: 6.5 KiB |
|
After Width: | Height: | Size: 9.6 KiB |
|
After Width: | Height: | Size: 10 KiB |
|
After Width: | Height: | Size: 9.2 KiB |
|
After Width: | Height: | Size: 15 KiB |
|
After Width: | Height: | Size: 16 KiB |
@@ -0,0 +1,4 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
<color name="ic_launcher_background">#FFFFFF</color>
|
||||
</resources>
|
||||
@@ -0,0 +1,7 @@
|
||||
<?xml version='1.0' encoding='utf-8'?>
|
||||
<resources>
|
||||
<string name="app_name">TipToi Sync</string>
|
||||
<string name="title_activity_main">TipToi Sync</string>
|
||||
<string name="package_name">com.tiptoisync.app</string>
|
||||
<string name="custom_url_scheme">com.tiptoisync.app</string>
|
||||
</resources>
|
||||
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<resources>
|
||||
|
||||
<!-- Base application theme. -->
|
||||
<style name="AppTheme" parent="Theme.AppCompat.Light.DarkActionBar">
|
||||
<!-- Customize your theme here. -->
|
||||
<item name="colorPrimary">@color/colorPrimary</item>
|
||||
<item name="colorPrimaryDark">@color/colorPrimaryDark</item>
|
||||
<item name="colorAccent">@color/colorAccent</item>
|
||||
</style>
|
||||
|
||||
<style name="AppTheme.NoActionBar" parent="Theme.AppCompat.DayNight.NoActionBar">
|
||||
<item name="windowActionBar">false</item>
|
||||
<item name="windowNoTitle">true</item>
|
||||
<item name="android:background">@null</item>
|
||||
</style>
|
||||
|
||||
|
||||
<style name="AppTheme.NoActionBarLaunch" parent="Theme.SplashScreen">
|
||||
<item name="android:background">@drawable/splash</item>
|
||||
</style>
|
||||
</resources>
|
||||
@@ -0,0 +1,5 @@
|
||||
<?xml version="1.0" encoding="utf-8"?>
|
||||
<paths xmlns:android="http://schemas.android.com/apk/res/android">
|
||||
<external-path name="my_images" path="." />
|
||||
<cache-path name="my_cache_images" path="." />
|
||||
</paths>
|
||||
@@ -0,0 +1,18 @@
|
||||
package com.getcapacitor.myapp;
|
||||
|
||||
import static org.junit.Assert.*;
|
||||
|
||||
import org.junit.Test;
|
||||
|
||||
/**
|
||||
* Example local unit test, which will execute on the development machine (host).
|
||||
*
|
||||
* @see <a href="http://d.android.com/tools/testing">Testing documentation</a>
|
||||
*/
|
||||
public class ExampleUnitTest {
|
||||
|
||||
@Test
|
||||
public void addition_isCorrect() throws Exception {
|
||||
assertEquals(4, 2 + 2);
|
||||
}
|
||||
}
|
||||