wip
This commit is contained in:
@@ -127,6 +127,8 @@ dependencies {
|
|||||||
|
|
||||||
implementation "ru.noties.markwon:core:3.1.0"
|
implementation "ru.noties.markwon:core:3.1.0"
|
||||||
|
|
||||||
|
implementation "com.skyfishjy.ripplebackground:library:1.0.1"
|
||||||
|
|
||||||
implementation "org.jsoup:jsoup:1.14.3"
|
implementation "org.jsoup:jsoup:1.14.3"
|
||||||
|
|
||||||
implementation "xyz.quaver:documentfilex:0.7.2"
|
implementation "xyz.quaver:documentfilex:0.7.2"
|
||||||
|
|||||||
@@ -13,6 +13,12 @@
|
|||||||
<uses-permission android:name="android.permission.VIBRATE"/>
|
<uses-permission android:name="android.permission.VIBRATE"/>
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_SPECIAL_USE"/>
|
||||||
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
|
<uses-permission android:name="android.permission.NEARBY_WIFI_DEVICES" android:usesPermissionFlags="neverForLocation"
|
||||||
|
tools:targetApi="s" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_FINE_LOCATION" android:maxSdkVersion="32"
|
||||||
|
tools:ignore="CoarseFineLocation" />
|
||||||
|
<uses-permission android:name="android.permission.ACCESS_WIFI_STATE" />
|
||||||
|
<uses-permission android:name="android.permission.CHANGE_WIFI_STATE" />
|
||||||
|
|
||||||
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
<uses-feature android:name="android.hardware.camera" android:required="false" />
|
||||||
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
<uses-feature android:name="android.hardware.camera.autofocus" android:required="false" />
|
||||||
@@ -179,6 +185,7 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
</activity>
|
</activity>
|
||||||
<activity android:name="net.rdrei.android.dirchooser.DirectoryChooserActivity" />
|
<activity android:name="net.rdrei.android.dirchooser.DirectoryChooserActivity" />
|
||||||
|
<activity android:name=".ui.TransferActivity" />
|
||||||
</application>
|
</application>
|
||||||
|
|
||||||
</manifest>
|
</manifest>
|
||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package xyz.quaver.pupil.adapters
|
||||||
|
|
||||||
|
import android.net.wifi.p2p.WifiP2pDevice
|
||||||
|
import android.net.wifi.p2p.WifiP2pDeviceList
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.BaseAdapter
|
||||||
|
import androidx.recyclerview.widget.RecyclerView
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.databinding.TransferPeerListItemBinding
|
||||||
|
|
||||||
|
class TransferPeersAdapter(
|
||||||
|
private val devices: Collection<WifiP2pDevice>,
|
||||||
|
private val onDeviceSelected: (WifiP2pDevice) -> Unit
|
||||||
|
): RecyclerView.Adapter<TransferPeersAdapter.ViewHolder>() {
|
||||||
|
|
||||||
|
class ViewHolder(val binding: TransferPeerListItemBinding): RecyclerView.ViewHolder(binding.root)
|
||||||
|
|
||||||
|
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewHolder {
|
||||||
|
val binding = TransferPeerListItemBinding.inflate(
|
||||||
|
LayoutInflater.from(parent.context),
|
||||||
|
parent,
|
||||||
|
false
|
||||||
|
)
|
||||||
|
return ViewHolder(binding)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onBindViewHolder(holder: ViewHolder, position: Int) {
|
||||||
|
val device = devices.elementAt(position)
|
||||||
|
|
||||||
|
holder.binding.deviceName.text = device.deviceName
|
||||||
|
holder.binding.deviceAddress.text = device.deviceAddress
|
||||||
|
|
||||||
|
holder.binding.root.setOnClickListener {
|
||||||
|
onDeviceSelected(device)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun getItemCount(): Int {
|
||||||
|
return devices.size
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,59 @@
|
|||||||
|
package xyz.quaver.pupil.receiver
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.Context
|
||||||
|
import android.content.Intent
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.wifi.p2p.WifiP2pManager
|
||||||
|
import android.os.Build
|
||||||
|
import android.os.Parcelable
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import xyz.quaver.pupil.ui.ErrorType
|
||||||
|
import xyz.quaver.pupil.ui.TransferStep
|
||||||
|
import xyz.quaver.pupil.ui.TransferViewModel
|
||||||
|
|
||||||
|
private inline fun <reified T : Parcelable> Intent.getParcelableExtraCompat(key: String): T? = when {
|
||||||
|
Build.VERSION.SDK_INT >= 33 -> getParcelableExtra(key, T::class.java)
|
||||||
|
else -> @Suppress("DEPRECATION") getParcelableExtra(key) as? T
|
||||||
|
}
|
||||||
|
|
||||||
|
class WifiDirectBroadcastReceiver(
|
||||||
|
private val manager: WifiP2pManager,
|
||||||
|
private val channel: WifiP2pManager.Channel,
|
||||||
|
private val viewModel: TransferViewModel
|
||||||
|
): BroadcastReceiver() {
|
||||||
|
@SuppressLint("MissingPermission")
|
||||||
|
override fun onReceive(context: Context?, intent: Intent?) {
|
||||||
|
context!!
|
||||||
|
when (intent?.action) {
|
||||||
|
WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION -> {
|
||||||
|
val state = intent.getIntExtra(WifiP2pManager.EXTRA_WIFI_STATE, -1)
|
||||||
|
viewModel.setWifiP2pEnabled(state == WifiP2pManager.WIFI_P2P_STATE_ENABLED)
|
||||||
|
}
|
||||||
|
WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION -> {
|
||||||
|
manager.requestPeers(channel) { peers ->
|
||||||
|
viewModel.setPeers(peers)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION -> {
|
||||||
|
// Respond to new connection or disconnections
|
||||||
|
val networkInfo = intent.getParcelableExtraCompat<android.net.NetworkInfo>(WifiP2pManager.EXTRA_NETWORK_INFO)
|
||||||
|
|
||||||
|
if (networkInfo?.isConnected == true) {
|
||||||
|
manager.requestConnectionInfo(channel) { info ->
|
||||||
|
viewModel.setConnectionInfo(info)
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
viewModel.setConnectionInfo(null)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION -> {
|
||||||
|
// Respond to this device's wifi state changing
|
||||||
|
viewModel.setThisDevice(intent.getParcelableExtraCompat(WifiP2pManager.EXTRA_WIFI_P2P_DEVICE))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
294
app/src/main/java/xyz/quaver/pupil/ui/TransferActivity.kt
Normal file
294
app/src/main/java/xyz/quaver/pupil/ui/TransferActivity.kt
Normal file
@@ -0,0 +1,294 @@
|
|||||||
|
package xyz.quaver.pupil.ui
|
||||||
|
|
||||||
|
import android.Manifest
|
||||||
|
import android.annotation.SuppressLint
|
||||||
|
import android.content.BroadcastReceiver
|
||||||
|
import android.content.IntentFilter
|
||||||
|
import android.content.pm.ActivityInfo
|
||||||
|
import android.content.pm.PackageManager
|
||||||
|
import android.net.wifi.WpsInfo
|
||||||
|
import android.net.wifi.p2p.WifiP2pConfig
|
||||||
|
import android.net.wifi.p2p.WifiP2pDevice
|
||||||
|
import android.net.wifi.p2p.WifiP2pDeviceList
|
||||||
|
import android.net.wifi.p2p.WifiP2pInfo
|
||||||
|
import android.net.wifi.p2p.WifiP2pManager
|
||||||
|
import android.os.Build.VERSION
|
||||||
|
import android.os.Build.VERSION_CODES
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
|
import androidx.activity.viewModels
|
||||||
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
|
import androidx.core.app.ActivityCompat
|
||||||
|
import androidx.fragment.app.commit
|
||||||
|
import androidx.lifecycle.Lifecycle
|
||||||
|
import androidx.lifecycle.LiveData
|
||||||
|
import androidx.lifecycle.MutableLiveData
|
||||||
|
import androidx.lifecycle.ViewModel
|
||||||
|
import androidx.lifecycle.flowWithLifecycle
|
||||||
|
import androidx.lifecycle.lifecycleScope
|
||||||
|
import kotlinx.coroutines.flow.MutableStateFlow
|
||||||
|
import kotlinx.coroutines.flow.StateFlow
|
||||||
|
import kotlinx.coroutines.launch
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.receiver.WifiDirectBroadcastReceiver
|
||||||
|
import xyz.quaver.pupil.ui.fragment.TransferDirectionFragment
|
||||||
|
import xyz.quaver.pupil.ui.fragment.TransferPermissionFragment
|
||||||
|
import xyz.quaver.pupil.ui.fragment.TransferTargetFragment
|
||||||
|
import xyz.quaver.pupil.ui.fragment.TransferWaitForConnectionFragment
|
||||||
|
import kotlin.coroutines.resume
|
||||||
|
import kotlin.coroutines.resumeWithException
|
||||||
|
import kotlin.coroutines.suspendCoroutine
|
||||||
|
|
||||||
|
class TransferActivity : AppCompatActivity(R.layout.transfer_activity) {
|
||||||
|
|
||||||
|
private val viewModel: TransferViewModel by viewModels()
|
||||||
|
|
||||||
|
private val intentFilter = IntentFilter().apply {
|
||||||
|
addAction(WifiP2pManager.WIFI_P2P_STATE_CHANGED_ACTION)
|
||||||
|
addAction(WifiP2pManager.WIFI_P2P_PEERS_CHANGED_ACTION)
|
||||||
|
addAction(WifiP2pManager.WIFI_P2P_CONNECTION_CHANGED_ACTION)
|
||||||
|
addAction(WifiP2pManager.WIFI_P2P_THIS_DEVICE_CHANGED_ACTION)
|
||||||
|
}
|
||||||
|
|
||||||
|
private lateinit var manager: WifiP2pManager
|
||||||
|
private lateinit var channel: WifiP2pManager.Channel
|
||||||
|
|
||||||
|
private var receiver: BroadcastReceiver? = null
|
||||||
|
|
||||||
|
private val requestPermissionLauncher = registerForActivityResult(
|
||||||
|
ActivityResultContracts.RequestPermission()
|
||||||
|
) { granted ->
|
||||||
|
if (granted) {
|
||||||
|
viewModel.setStep(TransferStep.TARGET)
|
||||||
|
} else {
|
||||||
|
viewModel.setStep(TransferStep.PERMISSION)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun checkPermission(force: Boolean = false): Boolean {
|
||||||
|
val permissionRequired = if (VERSION.SDK_INT < VERSION_CODES.TIRAMISU) {
|
||||||
|
Manifest.permission.ACCESS_FINE_LOCATION
|
||||||
|
} else {
|
||||||
|
Manifest.permission.NEARBY_WIFI_DEVICES
|
||||||
|
}
|
||||||
|
|
||||||
|
val permissionGranted =
|
||||||
|
ActivityCompat.checkSelfPermission(this, permissionRequired) == PackageManager.PERMISSION_GRANTED
|
||||||
|
|
||||||
|
val shouldShowRationale =
|
||||||
|
ActivityCompat.shouldShowRequestPermissionRationale(this, permissionRequired)
|
||||||
|
|
||||||
|
if (!permissionGranted) {
|
||||||
|
if (shouldShowRationale && force) {
|
||||||
|
viewModel.setStep(TransferStep.PERMISSION)
|
||||||
|
} else {
|
||||||
|
requestPermissionLauncher.launch(permissionRequired)
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressLint("SourceLockedOrientationActivity")
|
||||||
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
|
super.onCreate(savedInstanceState)
|
||||||
|
supportActionBar?.hide()
|
||||||
|
requestedOrientation = ActivityInfo.SCREEN_ORIENTATION_PORTRAIT
|
||||||
|
|
||||||
|
manager = getSystemService(WIFI_P2P_SERVICE) as WifiP2pManager
|
||||||
|
channel = manager.initialize(this, mainLooper, null)
|
||||||
|
|
||||||
|
viewModel.peerToConnect.observe(this) { peer ->
|
||||||
|
if (peer == null) { return@observe }
|
||||||
|
if (!checkPermission()) { return@observe }
|
||||||
|
|
||||||
|
val config = WifiP2pConfig().apply {
|
||||||
|
deviceAddress = peer.deviceAddress
|
||||||
|
wps.setup = WpsInfo.PBC
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.connect(channel, config, object: WifiP2pManager.ActionListener {
|
||||||
|
override fun onSuccess() {
|
||||||
|
Log.d("PUPILD", "Connection successful")
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(reason: Int) {
|
||||||
|
Log.d("PUPILD", "Connection failed: $reason")
|
||||||
|
viewModel.setPeers(null)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.connectionInfo.observe(this) { info ->
|
||||||
|
if (info == null) { return@observe }
|
||||||
|
|
||||||
|
if (info.groupFormed && info.isGroupOwner) {
|
||||||
|
// Do something
|
||||||
|
Log.d("PUPILD", "Group formed and is group owner")
|
||||||
|
Log.d("PUPILD", "Group owner IP: ${info.groupOwnerAddress.hostAddress}")
|
||||||
|
} else if (info.groupFormed) {
|
||||||
|
// Do something
|
||||||
|
Log.d("PUPILD", "Group formed")
|
||||||
|
Log.d("PUPILD", "Group owner IP: ${info.groupOwnerAddress.hostAddress}")
|
||||||
|
Log.d("PUPILD", "Local IP: ${info.groupOwnerAddress.hostAddress}")
|
||||||
|
Log.d("PUPILD", "Is group owner: ${info.isGroupOwner}")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
lifecycleScope.launch {
|
||||||
|
viewModel.step.flowWithLifecycle(lifecycle, Lifecycle.State.STARTED).collect { step ->
|
||||||
|
when (step) {
|
||||||
|
TransferStep.TARGET,
|
||||||
|
TransferStep.TARGET_FORCE -> {
|
||||||
|
if (!checkPermission(step == TransferStep.TARGET_FORCE)) {
|
||||||
|
return@collect
|
||||||
|
}
|
||||||
|
|
||||||
|
manager.discoverPeers(channel, object: WifiP2pManager.ActionListener {
|
||||||
|
override fun onSuccess() { }
|
||||||
|
|
||||||
|
override fun onFailure(reason: Int) {
|
||||||
|
}
|
||||||
|
})
|
||||||
|
|
||||||
|
supportFragmentManager.commit {
|
||||||
|
replace(R.id.fragment_container_view, TransferTargetFragment())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TransferStep.DIRECTION -> {
|
||||||
|
supportFragmentManager.commit {
|
||||||
|
replace(R.id.fragment_container_view, TransferDirectionFragment())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TransferStep.PERMISSION -> {
|
||||||
|
supportFragmentManager.commit {
|
||||||
|
replace(R.id.fragment_container_view, TransferPermissionFragment())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
TransferStep.WAIT_FOR_CONNECTION -> {
|
||||||
|
if (!checkPermission()) { return@collect }
|
||||||
|
|
||||||
|
runCatching {
|
||||||
|
suspendCoroutine { continuation ->
|
||||||
|
manager.removeGroup(channel, object: WifiP2pManager.ActionListener {
|
||||||
|
override fun onSuccess() {
|
||||||
|
continuation.resume(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(reason: Int) {
|
||||||
|
continuation.resume(Unit)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
suspendCoroutine { continuation ->
|
||||||
|
manager.cancelConnect(channel, object: WifiP2pManager.ActionListener {
|
||||||
|
override fun onSuccess() {
|
||||||
|
continuation.resume(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(reason: Int) {
|
||||||
|
continuation.resume(Unit)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
suspendCoroutine { continuation ->
|
||||||
|
manager.createGroup(channel, object: WifiP2pManager.ActionListener {
|
||||||
|
override fun onSuccess() {
|
||||||
|
continuation.resume(Unit)
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onFailure(reason: Int) {
|
||||||
|
continuation.resumeWithException(Exception("Failed to create group $reason"))
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}.onFailure {
|
||||||
|
Log.e("PUPILD", "Failed to create group", it)
|
||||||
|
}
|
||||||
|
|
||||||
|
supportFragmentManager.commit {
|
||||||
|
replace(R.id.fragment_container_view, TransferWaitForConnectionFragment())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onResume() {
|
||||||
|
super.onResume()
|
||||||
|
WifiDirectBroadcastReceiver(manager, channel, viewModel).also {
|
||||||
|
receiver = it
|
||||||
|
registerReceiver(it, intentFilter)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onPause() {
|
||||||
|
super.onPause()
|
||||||
|
receiver?.let { unregisterReceiver(it) }
|
||||||
|
receiver = null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class TransferStep {
|
||||||
|
TARGET, TARGET_FORCE, DIRECTION, PERMISSION, WAIT_FOR_CONNECTION
|
||||||
|
}
|
||||||
|
|
||||||
|
enum class ErrorType {
|
||||||
|
}
|
||||||
|
|
||||||
|
class TransferViewModel : ViewModel() {
|
||||||
|
private val _step: MutableStateFlow<TransferStep> = MutableStateFlow(TransferStep.DIRECTION)
|
||||||
|
val step: StateFlow<TransferStep> = _step
|
||||||
|
|
||||||
|
private val _error = MutableLiveData<ErrorType?>(null)
|
||||||
|
val error: LiveData<ErrorType?> = _error
|
||||||
|
|
||||||
|
private val _wifiP2pEnabled: MutableLiveData<Boolean> = MutableLiveData(false)
|
||||||
|
val wifiP2pEnabled: LiveData<Boolean> = _wifiP2pEnabled
|
||||||
|
|
||||||
|
private val _thisDevice: MutableLiveData<WifiP2pDevice?> = MutableLiveData(null)
|
||||||
|
val thisDevice: LiveData<WifiP2pDevice?> = _thisDevice
|
||||||
|
|
||||||
|
private val _peers: MutableLiveData<WifiP2pDeviceList?> = MutableLiveData(null)
|
||||||
|
val peers: LiveData<WifiP2pDeviceList?> = _peers
|
||||||
|
|
||||||
|
private val _connectionInfo: MutableLiveData<WifiP2pInfo?> = MutableLiveData(null)
|
||||||
|
val connectionInfo: LiveData<WifiP2pInfo?> = _connectionInfo
|
||||||
|
|
||||||
|
private val _peerToConnect: MutableLiveData<WifiP2pDevice?> = MutableLiveData(null)
|
||||||
|
val peerToConnect: LiveData<WifiP2pDevice?> = _peerToConnect
|
||||||
|
|
||||||
|
fun setStep(step: TransferStep) {
|
||||||
|
_step.value = step
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setWifiP2pEnabled(enabled: Boolean) {
|
||||||
|
_wifiP2pEnabled.value = enabled
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setThisDevice(device: WifiP2pDevice?) {
|
||||||
|
_thisDevice.value = device
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setPeers(peers: WifiP2pDeviceList?) {
|
||||||
|
_peers.value = peers
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setConnectionInfo(info: WifiP2pInfo?) {
|
||||||
|
_connectionInfo.value = info
|
||||||
|
}
|
||||||
|
|
||||||
|
fun setError(error: ErrorType?) {
|
||||||
|
_error.value = error
|
||||||
|
}
|
||||||
|
|
||||||
|
fun connect(device: WifiP2pDevice) {
|
||||||
|
_peerToConnect.value = device
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -21,12 +21,14 @@ package xyz.quaver.pupil.ui.fragment
|
|||||||
import android.app.Activity
|
import android.app.Activity
|
||||||
import android.content.*
|
import android.content.*
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
import android.widget.Toast
|
import android.widget.Toast
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.appcompat.app.AppCompatDelegate
|
import androidx.appcompat.app.AppCompatDelegate
|
||||||
import androidx.preference.*
|
import androidx.preference.*
|
||||||
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
|
import com.google.android.gms.oss.licenses.OssLicensesMenuActivity
|
||||||
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
import com.google.firebase.crashlytics.FirebaseCrashlytics
|
||||||
|
import com.google.firebase.crashlytics.internal.model.CrashlyticsReport
|
||||||
import kotlinx.coroutines.CoroutineScope
|
import kotlinx.coroutines.CoroutineScope
|
||||||
import kotlinx.coroutines.Dispatchers
|
import kotlinx.coroutines.Dispatchers
|
||||||
import kotlinx.coroutines.launch
|
import kotlinx.coroutines.launch
|
||||||
@@ -40,6 +42,7 @@ import xyz.quaver.pupil.clientHolder
|
|||||||
import xyz.quaver.pupil.types.SendLogException
|
import xyz.quaver.pupil.types.SendLogException
|
||||||
import xyz.quaver.pupil.ui.LockActivity
|
import xyz.quaver.pupil.ui.LockActivity
|
||||||
import xyz.quaver.pupil.ui.SettingsActivity
|
import xyz.quaver.pupil.ui.SettingsActivity
|
||||||
|
import xyz.quaver.pupil.ui.TransferActivity
|
||||||
import xyz.quaver.pupil.ui.dialog.*
|
import xyz.quaver.pupil.ui.dialog.*
|
||||||
import xyz.quaver.pupil.util.*
|
import xyz.quaver.pupil.util.*
|
||||||
import xyz.quaver.pupil.util.downloader.DownloadManager
|
import xyz.quaver.pupil.util.downloader.DownloadManager
|
||||||
@@ -113,6 +116,9 @@ class SettingsFragment :
|
|||||||
)
|
)
|
||||||
Toast.makeText(context, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
|
Toast.makeText(context, R.string.copied_to_clipboard, Toast.LENGTH_SHORT).show()
|
||||||
}
|
}
|
||||||
|
"transfer_data" -> {
|
||||||
|
activity?.startActivity(Intent(activity, TransferActivity::class.java))
|
||||||
|
}
|
||||||
else -> return false
|
else -> return false
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -300,6 +306,9 @@ class SettingsFragment :
|
|||||||
true
|
true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
"transfer_data" -> {
|
||||||
|
onPreferenceClickListener = this@SettingsFragment
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,44 @@
|
|||||||
|
package xyz.quaver.pupil.ui.fragment
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.databinding.TransferDirectionFragmentBinding
|
||||||
|
import xyz.quaver.pupil.ui.TransferStep
|
||||||
|
import xyz.quaver.pupil.ui.TransferViewModel
|
||||||
|
|
||||||
|
class TransferDirectionFragment : Fragment(R.layout.transfer_direction_fragment) {
|
||||||
|
|
||||||
|
private var _binding: TransferDirectionFragmentBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
private val viewModel: TransferViewModel by activityViewModels()
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = TransferDirectionFragmentBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
binding.inButton.setOnClickListener {
|
||||||
|
viewModel.setStep(TransferStep.TARGET)
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.outButton.setOnClickListener {
|
||||||
|
viewModel.setStep(TransferStep.WAIT_FOR_CONNECTION)
|
||||||
|
}
|
||||||
|
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,38 @@
|
|||||||
|
package xyz.quaver.pupil.ui.fragment
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import xyz.quaver.pupil.databinding.TransferPermissionFragmentBinding
|
||||||
|
import xyz.quaver.pupil.ui.TransferStep
|
||||||
|
import xyz.quaver.pupil.ui.TransferViewModel
|
||||||
|
|
||||||
|
class TransferPermissionFragment: Fragment() {
|
||||||
|
|
||||||
|
private var _binding: TransferPermissionFragmentBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
private val viewModel: TransferViewModel by activityViewModels()
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = TransferPermissionFragmentBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
binding.permissionsButton.setOnClickListener {
|
||||||
|
viewModel.setStep(TransferStep.TARGET_FORCE)
|
||||||
|
}
|
||||||
|
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,69 @@
|
|||||||
|
package xyz.quaver.pupil.ui.fragment
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.util.Log
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import android.widget.ArrayAdapter
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import androidx.fragment.app.activityViewModels
|
||||||
|
import xyz.quaver.pupil.R
|
||||||
|
import xyz.quaver.pupil.adapters.TransferPeersAdapter
|
||||||
|
import xyz.quaver.pupil.databinding.TransferTargetFragmentBinding
|
||||||
|
import xyz.quaver.pupil.ui.TransferStep
|
||||||
|
import xyz.quaver.pupil.ui.TransferViewModel
|
||||||
|
|
||||||
|
class TransferTargetFragment : Fragment() {
|
||||||
|
|
||||||
|
private var _binding: TransferTargetFragmentBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
private val viewModel: TransferViewModel by activityViewModels()
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View {
|
||||||
|
_binding = TransferTargetFragmentBinding.inflate(inflater, container, false)
|
||||||
|
|
||||||
|
viewModel.thisDevice.observe(viewLifecycleOwner) { device ->
|
||||||
|
if (device == null) {
|
||||||
|
return@observe
|
||||||
|
}
|
||||||
|
|
||||||
|
if (device.status == 3) {
|
||||||
|
binding.ripple.startRippleAnimation()
|
||||||
|
binding.retryButton.visibility = View.INVISIBLE
|
||||||
|
} else {
|
||||||
|
binding.ripple.stopRippleAnimation()
|
||||||
|
binding.retryButton.visibility = View.VISIBLE
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
viewModel.peers.observe(viewLifecycleOwner) { peers ->
|
||||||
|
if (peers == null) {
|
||||||
|
return@observe
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.deviceList.adapter = TransferPeersAdapter(peers.deviceList) {
|
||||||
|
viewModel.connect(it)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
binding.ripple.startRippleAnimation()
|
||||||
|
|
||||||
|
binding.retryButton.setOnClickListener {
|
||||||
|
viewModel.setStep(TransferStep.TARGET)
|
||||||
|
}
|
||||||
|
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
@@ -0,0 +1,32 @@
|
|||||||
|
package xyz.quaver.pupil.ui.fragment
|
||||||
|
|
||||||
|
import android.os.Bundle
|
||||||
|
import android.view.LayoutInflater
|
||||||
|
import android.view.View
|
||||||
|
import android.view.ViewGroup
|
||||||
|
import androidx.fragment.app.Fragment
|
||||||
|
import xyz.quaver.pupil.databinding.TransferWaitForConnectionFragmentBinding
|
||||||
|
|
||||||
|
class TransferWaitForConnectionFragment : Fragment() {
|
||||||
|
|
||||||
|
private var _binding: TransferWaitForConnectionFragmentBinding? = null
|
||||||
|
private val binding get() = _binding!!
|
||||||
|
|
||||||
|
override fun onCreateView(
|
||||||
|
inflater: LayoutInflater,
|
||||||
|
container: ViewGroup?,
|
||||||
|
savedInstanceState: Bundle?
|
||||||
|
): View? {
|
||||||
|
_binding = TransferWaitForConnectionFragmentBinding.inflate(layoutInflater)
|
||||||
|
|
||||||
|
binding.ripple.startRippleAnimation()
|
||||||
|
|
||||||
|
return binding.root
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onDestroyView() {
|
||||||
|
super.onDestroyView()
|
||||||
|
_binding = null
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
13
app/src/main/res/drawable/arrow.xml
Normal file
13
app/src/main/res/drawable/arrow.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M4.5,10.5 L12,3m0,0 l7.5,7.5M12,3v18"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="@color/colorPrimaryDark"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
||||||
13
app/src/main/res/drawable/link.xml
Normal file
13
app/src/main/res/drawable/link.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M13.19,8.688a4.5,4.5 0,0 1,1.242 7.244l-4.5,4.5a4.5,4.5 0,0 1,-6.364 -6.364l1.757,-1.757m13.35,-0.622 l1.757,-1.757a4.5,4.5 0,0 0,-6.364 -6.364l-4.5,4.5a4.5,4.5 0,0 0,1.242 7.244"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="@color/colorPrimaryDark"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
||||||
13
app/src/main/res/drawable/round_button.xml
Normal file
13
app/src/main/res/drawable/round_button.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<selector xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:state_pressed="false">
|
||||||
|
<shape android:shape="oval">
|
||||||
|
<solid android:color="#dddddd"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
<item android:state_pressed="true">
|
||||||
|
<shape android:shape="oval">
|
||||||
|
<solid android:color="#888888"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</selector>
|
||||||
12
app/src/main/res/drawable/transfer_device.xml
Normal file
12
app/src/main/res/drawable/transfer_device.xml
Normal file
@@ -0,0 +1,12 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<layer-list xmlns:android="http://schemas.android.com/apk/res/android">
|
||||||
|
<item android:bottom="-8dp">
|
||||||
|
<shape xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:shape="rectangle">
|
||||||
|
<corners android:radius="32dp"
|
||||||
|
android:bottomLeftRadius="0dp"
|
||||||
|
android:bottomRightRadius="0dp" />
|
||||||
|
<stroke android:width="4dp" android:color="#444444"/>
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</layer-list>
|
||||||
9
app/src/main/res/drawable/transfer_ripple.xml
Normal file
9
app/src/main/res/drawable/transfer_ripple.xml
Normal file
@@ -0,0 +1,9 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<ripple xmlns:android="http://schemas.android.com/apk/res/android" android:color="#9d9d9d">
|
||||||
|
<item android:id="@android:id/mask">
|
||||||
|
<shape android:shape="rectangle">
|
||||||
|
<solid android:color="#9d9d9d" />
|
||||||
|
<corners android:radius="16dp" />
|
||||||
|
</shape>
|
||||||
|
</item>
|
||||||
|
</ripple>
|
||||||
13
app/src/main/res/drawable/warning.xml
Normal file
13
app/src/main/res/drawable/warning.xml
Normal file
@@ -0,0 +1,13 @@
|
|||||||
|
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:width="24dp"
|
||||||
|
android:height="24dp"
|
||||||
|
android:viewportWidth="24"
|
||||||
|
android:viewportHeight="24">
|
||||||
|
<path
|
||||||
|
android:pathData="M12,9v3.75m-9.303,3.376c-0.866,1.5 0.217,3.374 1.948,3.374h14.71c1.73,0 2.813,-1.874 1.948,-3.374L13.949,3.378c-0.866,-1.5 -3.032,-1.5 -3.898,0L2.697,16.126ZM12,15.75h0.007v0.008H12v-0.008Z"
|
||||||
|
android:strokeLineJoin="round"
|
||||||
|
android:strokeWidth="1.5"
|
||||||
|
android:fillColor="#00000000"
|
||||||
|
android:strokeColor="@color/colorAccent"
|
||||||
|
android:strokeLineCap="round"/>
|
||||||
|
</vector>
|
||||||
7
app/src/main/res/layout/transfer_activity.xml
Normal file
7
app/src/main/res/layout/transfer_activity.xml
Normal file
@@ -0,0 +1,7 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.fragment.app.FragmentContainerView
|
||||||
|
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:id="@+id/fragment_container_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
android:name="xyz.quaver.pupil.ui.fragment.TransferDirectionFragment" />
|
||||||
89
app/src/main/res/layout/transfer_direction_fragment.xml
Normal file
89
app/src/main/res/layout/transfer_direction_fragment.xml
Normal file
@@ -0,0 +1,89 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
|
||||||
|
android:layout_marginTop="80dp"
|
||||||
|
android:text="Transfer your data"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textSize="28sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/device"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="300dp"
|
||||||
|
android:layout_marginHorizontal="64dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
android:background="@drawable/transfer_device"/>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/out_button"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:background="@drawable/transfer_ripple"
|
||||||
|
app:layout_constraintBottom_toTopOf="@id/device"
|
||||||
|
app:layout_constraintLeft_toLeftOf="@id/device"
|
||||||
|
app:layout_constraintRight_toRightOf="@id/device"
|
||||||
|
android:layout_marginBottom="32dp">
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/out_arrow"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
app:srcCompat="@drawable/arrow"
|
||||||
|
android:layout_marginBottom="4dp" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/out_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Send data"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginTop="4dp"/>
|
||||||
|
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
<LinearLayout
|
||||||
|
android:id="@+id/in_button"
|
||||||
|
android:layout_width="100dp"
|
||||||
|
android:layout_height="100dp"
|
||||||
|
android:orientation="vertical"
|
||||||
|
android:gravity="center"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:background="@drawable/transfer_ripple"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/device"
|
||||||
|
app:layout_constraintLeft_toLeftOf="@id/device"
|
||||||
|
app:layout_constraintRight_toRightOf="@id/device"
|
||||||
|
android:layout_marginTop="32dp">
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/in_text"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Receive data"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/in_arrow"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
app:srcCompat="@drawable/arrow"
|
||||||
|
android:layout_marginTop="4dp"
|
||||||
|
android:rotation="180"/>
|
||||||
|
</LinearLayout>
|
||||||
|
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
32
app/src/main/res/layout/transfer_peer_list_item.xml
Normal file
32
app/src/main/res/layout/transfer_peer_list_item.xml
Normal file
@@ -0,0 +1,32 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:background="?attr/selectableItemBackground"
|
||||||
|
android:clickable="true"
|
||||||
|
android:focusable="true"
|
||||||
|
android:padding="16dp">
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/device_name"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Gonsales Vorecek's Galaxy S22 Ultra"
|
||||||
|
android:textStyle="bold"
|
||||||
|
android:textSize="24sp"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
android:layout_marginBottom="4dp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/device_address"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="26:7D:2D:3A:4F:5E"
|
||||||
|
android:textSize="16sp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/device_name"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
android:layout_marginTop="4dp"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
38
app/src/main/res/layout/transfer_permission_fragment.xml
Normal file
38
app/src/main/res/layout/transfer_permission_fragment.xml
Normal file
@@ -0,0 +1,38 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="48dp"
|
||||||
|
app:srcCompat="@drawable/warning"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="Permissions Missing"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textSize="28sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/icon" />
|
||||||
|
|
||||||
|
<com.google.android.material.button.MaterialButton
|
||||||
|
android:id="@+id/permissions_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Grant Permissions"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
android:layout_marginHorizontal="36dp"
|
||||||
|
android:layout_marginVertical="64dp"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
58
app/src/main/res/layout/transfer_target_fragment.xml
Normal file
58
app/src/main/res/layout/transfer_target_fragment.xml
Normal file
@@ -0,0 +1,58 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="48dp"
|
||||||
|
app:srcCompat="@drawable/link"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="Connect to your device"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textSize="28sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/icon" />
|
||||||
|
|
||||||
|
<androidx.recyclerview.widget.RecyclerView
|
||||||
|
android:id="@+id/device_list"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layoutManager="androidx.recyclerview.widget.LinearLayoutManager"
|
||||||
|
android:layout_marginHorizontal="32dp"
|
||||||
|
android:layout_marginVertical="32dp"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/textView"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent" />
|
||||||
|
|
||||||
|
<com.skyfishjy.library.RippleBackground
|
||||||
|
android:id="@+id/ripple"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:rb_color="@color/colorPrimaryDark"
|
||||||
|
app:rb_radius="32dp"
|
||||||
|
app:rb_type="strokeRipple"/>
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatButton
|
||||||
|
android:id="@+id/retry_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:text="Retry"
|
||||||
|
style="@style/Widget.AppCompat.Button.Borderless"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent"
|
||||||
|
android:visibility="invisible"
|
||||||
|
android:layout_margin="32dp"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent">
|
||||||
|
|
||||||
|
<androidx.appcompat.widget.AppCompatImageView
|
||||||
|
android:id="@+id/icon"
|
||||||
|
android:layout_width="48dp"
|
||||||
|
android:layout_height="48dp"
|
||||||
|
android:layout_marginTop="48dp"
|
||||||
|
app:srcCompat="@drawable/link"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintLeft_toLeftOf="parent"
|
||||||
|
app:layout_constraintRight_toRightOf="parent" />
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/textView"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="16dp"
|
||||||
|
android:text="Wait for connection"
|
||||||
|
android:textAlignment="center"
|
||||||
|
android:textSize="28sp"
|
||||||
|
android:textStyle="bold"
|
||||||
|
app:layout_constraintTop_toBottomOf="@id/icon" />
|
||||||
|
|
||||||
|
<com.skyfishjy.library.RippleBackground
|
||||||
|
android:id="@+id/ripple"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="0dp"
|
||||||
|
app:layout_constraintDimensionRatio="1"
|
||||||
|
app:layout_constraintTop_toTopOf="@id/barrier"
|
||||||
|
app:layout_constraintBottom_toBottomOf="@id/barrier"
|
||||||
|
app:rb_color="@color/colorPrimaryDark"
|
||||||
|
app:rb_radius="32dp"
|
||||||
|
app:rb_type="strokeRipple"/>
|
||||||
|
|
||||||
|
<androidx.constraintlayout.widget.Barrier
|
||||||
|
android:id="@+id/barrier"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
app:barrierDirection="top"
|
||||||
|
app:constraint_referenced_ids="device" />
|
||||||
|
|
||||||
|
<View
|
||||||
|
android:id="@+id/device"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="300dp"
|
||||||
|
android:layout_marginHorizontal="64dp"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
|
android:background="@drawable/transfer_device"/>
|
||||||
|
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
||||||
@@ -159,4 +159,5 @@
|
|||||||
<string name="unaccessible_download_folder">アンドロイド11以上では外部からのアプリ内部空間接近が不可能です。ダウンロードフォルダを変更しますか?</string>
|
<string name="unaccessible_download_folder">アンドロイド11以上では外部からのアプリ内部空間接近が不可能です。ダウンロードフォルダを変更しますか?</string>
|
||||||
<string name="settings_networking">ネットワーク</string>
|
<string name="settings_networking">ネットワーク</string>
|
||||||
<string name="settings_recover_downloads">ダウンロードデータベースを再構築</string>
|
<string name="settings_recover_downloads">ダウンロードデータベースを再構築</string>
|
||||||
|
<string name="settings_transfer_data">他の機器にデータを転送</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -159,4 +159,5 @@
|
|||||||
<string name="unaccessible_download_folder">안드로이드 11 이상에서는 외부에서 현재 다운로드 폴더에 접근할 수 없습니다. 변경하시겠습니까?</string>
|
<string name="unaccessible_download_folder">안드로이드 11 이상에서는 외부에서 현재 다운로드 폴더에 접근할 수 없습니다. 변경하시겠습니까?</string>
|
||||||
<string name="settings_networking">네트워크</string>
|
<string name="settings_networking">네트워크</string>
|
||||||
<string name="settings_recover_downloads">다운로드 데이터베이스 복구</string>
|
<string name="settings_recover_downloads">다운로드 데이터베이스 복구</string>
|
||||||
|
<string name="settings_transfer_data">다른 기기에 데이터 전송</string>
|
||||||
</resources>
|
</resources>
|
||||||
@@ -174,6 +174,7 @@
|
|||||||
<string name="settings_nomedia_title">Hide image from gallery</string>
|
<string name="settings_nomedia_title">Hide image from gallery</string>
|
||||||
<string name="settings_low_quality">Low quality images</string>
|
<string name="settings_low_quality">Low quality images</string>
|
||||||
<string name="settings_low_quality_summary">Load low quality images to improve load speed and data usage</string>
|
<string name="settings_low_quality_summary">Load low quality images to improve load speed and data usage</string>
|
||||||
|
<string name="settings_transfer_data">Transfer data to another device</string>
|
||||||
|
|
||||||
<!-- SETTINGS/APP LOCK -->
|
<!-- SETTINGS/APP LOCK -->
|
||||||
|
|
||||||
|
|||||||
@@ -52,6 +52,10 @@
|
|||||||
app:defaultValue="8"
|
app:defaultValue="8"
|
||||||
app:useSimpleSummaryProvider="true"/>
|
app:useSimpleSummaryProvider="true"/>
|
||||||
|
|
||||||
|
<Preference
|
||||||
|
app:key="transfer_data"
|
||||||
|
app:title="@string/settings_transfer_data"/>
|
||||||
|
|
||||||
<SwitchPreferenceCompat
|
<SwitchPreferenceCompat
|
||||||
app:key="nomedia"
|
app:key="nomedia"
|
||||||
app:title="@string/settings_nomedia_title"/>
|
app:title="@string/settings_nomedia_title"/>
|
||||||
|
|||||||
Reference in New Issue
Block a user