Add compression (#7)

This commit is contained in:
Ethan Pearson 2022-12-06 20:32:26 -04:00 committed by GitHub
commit 43132f9cdf
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
14 changed files with 316 additions and 47 deletions

View File

@ -7,6 +7,7 @@
<option name="testRunner" value="GRADLE" /> <option name="testRunner" value="GRADLE" />
<option name="distributionType" value="DEFAULT_WRAPPED" /> <option name="distributionType" value="DEFAULT_WRAPPED" />
<option name="externalProjectPath" value="$PROJECT_DIR$" /> <option name="externalProjectPath" value="$PROJECT_DIR$" />
<option name="gradleJvm" value="Embedded JDK" />
<option name="modules"> <option name="modules">
<set> <set>
<option value="$PROJECT_DIR$" /> <option value="$PROJECT_DIR$" />

View File

@ -8,7 +8,7 @@ android {
defaultConfig { defaultConfig {
applicationId "com.example.myapplication" applicationId "com.example.myapplication"
minSdk 21 minSdk 24
targetSdk 32 targetSdk 32
versionCode 1 versionCode 1
versionName "1.0" versionName "1.0"
@ -35,8 +35,7 @@ android {
} }
dependencies { dependencies {
implementation 'androidx.core:core-ktx:1.8.0'
implementation 'androidx.core:core-ktx:1.7.0'
implementation 'androidx.appcompat:appcompat:1.5.1' implementation 'androidx.appcompat:appcompat:1.5.1'
implementation 'com.google.android.material:material:1.7.0' implementation 'com.google.android.material:material:1.7.0'
implementation 'androidx.constraintlayout:constraintlayout:2.1.4' implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
@ -44,7 +43,8 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1' implementation 'androidx.lifecycle:lifecycle-viewmodel-ktx:2.5.1'
implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3' implementation 'androidx.navigation:navigation-fragment-ktx:2.5.3'
implementation 'androidx.navigation:navigation-ui-ktx:2.5.3' implementation 'androidx.navigation:navigation-ui-ktx:2.5.3'
implementation 'com.arthenica:ffmpeg-kit-full:5.1.LTS'
testImplementation 'junit:junit:4.13.2' testImplementation 'junit:junit:4.13.2'
androidTestImplementation 'androidx.test.ext:junit:1.1.3' androidTestImplementation 'androidx.test.ext:junit:1.1.4'
androidTestImplementation 'androidx.test.espresso:espresso-core:3.4.0' androidTestImplementation 'androidx.test.espresso:espresso-core:3.5.0'
} }

View File

@ -3,6 +3,9 @@
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.example.myapplication"> package="com.example.myapplication">
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.WRITE_INTERNAL_STORAGE"/>
<application <application
android:allowBackup="true" android:allowBackup="true"
android:dataExtractionRules="@xml/data_extraction_rules" android:dataExtractionRules="@xml/data_extraction_rules"

View File

@ -1,22 +1,54 @@
package com.example.myapplication package com.example.myapplication
import android.Manifest
import android.annotation.SuppressLint
import android.content.Intent
import android.content.pm.PackageManager
import android.net.Uri
import android.os.Bundle import android.os.Bundle
import android.os.Handler
import android.os.Looper
import android.provider.MediaStore
import android.util.Log
import android.view.Menu import android.view.Menu
import com.google.android.material.bottomnavigation.BottomNavigationView import android.view.MenuItem
import android.widget.Toast
import androidx.activity.result.contract.ActivityResultContracts
import androidx.appcompat.app.AppCompatActivity import androidx.appcompat.app.AppCompatActivity
import androidx.core.app.ActivityCompat
import androidx.navigation.findNavController import androidx.navigation.findNavController
import androidx.navigation.ui.AppBarConfiguration import androidx.navigation.ui.AppBarConfiguration
import androidx.navigation.ui.setupActionBarWithNavController import androidx.navigation.ui.setupActionBarWithNavController
import androidx.navigation.ui.setupWithNavController import androidx.navigation.ui.setupWithNavController
import com.arthenica.ffmpegkit.FFmpegKit
import com.arthenica.ffmpegkit.FFmpegKitConfig
import com.example.myapplication.databinding.ActivityMainBinding import com.example.myapplication.databinding.ActivityMainBinding
import com.example.myapplication.ui.compressing.CompressingAdapter
import com.example.myapplication.ui.compressing.CompressingItem
import com.google.android.material.bottomnavigation.BottomNavigationView
import java.io.File
import java.util.*
class MainActivity : AppCompatActivity() { class MainActivity : AppCompatActivity() {
private lateinit var binding: ActivityMainBinding private lateinit var binding: ActivityMainBinding
override fun onCreate(savedInstanceState: Bundle?) { override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState) super.onCreate(savedInstanceState)
// VERIFY PERMISSIONS
val permission = ActivityCompat.checkSelfPermission(this, Manifest.permission.READ_EXTERNAL_STORAGE)
if (permission != PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(
this,
arrayOf(Manifest.permission.READ_EXTERNAL_STORAGE),
1
)
}
binding = ActivityMainBinding.inflate(layoutInflater) binding = ActivityMainBinding.inflate(layoutInflater)
setContentView(binding.root) setContentView(binding.root)
@ -35,4 +67,90 @@ class MainActivity : AppCompatActivity() {
menuInflater.inflate(R.menu.action_bar_menu, menu) menuInflater.inflate(R.menu.action_bar_menu, menu)
return super.onCreateOptionsMenu(menu) return super.onCreateOptionsMenu(menu)
} }
//adds actions for when you press buttons
override fun onOptionsItemSelected(item: MenuItem): Boolean {
return when (item.itemId) {
//runs when pressing "Files"
R.id.addFile -> {
val intent = Intent()
.setType("*/*")
.setAction(Intent.ACTION_GET_CONTENT)
.addFlags(Intent.FLAG_GRANT_READ_URI_PERMISSION)
.addFlags(Intent.FLAG_GRANT_WRITE_URI_PERMISSION)
.addFlags(Intent.FLAG_GRANT_PERSISTABLE_URI_PERMISSION)
resultLauncher.launch(intent)
Toast.makeText(applicationContext, "Files", Toast.LENGTH_LONG).show()
return true
}
R.id.addYoutube ->{
Toast.makeText(applicationContext, "Youtube downloading is currently not available", Toast.LENGTH_LONG).show()
return true
}
else -> super.onOptionsItemSelected(item)
}
}
//grabs output from pressing files, used for grabbing URI
@SuppressLint("NotifyDataSetChanged") // Needed because of custom adapter
private var resultLauncher = registerForActivityResult(ActivityResultContracts.StartActivityForResult()) { result ->
if (result.resultCode == RESULT_OK) {
// There are no request codes
val data: Uri? = result.data?.data
val inUri = FFmpegKitConfig.getSafParameterForRead(this, data)
val cursor = contentResolver.query(data!!, null, null, null, null)
cursor?.moveToFirst()
val fileDate = Date(System.currentTimeMillis())
val fileName = cursor?.getString(cursor.getColumnIndexOrThrow(MediaStore.MediaColumns.DISPLAY_NAME))
val item = CompressingItem(fileName!!, 0.0, fileDate)
val outputFile = File(this.getExternalFilesDir(null), "converted_$fileName")
compressingItems.add(item)
val handler = Handler(Looper.getMainLooper())
val command = "-i $inUri -c:v mpeg4 ${outputFile.absolutePath} -y"
val session = FFmpegKit.executeAsync(command) {
compressingItems.remove(item)
handler.post {
Toast.makeText(this, "Finished converting $fileName", Toast.LENGTH_SHORT).show()
adapter.notifyDataSetChanged()
}
}
Log.i("Tag", Arrays.deepToString(session.arguments))
Log.i("Tag", session.output)
adapter.notifyDataSetChanged()
}
}
companion object {
val compressingItems: MutableList<CompressingItem> = mutableListOf()
val adapter = CompressingAdapter(compressingItems)
}
} }

View File

@ -0,0 +1,34 @@
package com.example.myapplication.ui.compressing
import android.view.LayoutInflater
import android.view.View
import android.view.ViewGroup
import android.widget.ProgressBar
import android.widget.TextView
import androidx.recyclerview.widget.RecyclerView
import com.example.myapplication.R
class CompressingAdapter(private val mCompressingItems: MutableList<CompressingItem>): RecyclerView.Adapter<CompressingAdapter.ViewHolder>() {
inner class ViewHolder(itemView: View): RecyclerView.ViewHolder(itemView) {
val filename: TextView = itemView.findViewById(R.id.compressing_filename)
val date: TextView = itemView.findViewById(R.id.compressing_date)
val progress: ProgressBar = itemView.findViewById(R.id.compressing_progress)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
val compressingItemView = LayoutInflater.from(parent.context).inflate(R.layout.item_compressing, parent, false)
return ViewHolder(compressingItemView)
}
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
val compressingItem: CompressingItem = mCompressingItems[position]
holder.filename.text = compressingItem.filename
holder.date.text = compressingItem.date.toString()
holder.progress.progress = compressingItem.progress.toInt()
}
override fun getItemCount(): Int {
return mCompressingItems.size
}
}

View File

@ -4,35 +4,38 @@ import android.os.Bundle
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.TextView
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import androidx.recyclerview.widget.LinearLayoutManager
import androidx.recyclerview.widget.RecyclerView
import com.example.myapplication.MainActivity
import com.example.myapplication.MainActivity.Companion.compressingItems
import com.example.myapplication.R
import com.example.myapplication.databinding.FragmentCompressingBinding import com.example.myapplication.databinding.FragmentCompressingBinding
class CompressingFragment : Fragment() { class CompressingFragment : Fragment() {
private var _binding: FragmentCompressingBinding? = null private var _binding: FragmentCompressingBinding? = null
// This property is only valid between onCreateView and // This property is only valid between onCreateView and
// onDestroyView. // onDestroyView.
private val binding get() = _binding!! private val binding get() = _binding!!
override fun onCreateView( override fun onCreateView(inflater: LayoutInflater, container: ViewGroup?, savedInstanceState: Bundle?): View {
inflater: LayoutInflater, ViewModelProvider(this)[CompressingViewModel::class.java]
container: ViewGroup?,
savedInstanceState: Bundle?
): View {
val homeViewModel =
ViewModelProvider(this).get(CompressingViewModel::class.java)
_binding = FragmentCompressingBinding.inflate(inflater, container, false) _binding = FragmentCompressingBinding.inflate(inflater, container, false)
val root: View = binding.root
val textView: TextView = binding.textCompressing
homeViewModel.text.observe(viewLifecycleOwner) { val compressingRecycler = binding.root.findViewById<View>(R.id.compressing_recycler_view) as? RecyclerView
textView.text = it compressingRecycler?.adapter = MainActivity.adapter
} compressingRecycler?.layoutManager = LinearLayoutManager(binding.root.context)
return root
return binding.root
} }
override fun onDestroyView() { override fun onDestroyView() {

View File

@ -0,0 +1,5 @@
package com.example.myapplication.ui.compressing
import java.util.Date
data class CompressingItem(val filename: String, val progress: Double, val date: Date)

View File

@ -1,9 +1,12 @@
package com.example.myapplication.ui.settings package com.example.myapplication.ui.settings
import android.os.Bundle import android.os.Bundle
import android.text.Editable
import android.text.TextWatcher
import android.view.LayoutInflater import android.view.LayoutInflater
import android.view.View import android.view.View
import android.view.ViewGroup import android.view.ViewGroup
import android.widget.Toast
import androidx.fragment.app.Fragment import androidx.fragment.app.Fragment
import androidx.lifecycle.ViewModelProvider import androidx.lifecycle.ViewModelProvider
import com.example.myapplication.databinding.FragmentSettingsBinding import com.example.myapplication.databinding.FragmentSettingsBinding
@ -22,12 +25,69 @@ class SettingsFragment : Fragment() {
container: ViewGroup?, container: ViewGroup?,
savedInstanceState: Bundle? savedInstanceState: Bundle?
): View { ): View {
val homeViewModel = val homeViewModel = ViewModelProvider(this)[SettingsViewModel::class.java]
ViewModelProvider(this)[SettingsViewModel::class.java]
_binding = FragmentSettingsBinding.inflate(inflater, container, false) _binding = FragmentSettingsBinding.inflate(inflater, container, false)
val root: View = binding.root val root: View = binding.root
//sets the textbox visibility on startup
binding.settingCustomSizeVideoText.visibility = View.INVISIBLE
//updates size when size selection changes
binding.settingRadioGroup.setOnCheckedChangeListener{ group,checkedID ->
if(checkedID == binding.settingDefaultSizeVideo.id){
homeViewModel.size.value = 8.0
}else if(checkedID == binding.settingBigSizeVideo.id){
homeViewModel.size.value = 50.0
}else if(checkedID == binding.settingHugeSize.id){
homeViewModel.size.value = 500.0
}else{
try{
homeViewModel.size.value = binding.settingCustomSizeVideoText.text.toString().toDouble()
}catch (e: NumberFormatException){
homeViewModel.size.value = 0.0
}
}
Toast.makeText(root.context,"" + homeViewModel.size.value,Toast.LENGTH_SHORT).show()
}
//updates size when the custom value changes
binding.settingCustomSizeVideoText.addTextChangedListener ( object: TextWatcher {
override fun afterTextChanged(s: Editable) {}
override fun beforeTextChanged(s: CharSequence, start: Int,
count: Int, after: Int) {
}
override fun onTextChanged(s: CharSequence, start: Int,
before: Int, count: Int) {
try{
homeViewModel.size.value = s.toString().toDouble()
}catch (e: NumberFormatException){
homeViewModel.size.value = 0.0
}
Toast.makeText(root.context,"" + homeViewModel.size.value,Toast.LENGTH_SHORT).show()
}
}
)
//listens for changes in the custom radiobutton and hides or shows the textbox
binding.settingCustomSizeVideo.setOnCheckedChangeListener{ _, isChecked ->
if (isChecked){
binding.settingCustomSizeVideoText.visibility = View.VISIBLE
}else{
binding.settingCustomSizeVideoText.visibility = View.INVISIBLE
}
}
// val textView: TextView = binding.textSettings // val textView: TextView = binding.textSettings
// homeViewModel.text.observe(viewLifecycleOwner) { // homeViewModel.text.observe(viewLifecycleOwner) {
// textView.text = it // textView.text = it

View File

@ -10,4 +10,10 @@ class SettingsViewModel : ViewModel() {
value = "This is settings Fragment" value = "This is settings Fragment"
} }
val text: LiveData<String> = _text val text: LiveData<String> = _text
//size of the file, grab this for use
private val _size = MutableLiveData<Double>().apply {
value = 8.0
}
val size: MutableLiveData<Double> = _size
} }

View File

@ -6,17 +6,13 @@
android:layout_height="match_parent" android:layout_height="match_parent"
tools:context=".ui.settings.SettingsFragment"> tools:context=".ui.settings.SettingsFragment">
<TextView <androidx.recyclerview.widget.RecyclerView
android:id="@+id/text_compressing" android:id="@+id/compressing_recycler_view"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="match_parent"
android:layout_marginStart="8dp" android:layout_marginStart="16dp"
android:layout_marginTop="8dp" android:layout_marginEnd="16dp"
android:layout_marginEnd="8dp"
android:textAlignment="center"
android:textSize="20sp"
app:layout_constraintBottom_toBottomOf="parent"
app:layout_constraintEnd_toEndOf="parent" app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent" app:layout_constraintStart_toStartOf="parent" />
app:layout_constraintTop_toTopOf="parent" />
</androidx.constraintlayout.widget.ConstraintLayout> </androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -10,6 +10,7 @@
android:layout_height="match_parent"> android:layout_height="match_parent">
<RadioGroup <RadioGroup
android:id="@+id/setting_radio_group"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content"> android:layout_height="wrap_content">
@ -23,6 +24,7 @@
android:id="@+id/setting_default_size_video" android:id="@+id/setting_default_size_video"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="wrap_content" android:layout_height="wrap_content"
android:checked="true"
android:text="@string/settings_default_size" /> android:text="@string/settings_default_size" />
<RadioButton <RadioButton

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<LinearLayout
xmlns:android="http://schemas.android.com/apk/res/android"
android:orientation="horizontal"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingTop="10dp"
android:paddingBottom="10dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical">
<TextView
android:id="@+id/compressing_filename"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
<TextView
android:id="@+id/compressing_date"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>
<ProgressBar
android:id="@+id/compressing_progress"
android:layout_width="wrap_content"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -6,5 +6,15 @@
android:id="@+id/add" android:id="@+id/add"
android:title="Add" android:title="Add"
android:icon="@drawable/ic_add" android:icon="@drawable/ic_add"
app:showAsAction="ifRoom" /> app:showAsAction="ifRoom" >
<menu>
<item
android:id="@+id/addFile"
android:title="@string/files" />
<item
android:id="@+id/addYoutube"
android:title="@string/youtube" />
</menu>
</item>
</menu> </menu>

View File

@ -10,4 +10,6 @@
<string name="settings_huge_size">500mb</string> <string name="settings_huge_size">500mb</string>
<string name="settings_custom_size">Custom</string> <string name="settings_custom_size">Custom</string>
<string name="settings_custom_size_hint">Enter Size in MB</string> <string name="settings_custom_size_hint">Enter Size in MB</string>
<string name="files">Files</string>
<string name="youtube">Youtube</string>
</resources> </resources>