Skip to content

Commit

Permalink
Implement ExternalStorage and extract BinarySerializer
Browse files Browse the repository at this point in the history
  • Loading branch information
profiluefter committed Mar 16, 2021
1 parent b3b56b4 commit 86e2206
Show file tree
Hide file tree
Showing 22 changed files with 147 additions and 28 deletions.
7 changes: 5 additions & 2 deletions app/build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -41,13 +41,16 @@ 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 'androidx.activity:activity-ktx:1.2.0'
implementation 'androidx.fragment:fragment-ktx:1.3.0'
implementation 'androidx.activity:activity-ktx:1.2.1'
implementation 'androidx.fragment:fragment-ktx:1.3.1'
implementation 'com.google.android.material:material:1.3.0'
implementation 'androidx.constraintlayout:constraintlayout:2.0.4'
implementation 'androidx.lifecycle:lifecycle-extensions:2.2.0'
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.3.0'
implementation 'androidx.preference:preference-ktx:1.1.1'
implementation "com.google.dagger:hilt-android:$hilt_version"

implementation project(":data-lib")

kapt "com.google.dagger:hilt-android-compiler:$hilt_version"
}
5 changes: 4 additions & 1 deletion app/src/main/AndroidManifest.xml
Original file line number Diff line number Diff line change
Expand Up @@ -2,14 +2,17 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="me.profiluefter.profinote">

<uses-permission android:name="android.permission.WRITE_EXTERNAL_STORAGE"/>

<application
android:name=".NotianApplication"
android:allowBackup="true"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:roundIcon="@mipmap/ic_launcher_round"
android:supportsRtl="true"
android:theme="@style/Theme.Notian">
android:theme="@style/Theme.Notian"
android:requestLegacyExternalStorage="true">
<activity android:name=".activities.MainActivity">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
Expand Down
4 changes: 3 additions & 1 deletion app/src/main/java/me/profiluefter/profinote/HiltModule.kt
Original file line number Diff line number Diff line change
Expand Up @@ -34,10 +34,12 @@ object PreferenceBasedModule {
@Provides
fun storageBinding(
@ApplicationContext context: Context,
private: Provider<PrivateFileStorage>
private: Provider<PrivateFileStorage>,
external: Provider<ExternalStorage>
): Storage = when (PreferenceManager.getDefaultSharedPreferences(context)
.getString("storageLocation", "privateFile")) {
"privateFile" -> private.get()
"externalStorage" -> external.get()
else -> null!!
}
}
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
package me.profiluefter.profinote.activities

import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Bundle
import android.os.Environment
import android.view.Menu
import android.view.MenuItem
import android.view.View
Expand All @@ -11,27 +14,30 @@ import androidx.activity.viewModels
import androidx.appcompat.app.AppCompatActivity
import androidx.appcompat.view.menu.MenuBuilder
import androidx.databinding.DataBindingUtil
import androidx.preference.PreferenceManager
import androidx.recyclerview.widget.DividerItemDecoration
import androidx.recyclerview.widget.RecyclerView
import com.google.android.material.snackbar.Snackbar
import dagger.hilt.android.AndroidEntryPoint
import me.profiluefter.profinote.R
import me.profiluefter.profinote.data.Note
import me.profiluefter.profinote.databinding.ActivityMainBinding
import me.profiluefter.profinote.models.MainActivityViewModel
import me.profiluefter.profinote.models.Note

@AndroidEntryPoint
class MainActivity : AppCompatActivity() {
private val viewModel: MainActivityViewModel by viewModels()

private val editorActivity = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) {
if(it.resultCode != RESULT_OK || it.data == null) return@registerForActivityResult
if (it.resultCode != RESULT_OK || it.data == null) return@registerForActivityResult

val note = it.data!!.getSerializableExtra("note") as Note
val position = it.data!!.getIntExtra("position", -1)
viewModel.setNote(position, note)
}

private val permissionRequestCode = 187

override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val binding: ActivityMainBinding =
Expand Down Expand Up @@ -59,7 +65,7 @@ class MainActivity : AppCompatActivity() {
override fun onCreateOptionsMenu(menu: Menu?): Boolean {
menuInflater.inflate(R.menu.main_menu, menu)

if(menu is MenuBuilder) {
if (menu is MenuBuilder) {
menu.setOptionalIconsVisible(true)
}

Expand All @@ -69,10 +75,30 @@ class MainActivity : AppCompatActivity() {
fun onNewNote(item: MenuItem) = onNewNote()

fun onSaveNotes(item: MenuItem) {
val usesExternalStorage = PreferenceManager.getDefaultSharedPreferences(this).getString("storageLocation", "")
.equals("externalStorage")
if (usesExternalStorage && checkSelfPermission(Manifest.permission.WRITE_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), permissionRequestCode)
return
}
if (!checkExternalStorage()) return
viewModel.saveNotes()
Snackbar.make(findViewById(R.id.notes), R.string.saved_notes, Snackbar.LENGTH_SHORT).show()
}

override fun onRequestPermissionsResult(requestCode: Int, permissions: Array<out String>, grantResults: IntArray) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (requestCode != permissionRequestCode) return
if (grantResults.size == 1 && grantResults[0] == PackageManager.PERMISSION_GRANTED) {
if(!checkExternalStorage()) return
viewModel.saveNotes()
} else Snackbar.make(findViewById(R.id.notes), R.string.permission_denied, Snackbar.LENGTH_SHORT)
.setAction(R.string.grant_permission) {
requestPermissions(arrayOf(Manifest.permission.WRITE_EXTERNAL_STORAGE), permissionRequestCode)
}
.show()
}

fun onEditNote(index: Int) {
val intent = Intent(this, NoteEditorActivity::class.java)
intent.putExtra("position", index)
Expand Down Expand Up @@ -107,4 +133,11 @@ class MainActivity : AppCompatActivity() {
intent.putExtra("position", -1)
editorActivity.launch(intent)
}

private fun checkExternalStorage(): Boolean =
(Environment.getExternalStorageState() == Environment.MEDIA_MOUNTED).also {
if (!it) {
Snackbar.make(findViewById(R.id.notes), R.string.no_sd_card, Snackbar.LENGTH_SHORT).show()
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,7 @@ import androidx.appcompat.app.AppCompatActivity
import androidx.databinding.DataBindingUtil
import me.profiluefter.profinote.R
import me.profiluefter.profinote.databinding.ActivityNoteDetailsBinding
import me.profiluefter.profinote.models.Note
import me.profiluefter.profinote.data.Note

class NoteDetailsActivity : AppCompatActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ import androidx.databinding.BindingAdapter
import androidx.recyclerview.widget.RecyclerView
import me.profiluefter.profinote.R
import me.profiluefter.profinote.databinding.RecyclerViewItemBinding
import me.profiluefter.profinote.models.Note
import me.profiluefter.profinote.data.Note

class NotesAdapter(notes: List<Note>, private val context: MainActivity) :
RecyclerView.Adapter<NotesAdapter.ViewHolder>() {
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package me.profiluefter.profinote.data

import android.util.Log
import me.profiluefter.profinote.models.Note
import java.net.URLDecoder
import java.net.URLEncoder
import javax.inject.Inject
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
package me.profiluefter.profinote.data

import android.content.Context
import android.util.Log
import dagger.hilt.android.qualifiers.ApplicationContext
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
import java.io.File
import java.io.FileNotFoundException
import javax.inject.Inject
import javax.inject.Named

private const val TAG = "ExternalStorage"

class ExternalStorage @Inject constructor(
@ApplicationContext private val context: Context,
@Named("fileName") private val fileName: String
) : Storage {
override suspend fun store(data: ByteArray) = withContext(Dispatchers.IO) {
val externalStorage = context.getExternalFilesDir(null)
val file = File(externalStorage!!.absolutePath + File.separator + fileName)
Log.i(TAG, "Saving data to ${file.absolutePath}!")
file.writeBytes(data)
}

override suspend fun get(): ByteArray? = withContext(Dispatchers.IO) {
val externalStorage = context.getExternalFilesDir(null)
try {
val file = File(externalStorage!!.absolutePath + File.separator + fileName)
Log.i(TAG, "Trying to read data from ${file.absolutePath}!")
file.readBytes()
} catch (e: FileNotFoundException) {
Log.w(TAG, "File not found.")
null
}
}
}
Original file line number Diff line number Diff line change
@@ -1,7 +1,6 @@
package me.profiluefter.profinote.data

import android.util.Log
import me.profiluefter.profinote.models.Note
import javax.inject.Inject
import kotlin.math.min
import kotlin.random.Random
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import androidx.lifecycle.ViewModel
import androidx.lifecycle.viewModelScope
import dagger.hilt.android.lifecycle.HiltViewModel
import kotlinx.coroutines.launch
import me.profiluefter.profinote.data.Note
import me.profiluefter.profinote.data.Serializer
import javax.inject.Inject
import javax.inject.Provider
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import android.content.Intent
import androidx.lifecycle.MutableLiveData
import androidx.lifecycle.ViewModel
import androidx.lifecycle.ViewModelProvider
import me.profiluefter.profinote.data.*
import java.util.*

class NoteEditorActivityViewModelFactory(private val intent: Intent) : ViewModelProvider.Factory {
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/res/layout/activity_note_details.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,11 +5,11 @@

<data>

<import type="me.profiluefter.profinote.models.NoteKt" />
<import type="me.profiluefter.profinote.data.NoteKt" />

<variable
name="note"
type="me.profiluefter.profinote.models.Note" />
type="me.profiluefter.profinote.data.Note" />
</data>

<androidx.constraintlayout.widget.ConstraintLayout
Expand Down
4 changes: 2 additions & 2 deletions app/src/main/res/layout/recycler_view_item.xml
Original file line number Diff line number Diff line change
Expand Up @@ -5,12 +5,12 @@

<data>

<import type="me.profiluefter.profinote.models.NoteKt" />
<import type="me.profiluefter.profinote.data.NoteKt" />
<import type="me.profiluefter.profinote.activities.MainActivity"/>

<variable
name="note"
type="me.profiluefter.profinote.models.Note" />
type="me.profiluefter.profinote.data.Note" />

<variable
name="position"
Expand Down
2 changes: 2 additions & 0 deletions app/src/main/res/values/arrays.xml
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,10 @@
</string-array>
<string-array name="storage_location_name">
<item>@string/storage_location_private_file</item>
<item>@string/storage_location_external_storage</item>
</string-array>
<string-array name="storage_location_values">
<item>privateFile</item>
<item>externalStorage</item>
</string-array>
</resources>
4 changes: 4 additions & 0 deletions app/src/main/res/values/strings.xml
Original file line number Diff line number Diff line change
Expand Up @@ -26,4 +26,8 @@
<!-- <string name="storage_location_description">The location where your notes are saved</string>-->
<string name="preferences">Preferences</string>
<string name="storage_file_name">File Name</string>
<string name="permission_denied">Permission denied</string>
<string name="grant_permission">Grant permission</string>
<string name="no_sd_card">No SD-Card inserted</string>
<string name="storage_location_external_storage">External Storage</string>
</resources>
1 change: 1 addition & 0 deletions data-lib/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
/build
31 changes: 31 additions & 0 deletions data-lib/build.gradle
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
plugins {
id 'org.jetbrains.kotlin.jvm'
id 'java-library'
id 'org.jetbrains.dokka' version '1.4.20'
}

dependencies {
implementation 'javax.inject:javax.inject:1'
}

task srcZip(type: Zip) {
archiveAppendix = "src"
destinationDirectory = file("$buildDir/output")
from sourceSets*.allSource
}

jar {
destinationDirectory = file("$buildDir/output")
}

tasks.named("dokkaHtml") {
outputDirectory = file("$buildDir/dokka")
}

task docZip(type: Zip, dependsOn: [dokkaHtml]) {
archiveAppendix = "doc"
destinationDirectory = file("$buildDir/output")
from file("$buildDir/dokka")
}

task generateZIPs(dependsOn: [jar, srcZip, docZip])
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
package me.profiluefter.profinote.data

import me.profiluefter.profinote.models.Note
import java.nio.BufferUnderflowException
import java.nio.ByteBuffer
import javax.inject.Inject
Expand All @@ -22,15 +21,17 @@ class BinarySerializer @Inject constructor(private val storage: Storage) : Seria
}

repeat(size) {
notes.add(Note(
readString(),
buffer.get().toInt(),
buffer.get().toInt(),
buffer.get().toInt(),
buffer.get().toInt(),
buffer.short.toInt(),
readString()
))
notes.add(
Note(
readString(),
buffer.get().toInt(),
buffer.get().toInt(),
buffer.get().toInt(),
buffer.get().toInt(),
buffer.short.toInt(),
readString()
)
)
}

return notes
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
package me.profiluefter.profinote.models
package me.profiluefter.profinote.data

import java.io.Serializable
import java.util.*
Expand Down
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package me.profiluefter.profinote.data

import me.profiluefter.profinote.models.Note

interface Serializer {
suspend fun load(): List<Note>
suspend fun save(notes: List<Note>)
Expand Down
Original file line number Diff line number Diff line change
@@ -1,5 +1,8 @@
package me.profiluefter.profinote.data

/**
* A binary storage backend
*/
interface Storage {
suspend fun store(data: ByteArray)
suspend fun get(): ByteArray?
Expand Down
1 change: 1 addition & 0 deletions settings.gradle
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
include ':app'
include ':data-lib'
rootProject.name = "Notian"

0 comments on commit 86e2206

Please sign in to comment.