Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
## 2.1.0

- Add settings export/import
- Improve Intent selector (multi-select, search)
- Bump Gradle
5 changes: 3 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,8 @@ android {
minSdkVersion 21
compileSdkVersion 35
targetSdkVersion 35
versionCode 19
versionName "2.0.0"
versionCode 20
versionName "2.1.0"
archivesBaseName = "paperlaunch-v${defaultConfig.versionName}-${buildTime()}"
testInstrumentationRunner "android.support.test.runner.AndroidJUnitRunner"
}
Expand Down Expand Up @@ -60,6 +60,7 @@ dependencies {
implementation 'io.reactivex:rxandroid:1.1.0'
implementation 'io.reactivex:rxjava:1.1.0'
implementation "org.jetbrains.kotlin:kotlin-stdlib-jdk7:$kotlin_version"
implementation 'com.google.code.gson:gson:2.10.1'
implementation 'com.android.support.constraint:constraint-layout:2.0.4'

testImplementation 'junit:junit:4.13.2'
Expand Down
3 changes: 2 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,8 @@
</activity>
<activity
android:name=".view.utils.IntentSelector"
android:label="@string/activity_intentselector_label" />
android:label="@string/activity_intentselector_label"
android:theme="@style/Theme.PaperLaunch.NoActionBar"/>
<activity
android:name=".view.utils.UrlSelector"
android:label="@string/activity_urlselector_label" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -174,8 +174,12 @@ class LauncherOverlayService : Service() {
}

private fun adaptState(forceReload: Boolean) {
if (forceReload) {
resetConfig()
resetData()
}
if (state.isActive) {
ensureOverlayActive(forceReload)
ensureOverlayActive(false)
} else {
ensureOverlayInActive()
}
Expand Down
73 changes: 73 additions & 0 deletions app/src/main/java/de/devmil/paperlaunch/storage/DataExporter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
package de.devmil.paperlaunch.storage

import android.content.Context
import android.graphics.Bitmap
import android.graphics.drawable.BitmapDrawable
import android.util.Base64
import com.google.gson.Gson
import de.devmil.paperlaunch.model.IFolder
import de.devmil.paperlaunch.model.Launch
import de.devmil.paperlaunch.utils.BitmapUtils
import de.devmil.paperlaunch.utils.IntentSerializer
import java.io.ByteArrayOutputStream

class DataExporter(private val context: Context) {

data class ExportData(
val version: Int = 1,
val entries: List<ExportEntry>
)

data class ExportEntry(
val type: String, // "folder" or "launch"
val name: String?,
val icon: String?, // Base64 encoded png
val intentUri: String?, // Only for launch
val entries: List<ExportEntry>? // Only for folder
)

fun exportToJson(): String {
var rootEntries: List<ExportEntry> = emptyList()
EntriesDataSource.instance.accessData(context, object : ITransactionAction {
override fun execute(transactionContext: ITransactionContext) {
val roots = transactionContext.loadRootContent()
rootEntries = roots.map { convertToExportEntry(it) }
}
})

val exportData = ExportData(entries = rootEntries)
return Gson().toJson(exportData)
}

private fun convertToExportEntry(entry: de.devmil.paperlaunch.model.IEntry): ExportEntry {
if (entry.isFolder) {
val folder = entry as IFolder
val subEntries = folder.subEntries?.map { convertToExportEntry(it) } ?: emptyList()
return ExportEntry(
type = "folder",
name = folder.name,
icon = encodeIcon(folder.icon?.let { if (it is BitmapDrawable) it else null }), // Only encode if it's a BitmapDrawable (custom icon), otherwise null implies default
intentUri = null,
entries = subEntries
)
} else {
val launch = entry as Launch
return ExportEntry(
type = "launch",
name = launch.name,
icon = encodeIcon(launch.dto.icon?.let { if (it is BitmapDrawable) it else null }),
intentUri = IntentSerializer.serialize(launch.dto.launchIntent),
entries = null
)
}
}

private fun encodeIcon(drawable: BitmapDrawable?): String? {
if (drawable == null) return null
val bitmap = drawable.bitmap ?: return null
val stream = ByteArrayOutputStream()
bitmap.compress(Bitmap.CompressFormat.PNG, 100, stream)
val byteArray = stream.toByteArray()
return Base64.encodeToString(byteArray, Base64.DEFAULT)
}
}
81 changes: 81 additions & 0 deletions app/src/main/java/de/devmil/paperlaunch/storage/DataImporter.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
package de.devmil.paperlaunch.storage

import android.content.Context
import android.graphics.BitmapFactory
import android.graphics.drawable.BitmapDrawable
import android.util.Base64
import com.google.gson.Gson
import de.devmil.paperlaunch.utils.IntentSerializer
import java.lang.Exception

class DataImporter(private val context: Context) {

data class ExportData(
val version: Int = 1,
val entries: List<ExportEntry>
)

data class ExportEntry(
val type: String,
val name: String?,
val icon: String?,
val intentUri: String?,
val entries: List<ExportEntry>?
)

fun importFromJson(json: String) {
val data = Gson().fromJson(json, ExportData::class.java)

EntriesDataSource.instance.accessData(context, object : ITransactionAction {
override fun execute(transactionContext: ITransactionContext) {
// Clear existing data
transactionContext.clear()

// Restore data
data.entries.forEachIndexed { index, entry ->
restoreEntry(transactionContext, -1, entry, index, 0)
}
}
})
}

private fun restoreEntry(
transactionContext: ITransactionContext,
parentFolderId: Long,
entry: ExportEntry,
orderIndex: Int,
depth: Int
) {
if (entry.type == "folder") {
val folder = transactionContext.createFolder(parentFolderId, orderIndex, depth)
folder.dto.name = entry.name
if (entry.icon != null) {
folder.dto.icon = decodeIcon(entry.icon)
}
transactionContext.updateFolderData(folder)

entry.entries?.forEachIndexed { index, childEntry ->
restoreEntry(transactionContext, folder.id, childEntry, index, depth + 1)
}
} else if (entry.type == "launch") {
val launch = transactionContext.createLaunch(parentFolderId, orderIndex)
launch.dto.name = entry.name
launch.dto.launchIntent = entry.intentUri?.let { IntentSerializer.deserialize(it) }
if (entry.icon != null) {
launch.dto.icon = decodeIcon(entry.icon)
}
transactionContext.updateLaunchData(launch)
}
}

private fun decodeIcon(base64: String): BitmapDrawable? {
return try {
val bytes = Base64.decode(base64, Base64.DEFAULT)
val bitmap = BitmapFactory.decodeByteArray(bytes, 0, bytes.size)
BitmapDrawable(context.resources, bitmap)
} catch (e: Exception) {
e.printStackTrace()
null
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ class EditFolderFragment : Fragment() {
override fun onResume() {
super.onResume()
bottomSheetBehavior?.state = BottomSheetBehavior.STATE_HIDDEN
loadData()
}

@Deprecated("Deprecated in Java")
Expand Down Expand Up @@ -310,6 +311,7 @@ class EditFolderFragment : Fragment() {
intent.setClass(activity, IntentSelector::class.java)
intent.putExtra(IntentSelector.EXTRA_STRING_ACTIVITIES, resources.getString(R.string.folder_settings_add_app_activities))
intent.putExtra(IntentSelector.EXTRA_STRING_SHORTCUTS, resources.getString(R.string.folder_settings_add_app_shortcuts))
intent.putExtra(IntentSelector.EXTRA_ALLOW_MULTI_SELECT, true)

startActivityForResult(intent, REQUEST_ADD_APP)
}
Expand All @@ -331,7 +333,12 @@ class EditFolderFragment : Fragment() {
return
}
if(data != null) {
addLaunch(data)
if (data.hasExtra(IntentSelector.EXTRA_RESULT_INTENTS)) {
val list = data.getParcelableArrayListExtra<Intent>(IntentSelector.EXTRA_RESULT_INTENTS)
list?.let { addLaunches(it) }
} else {
addLaunch(data)
}
}
}
REQUEST_EDIT_FOLDER -> {
Expand All @@ -358,6 +365,27 @@ class EditFolderFragment : Fragment() {
notifyDataChanged()
}

private fun addLaunches(launchIntents: List<Intent>) {
EntriesDataSource.instance.accessData(activity, object: ITransactionAction {
override fun execute(transactionContext: ITransactionContext) {
adapter?.let { itAdapter ->
val newEntries = ArrayList<IEntry>()
for (launchIntent in launchIntents) {
val l = transactionContext.createLaunch(folderId)
l.dto.launchIntent = launchIntent
transactionContext.updateLaunchData(l)
newEntries.add(l)
}
itAdapter.addEntries(newEntries)
folder?.let { itFolder ->
updateFolderImage(itFolder.dto, itAdapter.entries)
}
}
}
})
notifyDataChanged()
}

private fun updateFolderImage(folderDto: FolderDTO, entries: List<IEntry>) {
config?.let { itConfig ->
val imgWidth = itConfig.imageWidthDip
Expand Down Expand Up @@ -434,6 +462,12 @@ class EditFolderFragment : Fragment() {
notifyDataSetChanged()
}

fun addEntries(entries: List<IEntry>) {
mEntries.addAll(entries)
saveOrder()
notifyDataSetChanged()
}

override fun getPositionForId(id: Long): Int {
return mEntries.indices.firstOrNull { mEntries[it].entryId == id } ?: -1
}
Expand Down
Loading