fix: email launch and tunnel import mobile
This commit is contained in:
parent
e11f0f794a
commit
7fdd95ea51
|
@ -133,8 +133,6 @@ android {
|
||||||
packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } }
|
packaging { resources { excludes += "/META-INF/{AL2.0,LGPL2.1}" } }
|
||||||
}
|
}
|
||||||
|
|
||||||
val generalImplementation by configurations
|
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
|
||||||
implementation(project(":logcatter"))
|
implementation(project(":logcatter"))
|
||||||
|
|
|
@ -60,6 +60,7 @@ import com.zaneschepke.wireguardautotunnel.ui.screens.support.SupportScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.screens.support.logs.LogsScreen
|
import com.zaneschepke.wireguardautotunnel.ui.screens.support.logs.LogsScreen
|
||||||
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
|
import com.zaneschepke.wireguardautotunnel.ui.theme.WireguardAutoTunnelTheme
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.requestAutoTunnelTileServiceUpdate
|
||||||
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
import com.zaneschepke.wireguardautotunnel.util.extensions.requestTunnelTileServiceStateUpdate
|
||||||
import dagger.hilt.android.AndroidEntryPoint
|
import dagger.hilt.android.AndroidEntryPoint
|
||||||
import javax.inject.Inject
|
import javax.inject.Inject
|
||||||
|
@ -103,6 +104,12 @@ class MainActivity : AppCompatActivity() {
|
||||||
context.requestTunnelTileServiceStateUpdate()
|
context.requestTunnelTileServiceStateUpdate()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
with(appUiState.settings) {
|
||||||
|
LaunchedEffect(isAutoTunnelPaused, isAutoTunnelEnabled) {
|
||||||
|
this@MainActivity.requestAutoTunnelTileServiceUpdate()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
CompositionLocalProvider(LocalNavController provides navController) {
|
CompositionLocalProvider(LocalNavController provides navController) {
|
||||||
SnackbarControllerProvider { host ->
|
SnackbarControllerProvider { host ->
|
||||||
WireguardAutoTunnelTheme {
|
WireguardAutoTunnelTheme {
|
||||||
|
|
|
@ -10,6 +10,7 @@ import androidx.activity.compose.rememberLauncherForActivityResult
|
||||||
import androidx.activity.result.contract.ActivityResultContracts
|
import androidx.activity.result.contract.ActivityResultContracts
|
||||||
import androidx.compose.runtime.Composable
|
import androidx.compose.runtime.Composable
|
||||||
import com.zaneschepke.wireguardautotunnel.util.Constants
|
import com.zaneschepke.wireguardautotunnel.util.Constants
|
||||||
|
import com.zaneschepke.wireguardautotunnel.util.extensions.isRunningOnTv
|
||||||
|
|
||||||
@Composable
|
@Composable
|
||||||
fun rememberFileImportLauncherForResult(onNoFileExplorer: () -> Unit, onData: (data: Uri) -> Unit): ManagedActivityResultLauncher<String, Uri?> {
|
fun rememberFileImportLauncherForResult(onNoFileExplorer: () -> Unit, onData: (data: Uri) -> Unit): ManagedActivityResultLauncher<String, Uri?> {
|
||||||
|
@ -17,7 +18,11 @@ fun rememberFileImportLauncherForResult(onNoFileExplorer: () -> Unit, onData: (d
|
||||||
object : ActivityResultContracts.GetContent() {
|
object : ActivityResultContracts.GetContent() {
|
||||||
override fun createIntent(context: Context, input: String): Intent {
|
override fun createIntent(context: Context, input: String): Intent {
|
||||||
val intent = super.createIntent(context, input).apply {
|
val intent = super.createIntent(context, input).apply {
|
||||||
type = Constants.ALLOWED_FILE_TYPES
|
type = if (context.isRunningOnTv()) {
|
||||||
|
Constants.ALLOWED_TV_FILE_TYPES
|
||||||
|
} else {
|
||||||
|
Constants.ALL_FILE_TYPES
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/* AndroidTV now comes with stubs that do nothing but display a Toast less helpful than
|
/* AndroidTV now comes with stubs that do nothing but display a Toast less helpful than
|
||||||
|
|
|
@ -31,7 +31,6 @@ import androidx.compose.runtime.setValue
|
||||||
import androidx.compose.ui.Alignment
|
import androidx.compose.ui.Alignment
|
||||||
import androidx.compose.ui.Modifier
|
import androidx.compose.ui.Modifier
|
||||||
import androidx.compose.ui.focus.FocusRequester
|
import androidx.compose.ui.focus.FocusRequester
|
||||||
import androidx.compose.ui.focus.focusRequester
|
|
||||||
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
import androidx.compose.ui.input.nestedscroll.nestedScroll
|
||||||
import androidx.compose.ui.input.pointer.pointerInput
|
import androidx.compose.ui.input.pointer.pointerInput
|
||||||
import androidx.compose.ui.platform.LocalContext
|
import androidx.compose.ui.platform.LocalContext
|
||||||
|
@ -181,7 +180,7 @@ fun MainScreen(viewModel: MainViewModel = hiltViewModel(), uiState: AppUiState,
|
||||||
TunnelImportSheet(
|
TunnelImportSheet(
|
||||||
showBottomSheet,
|
showBottomSheet,
|
||||||
onDismiss = { showBottomSheet = false },
|
onDismiss = { showBottomSheet = false },
|
||||||
onFileClick = { tunnelFileImportResultLauncher.launch(Constants.ALLOWED_FILE_TYPES) },
|
onFileClick = { tunnelFileImportResultLauncher.launch(Constants.ALLOWED_TV_FILE_TYPES) },
|
||||||
onQrClick = { launchQrScanner() },
|
onQrClick = { launchQrScanner() },
|
||||||
onManualImportClick = {
|
onManualImportClick = {
|
||||||
navController.navigate(
|
navController.navigate(
|
||||||
|
|
|
@ -37,8 +37,11 @@ fun TunnelStatisticsRow(statistics: TunnelStatistics?, tunnelConfig: TunnelConfi
|
||||||
val peerTxMB = NumberUtils.bytesToMB(peerTx).toThreeDecimalPlaceString()
|
val peerTxMB = NumberUtils.bytesToMB(peerTx).toThreeDecimalPlaceString()
|
||||||
val peerRxMB = NumberUtils.bytesToMB(peerRx).toThreeDecimalPlaceString()
|
val peerRxMB = NumberUtils.bytesToMB(peerRx).toThreeDecimalPlaceString()
|
||||||
val handshake = statistics?.peerStats(it.publicKey)?.latestHandshakeEpochMillis?.let {
|
val handshake = statistics?.peerStats(it.publicKey)?.latestHandshakeEpochMillis?.let {
|
||||||
if(it == 0L) stringResource(R.string.never) else
|
if (it == 0L) {
|
||||||
|
stringResource(R.string.never)
|
||||||
|
} else {
|
||||||
"${NumberUtils.getSecondsBetweenTimestampAndNow(it)} ${stringResource(R.string.sec)}"
|
"${NumberUtils.getSecondsBetweenTimestampAndNow(it)} ${stringResource(R.string.sec)}"
|
||||||
|
}
|
||||||
} ?: stringResource(R.string.never)
|
} ?: stringResource(R.string.never)
|
||||||
Column(
|
Column(
|
||||||
verticalArrangement = Arrangement.spacedBy(10.dp),
|
verticalArrangement = Arrangement.spacedBy(10.dp),
|
||||||
|
|
|
@ -22,7 +22,7 @@ fun PinLockScreen(appViewModel: AppViewModel) {
|
||||||
PinLock(
|
PinLock(
|
||||||
title = { pinExists ->
|
title = { pinExists ->
|
||||||
Text(
|
Text(
|
||||||
color = MaterialTheme.colorScheme.onSecondary,
|
color = MaterialTheme.colorScheme.onSurface,
|
||||||
text =
|
text =
|
||||||
if (pinExists) {
|
if (pinExists) {
|
||||||
stringResource(id = R.string.enter_pin)
|
stringResource(id = R.string.enter_pin)
|
||||||
|
@ -33,7 +33,8 @@ fun PinLockScreen(appViewModel: AppViewModel) {
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
},
|
},
|
||||||
color = MaterialTheme.colorScheme.secondary,
|
backgroundColor = MaterialTheme.colorScheme.surface,
|
||||||
|
textColor = MaterialTheme.colorScheme.onSurface,
|
||||||
onPinCorrect = {
|
onPinCorrect = {
|
||||||
// pin is correct, navigate or hide pin lock
|
// pin is correct, navigate or hide pin lock
|
||||||
if (context.isRunningOnTv()) {
|
if (context.isRunningOnTv()) {
|
||||||
|
|
|
@ -13,11 +13,11 @@ object Constants {
|
||||||
const val URI_CONTENT_SCHEME = "content"
|
const val URI_CONTENT_SCHEME = "content"
|
||||||
const val TEXT_MIME_TYPE = "text/plain"
|
const val TEXT_MIME_TYPE = "text/plain"
|
||||||
const val ZIP_FILE_MIME_TYPE = "application/zip"
|
const val ZIP_FILE_MIME_TYPE = "application/zip"
|
||||||
const val ALLOWED_FILE_TYPES = "${TEXT_MIME_TYPE}|${ZIP_FILE_MIME_TYPE}"
|
const val ALLOWED_TV_FILE_TYPES = "${TEXT_MIME_TYPE}|${ZIP_FILE_MIME_TYPE}"
|
||||||
|
const val ALL_FILE_TYPES = "*/*"
|
||||||
const val GOOGLE_TV_EXPLORER_STUB = "com.google.android.tv.frameworkpackagestubs"
|
const val GOOGLE_TV_EXPLORER_STUB = "com.google.android.tv.frameworkpackagestubs"
|
||||||
const val ANDROID_TV_EXPLORER_STUB = "com.android.tv.frameworkpackagestubs"
|
const val ANDROID_TV_EXPLORER_STUB = "com.android.tv.frameworkpackagestubs"
|
||||||
const val VPN_SETTINGS_PACKAGE = "android.net.vpn.SETTINGS"
|
const val VPN_SETTINGS_PACKAGE = "android.net.vpn.SETTINGS"
|
||||||
const val EMAIL_MIME_TYPE = "plain/text"
|
|
||||||
const val SYSTEM_EXEMPT_SERVICE_TYPE_ID = 1024
|
const val SYSTEM_EXEMPT_SERVICE_TYPE_ID = 1024
|
||||||
|
|
||||||
const val SUBSCRIPTION_TIMEOUT = 5_000L
|
const val SUBSCRIPTION_TIMEOUT = 5_000L
|
||||||
|
|
|
@ -12,8 +12,6 @@ import kotlinx.coroutines.CoroutineDispatcher
|
||||||
import kotlinx.coroutines.withContext
|
import kotlinx.coroutines.withContext
|
||||||
import timber.log.Timber
|
import timber.log.Timber
|
||||||
import java.io.File
|
import java.io.File
|
||||||
import java.io.FileInputStream
|
|
||||||
import java.io.FileOutputStream
|
|
||||||
import java.io.OutputStream
|
import java.io.OutputStream
|
||||||
import java.time.Instant
|
import java.time.Instant
|
||||||
import java.util.zip.ZipEntry
|
import java.util.zip.ZipEntry
|
||||||
|
@ -23,23 +21,6 @@ class FileUtils(
|
||||||
private val context: Context,
|
private val context: Context,
|
||||||
private val ioDispatcher: CoroutineDispatcher,
|
private val ioDispatcher: CoroutineDispatcher,
|
||||||
) {
|
) {
|
||||||
suspend fun readBytesFromFile(file: File): ByteArray {
|
|
||||||
return withContext(ioDispatcher) {
|
|
||||||
FileInputStream(file).use {
|
|
||||||
it.readBytes()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun readTextFromFileName(fileName: String): String {
|
|
||||||
return withContext(ioDispatcher) {
|
|
||||||
context.assets.open(fileName).use { stream ->
|
|
||||||
stream.bufferedReader(Charsets.UTF_8).use {
|
|
||||||
it.readText()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun createWgFiles(tunnels: TunnelConfigs): List<File> {
|
fun createWgFiles(tunnels: TunnelConfigs): List<File> {
|
||||||
return tunnels.map { config ->
|
return tunnels.map { config ->
|
||||||
|
@ -61,43 +42,6 @@ class FileUtils(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
suspend fun saveByteArrayToDownloads(content: ByteArray, fileName: String): Result<Unit> {
|
|
||||||
return withContext(ioDispatcher) {
|
|
||||||
try {
|
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
|
||||||
val contentValues =
|
|
||||||
ContentValues().apply {
|
|
||||||
put(MediaColumns.DISPLAY_NAME, fileName)
|
|
||||||
put(MediaColumns.MIME_TYPE, Constants.TEXT_MIME_TYPE)
|
|
||||||
put(MediaColumns.RELATIVE_PATH, Environment.DIRECTORY_DOWNLOADS)
|
|
||||||
}
|
|
||||||
val resolver = context.contentResolver
|
|
||||||
val uri =
|
|
||||||
resolver.insert(MediaStore.Downloads.EXTERNAL_CONTENT_URI, contentValues)
|
|
||||||
if (uri != null) {
|
|
||||||
resolver.openOutputStream(uri).use { output ->
|
|
||||||
output?.write(content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
val target =
|
|
||||||
File(
|
|
||||||
Environment.getExternalStoragePublicDirectory(
|
|
||||||
Environment.DIRECTORY_DOWNLOADS,
|
|
||||||
),
|
|
||||||
fileName,
|
|
||||||
)
|
|
||||||
FileOutputStream(target).use { output ->
|
|
||||||
output.write(content)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Result.success(Unit)
|
|
||||||
} catch (e: Exception) {
|
|
||||||
Result.failure(e)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
suspend fun saveFilesToZip(files: List<File>): Result<Unit> {
|
suspend fun saveFilesToZip(files: List<File>): Result<Unit> {
|
||||||
return withContext(ioDispatcher) {
|
return withContext(ioDispatcher) {
|
||||||
try {
|
try {
|
||||||
|
@ -124,7 +68,7 @@ class FileUtils(
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO issue with android 9
|
// TODO issue with android 9
|
||||||
private fun createDownloadsFileOutputStream(fileName: String, mimeType: String = Constants.ALLOWED_FILE_TYPES): OutputStream? {
|
private fun createDownloadsFileOutputStream(fileName: String, mimeType: String = Constants.ALL_FILE_TYPES): OutputStream? {
|
||||||
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.Q) {
|
||||||
val resolver = context.contentResolver
|
val resolver = context.contentResolver
|
||||||
val contentValues =
|
val contentValues =
|
||||||
|
|
|
@ -44,21 +44,21 @@ fun Context.showToast(resId: Int) {
|
||||||
).show()
|
).show()
|
||||||
}
|
}
|
||||||
|
|
||||||
fun Context.launchSupportEmail(): Result<Unit> {
|
fun Context.launchSupportEmail() {
|
||||||
return runCatching {
|
|
||||||
val intent =
|
val intent =
|
||||||
Intent(Intent.ACTION_SENDTO).apply {
|
Intent(Intent.ACTION_SENDTO).apply {
|
||||||
type = Constants.EMAIL_MIME_TYPE
|
data = Uri.parse("mailto:")
|
||||||
putExtra(Intent.EXTRA_EMAIL, arrayOf(getString(R.string.my_email)))
|
putExtra(Intent.EXTRA_EMAIL, arrayOf(getString(R.string.my_email)))
|
||||||
putExtra(Intent.EXTRA_SUBJECT, getString(R.string.email_subject))
|
putExtra(Intent.EXTRA_SUBJECT, getString(R.string.email_subject))
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
}
|
}
|
||||||
|
if (intent.resolveActivity(packageManager) != null) {
|
||||||
startActivity(
|
startActivity(
|
||||||
Intent.createChooser(intent, getString(R.string.email_chooser)).apply {
|
Intent.createChooser(intent, getString(R.string.email_chooser)).apply {
|
||||||
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
}.onFailure {
|
} else {
|
||||||
showToast(R.string.no_email_detected)
|
showToast(R.string.no_email_detected)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -17,7 +17,7 @@ kotlinx-serialization-json = "1.7.3"
|
||||||
lifecycle-runtime-compose = "2.8.6"
|
lifecycle-runtime-compose = "2.8.6"
|
||||||
material3 = "1.3.0"
|
material3 = "1.3.0"
|
||||||
navigationCompose = "2.8.2"
|
navigationCompose = "2.8.2"
|
||||||
pinLockCompose = "1.0.3"
|
pinLockCompose = "1.0.4"
|
||||||
roomVersion = "2.6.1"
|
roomVersion = "2.6.1"
|
||||||
timber = "5.0.1"
|
timber = "5.0.1"
|
||||||
tunnel = "1.2.1"
|
tunnel = "1.2.1"
|
||||||
|
|
Loading…
Reference in New Issue