From 40a3a9ba26152d363b4863ddf4d338663d80e847 Mon Sep 17 00:00:00 2001 From: Matthieu <24-artectrex@users.noreply.shinice.net> Date: Thu, 29 Aug 2024 18:17:10 +0200 Subject: [PATCH 1/4] Start tutorial --- app/build.gradle | 3 + .../org/pixeldroid/app/main/MainActivity.kt | 6 ++ .../app/settings/SettingsActivity.kt | 2 + .../app/settings/TutorialSettingsDialog.kt | 67 +++++++++++++++++++ app/src/main/res/drawable/help.xml | 5 ++ .../main/res/drawable/ic_home_white_24dp.xml | 4 +- app/src/main/res/drawable/notifications.xml | 4 +- app/src/main/res/drawable/photo_camera.xml | 4 +- app/src/main/res/values/strings.xml | 5 ++ app/src/main/res/xml/root_preferences.xml | 5 ++ pixel_common | 2 +- 11 files changed, 100 insertions(+), 7 deletions(-) create mode 100644 app/src/main/java/org/pixeldroid/app/settings/TutorialSettingsDialog.kt create mode 100644 app/src/main/res/drawable/help.xml diff --git a/app/build.gradle b/app/build.gradle index d383d43e..b6e6847c 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -226,6 +226,9 @@ dependencies { implementation 'com.davemorrissey.labs:subsampling-scale-image-view-androidx:3.10.0' + //Interactive tutorial + implementation 'com.getkeepsafe.taptargetview:taptargetview:1.14.0' + implementation 'com.google.android.material:material:1.12.0' //Dagger (dependency injection) diff --git a/app/src/main/java/org/pixeldroid/app/main/MainActivity.kt b/app/src/main/java/org/pixeldroid/app/main/MainActivity.kt index 3c882e5c..7c697ead 100644 --- a/app/src/main/java/org/pixeldroid/app/main/MainActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/main/MainActivity.kt @@ -66,6 +66,7 @@ import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedPostsFragment import org.pixeldroid.app.profile.ProfileActivity import org.pixeldroid.app.searchDiscover.SearchDiscoverFragment import org.pixeldroid.app.settings.SettingsActivity +import org.pixeldroid.app.settings.TutorialSettingsDialog.Companion.START_TUTORIAL import org.pixeldroid.app.utils.BaseActivity import org.pixeldroid.app.utils.Tab import org.pixeldroid.app.utils.api.objects.Notification @@ -128,9 +129,14 @@ class MainActivity : BaseActivity() { val showNotification: Boolean = intent.getBooleanExtra(SHOW_NOTIFICATION_TAG, false) + val showTutorial: Int = intent.getIntExtra(START_TUTORIAL, -1) + if(showNotification){ binding.viewPager.currentItem = 3 + } else if(showTutorial > 0) { + } + if (ActivityCompat.checkSelfPermission(applicationContext, Manifest.permission.POST_NOTIFICATIONS ) == PackageManager.PERMISSION_GRANTED diff --git a/app/src/main/java/org/pixeldroid/app/settings/SettingsActivity.kt b/app/src/main/java/org/pixeldroid/app/settings/SettingsActivity.kt index a4baff4f..be149b3e 100644 --- a/app/src/main/java/org/pixeldroid/app/settings/SettingsActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/settings/SettingsActivity.kt @@ -107,6 +107,8 @@ class SettingsActivity : ThemedActivity(), SharedPreferences.OnSharedPreferenceC dialogFragment = LanguageSettingFragment() } else if (preference.key == "arrange_tabs") { dialogFragment = ArrangeTabsFragment() + } else if (preference.key == "tutorial") { + dialogFragment = TutorialSettingsDialog() } if (dialogFragment != null) { dialogFragment.setTargetFragment(this, 0) diff --git a/app/src/main/java/org/pixeldroid/app/settings/TutorialSettingsDialog.kt b/app/src/main/java/org/pixeldroid/app/settings/TutorialSettingsDialog.kt new file mode 100644 index 00000000..4b93c572 --- /dev/null +++ b/app/src/main/java/org/pixeldroid/app/settings/TutorialSettingsDialog.kt @@ -0,0 +1,67 @@ +package org.pixeldroid.app.settings; + +import android.app.Dialog +import android.content.Context +import android.content.Intent +import android.graphics.Typeface +import android.os.Bundle +import android.view.LayoutInflater +import android.view.View +import android.view.ViewGroup +import android.widget.ArrayAdapter +import android.widget.TextView +import androidx.fragment.app.DialogFragment +import com.google.android.material.dialog.MaterialAlertDialogBuilder +import org.pixeldroid.app.R +import org.pixeldroid.app.main.MainActivity + +class TutorialSettingsDialog: DialogFragment() { + + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { + val items = arrayOf( + Pair(R.string.feeds_tutorial, R.drawable.ic_home_white_24dp), + Pair(R.string.create_tutorial, R.drawable.photo_camera), + Pair(R.string.feeds_tutorial, R.drawable.notifications), + Pair(R.string.feeds_tutorial, R.drawable.outline_bottom_navigation) + ) + + val adapter = object : ArrayAdapter>(requireContext(), android.R.layout.simple_list_item_1, items) { + override fun getView(position: Int, convertView: View?, parent: ViewGroup): View { + + val view: TextView = if (convertView == null) { + LayoutInflater.from(context).inflate(android.R.layout.simple_list_item_1, parent, false) as TextView + } else { + convertView as TextView + } + + val item = getItem(position) + + if (item != null) { + view.setText(item.first) + view.setTypeface(null, Typeface.NORMAL) // Set the typeface to normal + view.setCompoundDrawablesWithIntrinsicBounds(item.second, 0, 0, 0) + view.compoundDrawablePadding = 16 // Add padding between text and drawable + } + + view.setPadding(0, 32, 0, 32) + return view + } + } + return MaterialAlertDialogBuilder(requireContext()) + .setTitle(getString(R.string.tutorial_choice)) + .setAdapter(adapter) { _, which -> + val intent = Intent(requireContext(), MainActivity::class.java) + intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + intent.putExtra(START_TUTORIAL, which) + startActivity(intent) + } + .setNegativeButton(android.R.string.cancel) { dialog, _ -> + dialog.dismiss() + } + .create() + } + + companion object { + const val START_TUTORIAL = "tutorial_start_intent" + } +} \ No newline at end of file diff --git a/app/src/main/res/drawable/help.xml b/app/src/main/res/drawable/help.xml new file mode 100644 index 00000000..d1266fe9 --- /dev/null +++ b/app/src/main/res/drawable/help.xml @@ -0,0 +1,5 @@ + + + + + diff --git a/app/src/main/res/drawable/ic_home_white_24dp.xml b/app/src/main/res/drawable/ic_home_white_24dp.xml index 884766cc..ce574948 100644 --- a/app/src/main/res/drawable/ic_home_white_24dp.xml +++ b/app/src/main/res/drawable/ic_home_white_24dp.xml @@ -1,5 +1,5 @@ - - + \ No newline at end of file diff --git a/app/src/main/res/drawable/notifications.xml b/app/src/main/res/drawable/notifications.xml index 538f5818..f285f95e 100644 --- a/app/src/main/res/drawable/notifications.xml +++ b/app/src/main/res/drawable/notifications.xml @@ -1,5 +1,5 @@ - - + diff --git a/app/src/main/res/drawable/photo_camera.xml b/app/src/main/res/drawable/photo_camera.xml index 3e0e2d46..a2d0678a 100644 --- a/app/src/main/res/drawable/photo_camera.xml +++ b/app/src/main/res/drawable/photo_camera.xml @@ -1,6 +1,6 @@ - - + + \ No newline at end of file diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 971f0253..e6c03fdf 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -361,4 +361,9 @@ For more info about Pixelfed, you can check here: https://pixelfed.org" Target username Message you want to write. Say hello! Error sending your message! Check the username + Tutorial + Explanations on how to use PixelDroid and Pixelfed + Feeds, how do they work? Where do they come from? + A little walk through creating posts + What could you use some help with? diff --git a/app/src/main/res/xml/root_preferences.xml b/app/src/main/res/xml/root_preferences.xml index 346a5135..7355e284 100644 --- a/app/src/main/res/xml/root_preferences.xml +++ b/app/src/main/res/xml/root_preferences.xml @@ -54,6 +54,11 @@ + Date: Fri, 30 Aug 2024 15:30:37 +0200 Subject: [PATCH 2/4] Finished (?) tutorials --- app/build.gradle | 2 +- .../org/pixeldroid/app/main/MainActivity.kt | 225 +++++++++++++++++- .../app/postCreation/PostCreationActivity.kt | 122 ++++++++++ .../app/postCreation/camera/CameraFragment.kt | 7 + .../app/settings/ArrangeTabsFragment.kt | 83 ++++++- .../app/settings/SettingsActivity.kt | 55 ++++- .../app/settings/TutorialSettingsDialog.kt | 16 +- app/src/main/res/drawable/message.xml | 4 +- app/src/main/res/values/strings.xml | 2 + 9 files changed, 500 insertions(+), 16 deletions(-) diff --git a/app/build.gradle b/app/build.gradle index b6e6847c..4e0097c8 100644 --- a/app/build.gradle +++ b/app/build.gradle @@ -171,7 +171,7 @@ dependencies { implementation 'androidx.hilt:hilt-common:1.2.0' implementation 'androidx.hilt:hilt-work:1.2.0' - coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4' + coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.1.1' /** * AndroidX dependencies: diff --git a/app/src/main/java/org/pixeldroid/app/main/MainActivity.kt b/app/src/main/java/org/pixeldroid/app/main/MainActivity.kt index 7c697ead..bb6811ab 100644 --- a/app/src/main/java/org/pixeldroid/app/main/MainActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/main/MainActivity.kt @@ -12,7 +12,10 @@ import android.util.Log import android.view.Menu import android.view.MenuItem import android.view.View +import android.view.ViewGroup +import android.widget.ImageButton import android.widget.ImageView +import android.widget.TextView import androidx.activity.addCallback import androidx.activity.result.contract.ActivityResultContracts import androidx.activity.viewModels @@ -34,6 +37,8 @@ import androidx.viewpager2.adapter.FragmentStateAdapter import androidx.viewpager2.widget.ViewPager2 import androidx.viewpager2.widget.ViewPager2.OnPageChangeCallback import com.bumptech.glide.Glide +import com.getkeepsafe.taptargetview.TapTarget +import com.getkeepsafe.taptargetview.TapTargetView import com.google.android.material.color.DynamicColors import com.google.android.material.navigation.NavigationBarView import com.google.android.material.navigation.NavigationView @@ -50,6 +55,7 @@ import com.mikepenz.materialdrawer.model.interfaces.nameText import com.mikepenz.materialdrawer.util.AbstractDrawerImageLoader import com.mikepenz.materialdrawer.util.DrawerImageLoader import com.mikepenz.materialdrawer.widget.AccountHeaderView +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.ligi.tracedroid.sending.sendTraceDroidStackTracesIfExist import org.pixeldroid.app.R @@ -66,6 +72,7 @@ import org.pixeldroid.app.posts.feeds.uncachedFeeds.UncachedPostsFragment import org.pixeldroid.app.profile.ProfileActivity import org.pixeldroid.app.searchDiscover.SearchDiscoverFragment import org.pixeldroid.app.settings.SettingsActivity +import org.pixeldroid.app.settings.SettingsActivity.SettingsFragment import org.pixeldroid.app.settings.TutorialSettingsDialog.Companion.START_TUTORIAL import org.pixeldroid.app.utils.BaseActivity import org.pixeldroid.app.utils.Tab @@ -88,6 +95,7 @@ import java.time.Instant class MainActivity : BaseActivity() { + private lateinit var tabStored: List private lateinit var header: AccountHeaderView private var user: UserDatabaseEntity? = null @@ -128,13 +136,12 @@ class MainActivity : BaseActivity() { setupTabs() val showNotification: Boolean = intent.getBooleanExtra(SHOW_NOTIFICATION_TAG, false) - val showTutorial: Int = intent.getIntExtra(START_TUTORIAL, -1) if(showNotification){ binding.viewPager.currentItem = 3 - } else if(showTutorial > 0) { - + } else if(showTutorial >= 0) { + showTutorial(showTutorial) } if (ActivityCompat.checkSelfPermission(applicationContext, @@ -147,6 +154,216 @@ class MainActivity : BaseActivity() { } } + private fun showTutorial(showTutorial: Int) { + when(showTutorial){ + 0 -> tutorialOnTabs(Tab.HOME_FEED) + 1 -> tutorialOnTabs(Tab.CREATE_FEED) + 2 -> dmTutorial() + else -> return + } + } + + private fun tutorialOnTabs(tab: Tab) { + val target = (binding.tabs as? NavigationBarView)?.let{ findTab(it, tab) } ?: return //TODO tablet landscape not supported + + when(tab){ + Tab.HOME_FEED -> homeTutorial(target) + Tab.SEARCH_DISCOVER_FEED -> homeTutorialSearch(target) + Tab.PUBLIC_FEED -> homeTutorialPublic(target) + Tab.NOTIFICATIONS_FEED -> homeTutorialNotifications(target) + Tab.CREATE_FEED -> createTutorial(target) + else -> return + } + } + + private fun dmTutorial(){ + val target = (binding.tabs as? NavigationBarView)?.let{ findTab(it, Tab.DIRECT_MESSAGES) } ?: binding.mainDrawerButton ?: return //TODO tablet landscape not supported + + if(target is ImageButton) { + TapTargetView.showFor( + this@MainActivity, + TapTarget.forView(target, "First open the drawer menu") + .transparentTarget(true) + .targetRadius(60), // Specify the target radius (in dp) + object : TapTargetView.Listener() { + // The listener can listen for regular clicks, long clicks or cancels + override fun onTargetClick(view: TapTargetView?) { + super.onTargetClick(view) // This call is optional + // Perform action for the current target + target.performClick() + dmTutorial2(null) + } + }) + } else dmTutorial2(target) + } + private fun findViewWithText(root: ViewGroup, text: String?): View? { + for (i in 0 until root.childCount) { + val child = root.getChildAt(i) + if (child is TextView) { + if (child.text.toString().contains(text!!)) { + return child + } + } + if (child is ViewGroup) { + val result = findViewWithText(child, text) + if (result != null) { + return result + } + } + } + return null + } + + + private fun dmTutorial2(target: View?) { + lifecycleScope.launch { + var target = target ?: findViewWithText(binding.drawer as ViewGroup, getString(R.string.direct_messages)) + while (target == null) { + target = findViewWithText(binding.drawer as ViewGroup, getString(R.string.direct_messages)) + delay(100) + } + TapTargetView.showFor( + this@MainActivity, + TapTarget.forView(target, "Direct Messages", + "Send messages to other Pixelfed users: on your instance or on others") + .transparentTarget(true) + .targetRadius(60), // Specify the target radius (in dp) + object : TapTargetView.Listener() { + // The listener can listen for regular clicks, long clicks or cancels + override fun onTargetClick(view: TapTargetView?) { + super.onTargetClick(view) // This call is optional + // Perform action for the current target + target.performClick() + //tutorialOnTabs(Tab.NOTIFICATIONS_FEED) + } + }) + } + } + + private fun homeTutorialPublic(target: View) { + TapTargetView.showFor( + this@MainActivity, + TapTarget.forView(target, "Public feed", + "This feed contains all the posts on your instance! Maybe you can find some interesting posts here :)") + .transparentTarget(true) + .targetRadius(60), // Specify the target radius (in dp) + object : TapTargetView.Listener() { + // The listener can listen for regular clicks, long clicks or cancels + override fun onTargetClick(view: TapTargetView?) { + super.onTargetClick(view) // This call is optional + // Perform action for the current target + target.performClick() + tutorialOnTabs(Tab.NOTIFICATIONS_FEED) + } + }) + } + + private fun homeTutorialNotifications(target: View) { + TapTargetView.showFor( + this@MainActivity, + TapTarget.forView(target, "Notifications keep you in the loop", + "PixelDroid will also send you push notifications to make sure you don't miss anything!") + .transparentTarget(true) + .targetRadius(60), // Specify the target radius (in dp) + object : TapTargetView.Listener() { + // The listener can listen for regular clicks, long clicks or cancels + override fun onTargetClick(view: TapTargetView?) { + super.onTargetClick(view) // This call is optional + // Perform action for the current target + target.performClick() + } + }) + } + + private fun homeTutorialSearch(target: View) { + TapTargetView.showFor( + this@MainActivity, + TapTarget.forView(target, "This tab can get you started finding interesting accounts to follow", + "Maybe take a look at the trending posts \uD83D\uDCC8, or discover some random posts every day to broaden your horizons and find the real gems! \uD83D\uDC8E") + .transparentTarget(true) + .targetRadius(60), // Specify the target radius (in dp) + object : TapTargetView.Listener() { + // The listener can listen for regular clicks, long clicks or cancels + override fun onTargetClick(view: TapTargetView?) { + super.onTargetClick(view) // This call is optional + // Perform action for the current target + target.performClick() + tutorialOnTabs(Tab.PUBLIC_FEED) + } + }) + } + + private fun homeTutorial(target: View) { + TapTargetView.showFor( + this@MainActivity, + TapTarget.forView(target, "This is your home feed", + "The posts of the people you follow will show up here. No algorithms, just chronological goodness. Only you decide what you want to see!") + .transparentTarget(true) + .targetRadius(60), // Specify the target radius (in dp) + object : TapTargetView.Listener() { + // The listener can listen for regular clicks, long clicks or cancels + override fun onTargetClick(view: TapTargetView?) { + super.onTargetClick(view) // This call is optional + // Perform action for the current target + target.performClick() + tutorialOnTabs(Tab.SEARCH_DISCOVER_FEED) + } + }) + } + + private fun createTutorial(target: View) { + TapTargetView.showFor( + this@MainActivity, + TapTarget.forView(target, "This is where everything begins", + "First, let's navigate to the create tab. Click here") + .transparentTarget(true) + .targetRadius(60), // Specify the target radius (in dp) + object : TapTargetView.Listener() { + // The listener can listen for regular clicks, long clicks or cancels + override fun onTargetClick(view: TapTargetView?) { + super.onTargetClick(view) // This call is optional + // Perform action for the current target + target.performClick() + lifecycleScope.launch { + var targetCamera = findViewById(R.id.camera_capture_button) + while (targetCamera == null) { + targetCamera = findViewById(R.id.camera_capture_button) + delay(100) + } + TapTargetView.showFor( + this@MainActivity, + TapTarget.forView(targetCamera, "Take a picture to share", + "It doesn't have to be very good for now") + .transparentTarget(true) + .targetRadius(60), + object : TapTargetView.Listener() { + // The listener can listen for regular clicks, long clicks or cancels + override fun onTargetClick(view: TapTargetView?) { + super.onTargetClick(view) // This call is optional + targetCamera.performClick() + } + + override fun onTargetCancel(view: TapTargetView?) { + super.onTargetCancel(view) + intent.removeExtra(START_TUTORIAL) + } + }) + } + } + }) + } + + private fun findTab(navBar: NavigationBarView, tab: Tab): View? { + val index = tabStored.indexOf(tab) + for (i in 0 until navBar.childCount) { + val child = navBar.getChildAt(i) + if (child is ViewGroup) { + return child.getChildAt(index) + } + } + return null + } + private val notificationPermissionLauncher = registerForActivityResult( ActivityResultContracts.RequestPermission() ) { isGranted: Boolean -> @@ -521,6 +738,8 @@ class MainActivity : BaseActivity() { ) } + tabStored = tabs + val bottomNavigationMenu: Menu? = (binding.tabs as? NavigationBarView)?.menu?.apply { clear() } diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt index f0f251e1..f450cd2c 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt @@ -5,10 +5,19 @@ import android.content.Context import android.content.Intent import android.net.Uri import android.os.Bundle +import android.view.View +import androidx.appcompat.widget.Toolbar +import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment +import com.getkeepsafe.taptargetview.TapTarget +import com.getkeepsafe.taptargetview.TapTargetView +import com.google.android.material.appbar.MaterialToolbar +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import org.pixeldroid.app.R import org.pixeldroid.app.databinding.ActivityPostCreationBinding +import org.pixeldroid.app.settings.TutorialSettingsDialog.Companion.START_TUTORIAL import org.pixeldroid.app.utils.BaseActivity class PostCreationActivity : BaseActivity() { @@ -58,6 +67,119 @@ class PostCreationActivity : BaseActivity() { supportFragmentManager.findFragmentById(R.id.postCreationContainer) as NavHostFragment navController = navHostFragment.navController navController.setGraph(R.navigation.post_creation_graph) + + lifecycleScope.launch { + var targetCamera = findViewById(R.id.toggleStoryPost) + while (targetCamera == null) { + targetCamera = findViewById(R.id.toggleStoryPost) + delay(100) + } + TapTargetView.showFor( + this@PostCreationActivity, // `this` is an Activity + TapTarget.forView(targetCamera, "Story or Post?", + "Stories are short-lived: engage your followers and keep them coming back for more. Try them out!") + .transparentTarget(true) + .targetRadius(60), + object : TapTargetView.Listener() { + // The listener can listen for regular clicks, long clicks or cancels + override fun onTargetClick(view: TapTargetView?) { + super.onTargetClick(view) // This call is optional + findViewById(R.id.buttonStory)?.performClick() + TapTargetView.showFor( + this@PostCreationActivity, // `this` is an Activity + TapTarget.forView(findViewById(R.id.editPhotoButton), "Edit your picture to make it shine ✨", + "You can add filters, draw or add text, edit video, and more! \uD83D\uDCF7") + .transparentTarget(true) + .targetRadius(60), + object : TapTargetView.Listener() { + // The listener can listen for regular clicks, long clicks or cancels + override fun onTargetClick(view: TapTargetView?) { + super.onTargetClick(view) // This call is optional + findViewById(R.id.editPhotoButton)?.performClick() + TapTargetView.showFor( + this@PostCreationActivity, // `this` is an Activity + TapTarget.forView(findViewById(R.id.tv_caption), "Don't forget to add a media description!", + "This helps make Pixelfed accessible to everyone, and also lets you clarify what we're supposed to see in your pretty image ;)") + .transparentTarget(true) + .targetRadius(60), + object : TapTargetView.Listener() { + // The listener can listen for regular clicks, long clicks or cancels + override fun onTargetClick(view: TapTargetView?) { + super.onTargetClick(view) // This call is optional + findViewById(R.id.tv_caption)?.performClick() + lifecycleScope.launch { + delay(1000) + var tv_caption = findViewById(R.id.tv_caption) + while (tv_caption == null || tv_caption.visibility != View.VISIBLE) { + tv_caption = findViewById(R.id.tv_caption) + delay(100) + } + TapTargetView.showFor( + this@PostCreationActivity, // `this` is an Activity + TapTarget.forView(findViewById(R.id.post_creation_next_button), "Take a picture to share", + "It doesn't have to be very good for now") + .transparentTarget(true) + .targetRadius(60), + object : TapTargetView.Listener() { + // The listener can listen for regular clicks, long clicks or cancels + override fun onTargetClick(view: TapTargetView?) { + super.onTargetClick(view) // This call is optional + findViewById(R.id.post_creation_next_button)?.performClick() + showAccountChooser() + } + }) + } + } + }) + } + }) + } + }) + } + } + + private fun showAccountChooser() { + lifecycleScope.launch { + var toolbar = findViewById(R.id.top_bar) as? MaterialToolbar + while (toolbar == null) { + toolbar = findViewById(R.id.top_bar) as? MaterialToolbar + delay(100) + } + + TapTargetView.showFor( + this@PostCreationActivity, // `this` is an Activity + TapTarget.forToolbarMenuItem( + toolbar, + R.id.action_switch_accounts, + "Switch accounts!", + "PixelDroid supports using multiple Pixelfed accounts. Make sure you don't post those cat pics on the dog-only instance! \uD83D\uDE31" + ) + .transparentTarget(true) + .targetRadius(60), + object : TapTargetView.Listener() { + // The listener can listen for regular clicks, long clicks or cancels + override fun onTargetClick(view: TapTargetView?) { + super.onTargetClick(view) // This call is optional + showPostButton() + } + }) + } + } + + private fun showPostButton() { + TapTargetView.showFor( + this@PostCreationActivity, // `this` is an Activity + TapTarget.forView(findViewById(R.id.post_submission_send_button), "Final stretch! Post that picture", + "Have fun sharing your pictures with the world! Click anywhere else to cancel and keep looking around :)") + .transparentTarget(true) + .targetRadius(60), + object : TapTargetView.Listener() { + // The listener can listen for regular clicks, long clicks or cancels + override fun onTargetClick(view: TapTargetView?) { + super.onTargetClick(view) // This call is optional + findViewById(R.id.post_creation_next_button)?.performClick() + } + }) } override fun onSupportNavigateUp() = navController.navigateUp() || super.onSupportNavigateUp() diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/camera/CameraFragment.kt b/app/src/main/java/org/pixeldroid/app/postCreation/camera/CameraFragment.kt index a979573b..f8c247bf 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/camera/CameraFragment.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/camera/CameraFragment.kt @@ -37,6 +37,7 @@ import kotlinx.coroutines.Dispatchers import kotlinx.coroutines.launch import org.pixeldroid.app.databinding.FragmentCameraBinding import org.pixeldroid.app.postCreation.PostCreationActivity +import org.pixeldroid.app.settings.TutorialSettingsDialog.Companion.START_TUTORIAL import org.pixeldroid.app.utils.BaseFragment import java.io.File import java.util.concurrent.ExecutorService @@ -68,6 +69,7 @@ class CameraFragment : BaseFragment() { private var inActivity by Delegates.notNull() private var addToStory by Delegates.notNull() + private var tutorial by Delegates.notNull() private var filePermissionDialogLaunched: Boolean = false @@ -90,6 +92,8 @@ class CameraFragment : BaseFragment() { inActivity = arguments?.getBoolean(CAMERA_ACTIVITY) ?: false addToStory = arguments?.getBoolean(CAMERA_ACTIVITY_STORY) ?: false + tutorial = arguments?.getInt(START_TUTORIAL) ?: -1 + binding = FragmentCameraBinding.inflate(layoutInflater) return binding.root @@ -457,6 +461,9 @@ class CameraFragment : BaseFragment() { if(addToStory){ intent.putExtra(CAMERA_ACTIVITY_STORY, addToStory) } + if(!inActivity && tutorial != -1){ + intent.putExtra(START_TUTORIAL, true) + } startActivity(intent) } } diff --git a/app/src/main/java/org/pixeldroid/app/settings/ArrangeTabsFragment.kt b/app/src/main/java/org/pixeldroid/app/settings/ArrangeTabsFragment.kt index 41e3f90d..fd1f498e 100644 --- a/app/src/main/java/org/pixeldroid/app/settings/ArrangeTabsFragment.kt +++ b/app/src/main/java/org/pixeldroid/app/settings/ArrangeTabsFragment.kt @@ -16,13 +16,17 @@ import androidx.lifecycle.lifecycleScope import androidx.recyclerview.widget.ItemTouchHelper import androidx.recyclerview.widget.LinearLayoutManager import androidx.recyclerview.widget.RecyclerView +import com.getkeepsafe.taptargetview.TapTarget +import com.getkeepsafe.taptargetview.TapTargetView import com.google.android.material.bottomnavigation.BottomNavigationView import com.google.android.material.button.MaterialButton import com.google.android.material.checkbox.MaterialCheckBox import com.google.android.material.dialog.MaterialAlertDialogBuilder import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.pixeldroid.app.R +import org.pixeldroid.app.databinding.LayoutTabsArrangeBinding import org.pixeldroid.app.utils.Tab import org.pixeldroid.app.utils.db.AppDatabase import org.pixeldroid.app.utils.db.entities.TabsDatabaseEntity @@ -31,20 +35,23 @@ import javax.inject.Inject @AndroidEntryPoint class ArrangeTabsFragment: DialogFragment() { + private lateinit var binding: LayoutTabsArrangeBinding + @Inject lateinit var db: AppDatabase private val model: ArrangeTabsViewModel by viewModels() + var showTutorial = false + override fun onCreateDialog(savedInstanceState: Bundle?): Dialog { - val inflater: LayoutInflater = requireActivity().layoutInflater - val dialogView: View = inflater.inflate(R.layout.layout_tabs_arrange, null) + binding = LayoutTabsArrangeBinding.inflate(layoutInflater) val itemCount = model.initTabsChecked() model.initTabsButtons(itemCount, requireContext()) - val listFeed: RecyclerView = dialogView.findViewById(R.id.tabs) + val listFeed: RecyclerView = binding.tabs val listAdapter = ListViewAdapter(model) listFeed.adapter = listAdapter listFeed.layoutManager = LinearLayoutManager(requireActivity()) @@ -68,7 +75,7 @@ class ArrangeTabsFragment: DialogFragment() { val dialog = MaterialAlertDialogBuilder(requireContext()).apply { setIcon(R.drawable.outline_bottom_navigation) setTitle(R.string.arrange_tabs_summary) - setView(dialogView) + setView(binding.root) setNegativeButton(android.R.string.cancel) { _, _ -> } setPositiveButton(android.R.string.ok) { _, _ -> // Save values into preferences @@ -81,10 +88,76 @@ class ArrangeTabsFragment: DialogFragment() { } } }.create() - + if (showTutorial) showTutorial(dialog) return dialog } + private fun showTutorial(dialog: Dialog){ + lifecycleScope.launch { + var handle = binding.tabs.findViewHolderForLayoutPosition(0)?.itemView?.findViewById(R.id.dragHandle) + while (handle == null) { + handle = binding.tabs.findViewHolderForLayoutPosition(0)?.itemView?.findViewById(R.id.dragHandle) + delay(100) + } + TapTargetView.showFor( + dialog, + TapTarget.forView(handle, """Drag this to change the order of the tabs""") + .transparentTarget(true) + .targetRadius(60), // Specify the target radius (in dp) + object : TapTargetView.Listener() { + // The listener can listen for regular clicks, long clicks or cancels + override fun onTargetClick(view: TapTargetView?) { + super.onTargetClick(view) // This call is optional + // Perform action for the current target + val checkBox = binding.tabs.findViewHolderForLayoutPosition(0)?.itemView?.findViewById(R.id.checkBox) + TapTargetView.showFor( + dialog, + TapTarget.forView(checkBox, """De-activate tabs you don't need""") + .transparentTarget(true) + .targetRadius(60), // Specify the target radius (in dp) + object : TapTargetView.Listener() { + // The listener can listen for regular clicks, long clicks or cancels + override fun onTargetClick(view: TapTargetView?) { + super.onTargetClick(view) // This call is optional + // Perform action for the current target + val index = (Tab.defaultTabs + Tab.otherTabs).size - 1 + binding.tabs.scrollToPosition(index) + lifecycleScope.launch { + var hashtag = + binding.tabs.findViewHolderForLayoutPosition(index)?.itemView?.findViewById( + R.id.textView + ) + while (hashtag == null) { + hashtag = + binding.tabs.findViewHolderForLayoutPosition(index)?.itemView?.findViewById( + R.id.textView + ) + delay(100) + } + TapTargetView.showFor( + dialog, + TapTarget.forView( + hashtag, + """Create a custom feed with a hashtag you like""", + "You really like cats? Try #caturday! Lakes? #lake! Or #hiking? And it will be right there in a tab" + ) + .transparentTarget(true) + .targetRadius(60), // Specify the target radius (in dp) + object : TapTargetView.Listener() { + // The listener can listen for regular clicks, long clicks or cancels + override fun onTargetClick(view: TapTargetView?) { + super.onTargetClick(view) // This call is optional + // Perform action for the current target + } + }) + } + } + }) + } + }) + } + } + inner class ListViewAdapter(val model: ArrangeTabsViewModel): RecyclerView.Adapter() { diff --git a/app/src/main/java/org/pixeldroid/app/settings/SettingsActivity.kt b/app/src/main/java/org/pixeldroid/app/settings/SettingsActivity.kt index be149b3e..03163b7e 100644 --- a/app/src/main/java/org/pixeldroid/app/settings/SettingsActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/settings/SettingsActivity.kt @@ -4,35 +4,45 @@ import android.content.Intent import android.content.SharedPreferences import android.os.Build import android.os.Bundle +import android.view.View import androidx.activity.addCallback import androidx.appcompat.app.AppCompatDelegate import androidx.fragment.app.DialogFragment +import androidx.lifecycle.lifecycleScope import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat +import androidx.preference.PreferenceGroup import androidx.preference.PreferenceManager +import com.getkeepsafe.taptargetview.TapTarget +import com.getkeepsafe.taptargetview.TapTargetView import dagger.hilt.android.AndroidEntryPoint +import kotlinx.coroutines.delay +import kotlinx.coroutines.launch import org.pixeldroid.app.R import org.pixeldroid.app.databinding.SettingsBinding import org.pixeldroid.app.main.MainActivity import org.pixeldroid.app.utils.setThemeFromPreferences import org.pixeldroid.common.ThemedActivity + @AndroidEntryPoint class SettingsActivity : ThemedActivity(), SharedPreferences.OnSharedPreferenceChangeListener { private var restartMainOnExit = false + private lateinit var binding: SettingsBinding + override fun onCreate(savedInstanceState: Bundle?) { super.onCreate(savedInstanceState) - val binding = SettingsBinding.inflate(layoutInflater) + binding = SettingsBinding.inflate(layoutInflater) setContentView(binding.root) setSupportActionBar(binding.topBar) supportFragmentManager .beginTransaction() - .replace(R.id.settings, SettingsFragment()) + .replace(R.id.settings, SettingsFragment(), "topsettingsfragment") .commit() supportActionBar?.setDisplayHomeAsUpEnabled(true) @@ -98,6 +108,36 @@ class SettingsActivity : ThemedActivity(), SharedPreferences.OnSharedPreferenceC super.startActivity(intent) } + fun customTabsTutorial(){ + lifecycleScope.launch { + var target = + (supportFragmentManager.findFragmentByTag("topsettingsfragment") as? SettingsFragment)?.scrollToArrangeTabs() + while (target == null) { + target = (supportFragmentManager.findFragmentByTag("topsettingsfragment") as? SettingsFragment)?.scrollToArrangeTabs() + delay(100) + } + TapTargetView.showFor( + this@SettingsActivity, + TapTarget.forView(target, """First open the "Arrange tabs" settings""") + .transparentTarget(true) + .targetRadius(60), // Specify the target radius (in dp) + object : TapTargetView.Listener() { + // The listener can listen for regular clicks, long clicks or cancels + override fun onTargetClick(view: TapTargetView?) { + super.onTargetClick(view) // This call is optional + // Perform action for the current target + val dialogFragment = ArrangeTabsFragment().apply { showTutorial = true } + dialogFragment.setTargetFragment( + (supportFragmentManager.findFragmentByTag("topsettingsfragment") as? SettingsFragment), + 0 + ) + dialogFragment.show(supportFragmentManager, "settings_fragment") + } + }) + } + + } + class SettingsFragment : PreferenceFragmentCompat() { override fun onDisplayPreferenceDialog(preference: Preference) { var dialogFragment: DialogFragment? = null @@ -117,6 +157,17 @@ class SettingsActivity : ThemedActivity(), SharedPreferences.OnSharedPreferenceC super.onDisplayPreferenceDialog(preference) } } + fun scrollToArrangeTabs(): View? { + //Hardcoded because it's too annoying to find! + val position = 5 + + if (listView != null && position != -1) { + listView.post { + listView.smoothScrollToPosition(position) + } + } + return listView.findViewHolderForAdapterPosition(position)?.itemView + } override fun onCreatePreferences(savedInstanceState: Bundle?, rootKey: String?) { setPreferencesFromResource(R.xml.root_preferences, rootKey) diff --git a/app/src/main/java/org/pixeldroid/app/settings/TutorialSettingsDialog.kt b/app/src/main/java/org/pixeldroid/app/settings/TutorialSettingsDialog.kt index 4b93c572..e138cffe 100644 --- a/app/src/main/java/org/pixeldroid/app/settings/TutorialSettingsDialog.kt +++ b/app/src/main/java/org/pixeldroid/app/settings/TutorialSettingsDialog.kt @@ -1,7 +1,6 @@ package org.pixeldroid.app.settings; import android.app.Dialog -import android.content.Context import android.content.Intent import android.graphics.Typeface import android.os.Bundle @@ -11,9 +10,12 @@ import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.TextView import androidx.fragment.app.DialogFragment +import com.getkeepsafe.taptargetview.TapTarget +import com.getkeepsafe.taptargetview.TapTargetView import com.google.android.material.dialog.MaterialAlertDialogBuilder import org.pixeldroid.app.R import org.pixeldroid.app.main.MainActivity +import org.pixeldroid.app.utils.Tab class TutorialSettingsDialog: DialogFragment() { @@ -21,8 +23,8 @@ class TutorialSettingsDialog: DialogFragment() { val items = arrayOf( Pair(R.string.feeds_tutorial, R.drawable.ic_home_white_24dp), Pair(R.string.create_tutorial, R.drawable.photo_camera), - Pair(R.string.feeds_tutorial, R.drawable.notifications), - Pair(R.string.feeds_tutorial, R.drawable.outline_bottom_navigation) + Pair(R.string.dm_tutorial, R.drawable.message), + Pair(R.string.custom_tabs_tutorial, R.drawable.outline_bottom_navigation) ) val adapter = object : ArrayAdapter>(requireContext(), android.R.layout.simple_list_item_1, items) { @@ -50,6 +52,10 @@ class TutorialSettingsDialog: DialogFragment() { return MaterialAlertDialogBuilder(requireContext()) .setTitle(getString(R.string.tutorial_choice)) .setAdapter(adapter) { _, which -> + if(which == 3){ + customTabsTutorial() + return@setAdapter + } val intent = Intent(requireContext(), MainActivity::class.java) intent.flags = Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK intent.putExtra(START_TUTORIAL, which) @@ -61,6 +67,10 @@ class TutorialSettingsDialog: DialogFragment() { .create() } + private fun customTabsTutorial() { + (requireActivity() as SettingsActivity).customTabsTutorial() + } + companion object { const val START_TUTORIAL = "tutorial_start_intent" } diff --git a/app/src/main/res/drawable/message.xml b/app/src/main/res/drawable/message.xml index 5175b3f5..9006f264 100644 --- a/app/src/main/res/drawable/message.xml +++ b/app/src/main/res/drawable/message.xml @@ -1,5 +1,5 @@ - + - + diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index e6c03fdf..0b1ae1c5 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -366,4 +366,6 @@ For more info about Pixelfed, you can check here: https://pixelfed.org" Feeds, how do they work? Where do they come from? A little walk through creating posts What could you use some help with? + Direct Messages: keep in touch! + Customize what tabs show up on the main PixelDroid screen! From 212ae778fbf738af393bb4844e0c3f63783b4402 Mon Sep 17 00:00:00 2001 From: Matthieu <24-artectrex@users.noreply.shinice.net> Date: Fri, 30 Aug 2024 16:03:23 +0200 Subject: [PATCH 3/4] Add popup after login for the tutorial --- .../org/pixeldroid/app/login/LoginActivity.kt | 27 +++++++++++++++---- .../app/login/LoginActivityViewModel.kt | 16 ++++++++--- .../app/settings/SettingsActivity.kt | 24 +++++++++++++---- app/src/main/res/values/strings.xml | 2 ++ 4 files changed, 56 insertions(+), 13 deletions(-) diff --git a/app/src/main/java/org/pixeldroid/app/login/LoginActivity.kt b/app/src/main/java/org/pixeldroid/app/login/LoginActivity.kt index f62d3c20..a6b0869e 100644 --- a/app/src/main/java/org/pixeldroid/app/login/LoginActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/login/LoginActivity.kt @@ -5,6 +5,7 @@ import android.content.Intent import android.content.SharedPreferences import android.net.Uri import android.os.Bundle +import android.provider.Settings import android.view.View import android.view.inputmethod.EditorInfo import android.view.inputmethod.InputMethodManager @@ -20,6 +21,8 @@ import org.pixeldroid.app.BuildConfig import org.pixeldroid.app.R import org.pixeldroid.app.databinding.ActivityLoginBinding import org.pixeldroid.app.main.MainActivity +import org.pixeldroid.app.settings.SettingsActivity +import org.pixeldroid.app.settings.TutorialSettingsDialog.Companion.START_TUTORIAL import org.pixeldroid.app.utils.BaseActivity import org.pixeldroid.app.utils.api.PixelfedAPI import org.pixeldroid.app.utils.openUrl @@ -89,11 +92,25 @@ class LoginActivity : BaseActivity() { lifecycleScope.launch { repeatOnLifecycle(Lifecycle.State.STARTED) { model.finishedLogin.collectLatest { - if (it) { - val intent = Intent(this@LoginActivity, MainActivity::class.java) - intent.flags = - Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK - startActivity(intent) + when (it) { + LoginActivityViewModel.FinishedLogin.Finished -> { + val intent = Intent(this@LoginActivity, MainActivity::class.java) + intent.flags = + Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + startActivity(intent) + } + LoginActivityViewModel.FinishedLogin.FinishedFirstTime -> MaterialAlertDialogBuilder(binding.root.context) + .setMessage(R.string.first_time_question) + .setPositiveButton(android.R.string.ok) { _, _ -> + val intent = Intent(this@LoginActivity, SettingsActivity::class.java) + intent.flags = + Intent.FLAG_ACTIVITY_NEW_TASK or Intent.FLAG_ACTIVITY_CLEAR_TASK + intent.putExtra(START_TUTORIAL, true) + startActivity(intent) + } + .setNegativeButton(R.string.skip_tutorial) { _, _ -> model.finishLogin()} + .show() + LoginActivityViewModel.FinishedLogin.NotFinished -> {} } } } diff --git a/app/src/main/java/org/pixeldroid/app/login/LoginActivityViewModel.kt b/app/src/main/java/org/pixeldroid/app/login/LoginActivityViewModel.kt index 91f58a0a..e3b16083 100644 --- a/app/src/main/java/org/pixeldroid/app/login/LoginActivityViewModel.kt +++ b/app/src/main/java/org/pixeldroid/app/login/LoginActivityViewModel.kt @@ -50,7 +50,10 @@ class LoginActivityViewModel @Inject constructor( private val _loadingState: MutableStateFlow = MutableStateFlow(LoginState(LoginState.LoadingState.Resting)) val loadingState = _loadingState.asStateFlow() - private val _finishedLogin = MutableStateFlow(false) + enum class FinishedLogin { + NotFinished, Finished, FinishedFirstTime + } + private val _finishedLogin = MutableStateFlow(FinishedLogin.NotFinished) val finishedLogin = _finishedLogin.asStateFlow() private val _promptOauth: MutableStateFlow = MutableStateFlow(null) @@ -207,6 +210,7 @@ class LoginActivityViewModel @Inject constructor( private suspend fun storeUser(accessToken: String, refreshToken: String?, clientId: String, clientSecret: String, instance: String) { try { + val firstTime = db.userDao().getActiveUser() == null val user = pixelfedAPI.verifyCredentials("Bearer $accessToken") db.userDao().deActivateActiveUsers() addUser( @@ -220,12 +224,14 @@ class LoginActivityViewModel @Inject constructor( clientSecret = clientSecret ) apiHolder.setToCurrentUser() + + fetchNotifications() + + _finishedLogin.value = if(firstTime) FinishedLogin.FinishedFirstTime else FinishedLogin.Finished } catch (exception: Exception) { return failedRegistration(R.string.verify_credentials) } - fetchNotifications() - _finishedLogin.value = true } // Fetch the latest notifications of this account, to avoid launching old notifications @@ -280,4 +286,8 @@ class LoginActivityViewModel @Inject constructor( _loadingState.value = LoginState(LoginState.LoadingState.Resting) } + fun finishLogin() { + _finishedLogin.value = FinishedLogin.Finished + } + } \ No newline at end of file diff --git a/app/src/main/java/org/pixeldroid/app/settings/SettingsActivity.kt b/app/src/main/java/org/pixeldroid/app/settings/SettingsActivity.kt index 03163b7e..6557d4fc 100644 --- a/app/src/main/java/org/pixeldroid/app/settings/SettingsActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/settings/SettingsActivity.kt @@ -22,6 +22,7 @@ import kotlinx.coroutines.launch import org.pixeldroid.app.R import org.pixeldroid.app.databinding.SettingsBinding import org.pixeldroid.app.main.MainActivity +import org.pixeldroid.app.settings.TutorialSettingsDialog.Companion.START_TUTORIAL import org.pixeldroid.app.utils.setThemeFromPreferences import org.pixeldroid.common.ThemedActivity @@ -46,6 +47,20 @@ class SettingsActivity : ThemedActivity(), SharedPreferences.OnSharedPreferenceC .commit() supportActionBar?.setDisplayHomeAsUpEnabled(true) + val showTutorial = intent.getBooleanExtra(START_TUTORIAL, false) + + if(showTutorial){ + lifecycleScope.launch { + var target = + (supportFragmentManager.findFragmentByTag("topsettingsfragment") as? SettingsFragment)?.scrollToArrangeTabs(10) + while (target == null) { + target = (supportFragmentManager.findFragmentByTag("topsettingsfragment") as? SettingsFragment)?.scrollToArrangeTabs(10) + delay(100) + } + target.performClick() + } + } + onBackPressedDispatcher.addCallback(this /* lifecycle owner */) { // Handle the back button event // If a setting (for example language or theme) was changed, the main activity should be @@ -111,9 +126,9 @@ class SettingsActivity : ThemedActivity(), SharedPreferences.OnSharedPreferenceC fun customTabsTutorial(){ lifecycleScope.launch { var target = - (supportFragmentManager.findFragmentByTag("topsettingsfragment") as? SettingsFragment)?.scrollToArrangeTabs() + (supportFragmentManager.findFragmentByTag("topsettingsfragment") as? SettingsFragment)?.scrollToArrangeTabs(5) while (target == null) { - target = (supportFragmentManager.findFragmentByTag("topsettingsfragment") as? SettingsFragment)?.scrollToArrangeTabs() + target = (supportFragmentManager.findFragmentByTag("topsettingsfragment") as? SettingsFragment)?.scrollToArrangeTabs(5) delay(100) } TapTargetView.showFor( @@ -157,9 +172,8 @@ class SettingsActivity : ThemedActivity(), SharedPreferences.OnSharedPreferenceC super.onDisplayPreferenceDialog(preference) } } - fun scrollToArrangeTabs(): View? { - //Hardcoded because it's too annoying to find! - val position = 5 + fun scrollToArrangeTabs(position: Int): View? { + //Hardcoded positions because it's too annoying to find! if (listView != null && position != -1) { listView.post { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 0b1ae1c5..4887050c 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -368,4 +368,6 @@ For more info about Pixelfed, you can check here: https://pixelfed.org" What could you use some help with? Direct Messages: keep in touch! Customize what tabs show up on the main PixelDroid screen! + It seems like it might be your first time using PixelDroid. Do you want to open a tutorial? You can always find the tutorials in the settings. + No, continue From 4d158e47f715ad615a91c04a7e0e78300c2f6667 Mon Sep 17 00:00:00 2001 From: Matthieu <24-artectrex@users.noreply.shinice.net> Date: Fri, 30 Aug 2024 16:15:03 +0200 Subject: [PATCH 4/4] Stringify tutorials --- .../org/pixeldroid/app/main/MainActivity.kt | 34 +++++++++++-------- .../app/postCreation/PostCreationActivity.kt | 33 ++++++++++-------- .../app/settings/ArrangeTabsFragment.kt | 10 +++--- .../app/settings/SettingsActivity.kt | 3 +- app/src/main/res/values/strings.xml | 30 ++++++++++++++++ 5 files changed, 74 insertions(+), 36 deletions(-) diff --git a/app/src/main/java/org/pixeldroid/app/main/MainActivity.kt b/app/src/main/java/org/pixeldroid/app/main/MainActivity.kt index bb6811ab..a5a4f645 100644 --- a/app/src/main/java/org/pixeldroid/app/main/MainActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/main/MainActivity.kt @@ -182,7 +182,7 @@ class MainActivity : BaseActivity() { if(target is ImageButton) { TapTargetView.showFor( this@MainActivity, - TapTarget.forView(target, "First open the drawer menu") + TapTarget.forView(target, getString(R.string.dm_tutorial_drawer)) .transparentTarget(true) .targetRadius(60), // Specify the target radius (in dp) object : TapTargetView.Listener() { @@ -224,8 +224,8 @@ class MainActivity : BaseActivity() { } TapTargetView.showFor( this@MainActivity, - TapTarget.forView(target, "Direct Messages", - "Send messages to other Pixelfed users: on your instance or on others") + TapTarget.forView(target, getString(R.string.direct_messages), + getString(R.string.dm_tutorial_text)) .transparentTarget(true) .targetRadius(60), // Specify the target radius (in dp) object : TapTargetView.Listener() { @@ -243,8 +243,8 @@ class MainActivity : BaseActivity() { private fun homeTutorialPublic(target: View) { TapTargetView.showFor( this@MainActivity, - TapTarget.forView(target, "Public feed", - "This feed contains all the posts on your instance! Maybe you can find some interesting posts here :)") + TapTarget.forView(target, getString(R.string.public_feed), + getString(R.string.public_feed_tutorial_explanation)) .transparentTarget(true) .targetRadius(60), // Specify the target radius (in dp) object : TapTargetView.Listener() { @@ -261,8 +261,9 @@ class MainActivity : BaseActivity() { private fun homeTutorialNotifications(target: View) { TapTargetView.showFor( this@MainActivity, - TapTarget.forView(target, "Notifications keep you in the loop", - "PixelDroid will also send you push notifications to make sure you don't miss anything!") + TapTarget.forView(target, + getString(R.string.notifications_tutorial_title), + getString(R.string.notifications_tutorial_explanation)) .transparentTarget(true) .targetRadius(60), // Specify the target radius (in dp) object : TapTargetView.Listener() { @@ -278,8 +279,9 @@ class MainActivity : BaseActivity() { private fun homeTutorialSearch(target: View) { TapTargetView.showFor( this@MainActivity, - TapTarget.forView(target, "This tab can get you started finding interesting accounts to follow", - "Maybe take a look at the trending posts \uD83D\uDCC8, or discover some random posts every day to broaden your horizons and find the real gems! \uD83D\uDC8E") + TapTarget.forView(target, + getString(R.string.discover_tutorial_title), + getString(R.string.discover_tutorial_explanation)) .transparentTarget(true) .targetRadius(60), // Specify the target radius (in dp) object : TapTargetView.Listener() { @@ -296,8 +298,9 @@ class MainActivity : BaseActivity() { private fun homeTutorial(target: View) { TapTargetView.showFor( this@MainActivity, - TapTarget.forView(target, "This is your home feed", - "The posts of the people you follow will show up here. No algorithms, just chronological goodness. Only you decide what you want to see!") + TapTarget.forView(target, + getString(R.string.home_feed_tutorial_title), + getString(R.string.home_feed_tutorial_explanation)) .transparentTarget(true) .targetRadius(60), // Specify the target radius (in dp) object : TapTargetView.Listener() { @@ -314,8 +317,8 @@ class MainActivity : BaseActivity() { private fun createTutorial(target: View) { TapTargetView.showFor( this@MainActivity, - TapTarget.forView(target, "This is where everything begins", - "First, let's navigate to the create tab. Click here") + TapTarget.forView(target, getString(R.string.create_tutorial_title), + getString(R.string.create_tutorial_explanation)) .transparentTarget(true) .targetRadius(60), // Specify the target radius (in dp) object : TapTargetView.Listener() { @@ -332,8 +335,9 @@ class MainActivity : BaseActivity() { } TapTargetView.showFor( this@MainActivity, - TapTarget.forView(targetCamera, "Take a picture to share", - "It doesn't have to be very good for now") + TapTarget.forView(targetCamera, + getString(R.string.create_tutorial_title_2), + getString(R.string.create_tutorial_explanation_2)) .transparentTarget(true) .targetRadius(60), object : TapTargetView.Listener() { diff --git a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt index f450cd2c..afb5fa62 100644 --- a/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/postCreation/PostCreationActivity.kt @@ -6,7 +6,6 @@ import android.content.Intent import android.net.Uri import android.os.Bundle import android.view.View -import androidx.appcompat.widget.Toolbar import androidx.lifecycle.lifecycleScope import androidx.navigation.NavController import androidx.navigation.fragment.NavHostFragment @@ -17,7 +16,6 @@ import kotlinx.coroutines.delay import kotlinx.coroutines.launch import org.pixeldroid.app.R import org.pixeldroid.app.databinding.ActivityPostCreationBinding -import org.pixeldroid.app.settings.TutorialSettingsDialog.Companion.START_TUTORIAL import org.pixeldroid.app.utils.BaseActivity class PostCreationActivity : BaseActivity() { @@ -76,8 +74,9 @@ class PostCreationActivity : BaseActivity() { } TapTargetView.showFor( this@PostCreationActivity, // `this` is an Activity - TapTarget.forView(targetCamera, "Story or Post?", - "Stories are short-lived: engage your followers and keep them coming back for more. Try them out!") + TapTarget.forView(targetCamera, + getString(R.string.story_tutorial_title), + getString(R.string.story_tutorial_explanation)) .transparentTarget(true) .targetRadius(60), object : TapTargetView.Listener() { @@ -87,8 +86,9 @@ class PostCreationActivity : BaseActivity() { findViewById(R.id.buttonStory)?.performClick() TapTargetView.showFor( this@PostCreationActivity, // `this` is an Activity - TapTarget.forView(findViewById(R.id.editPhotoButton), "Edit your picture to make it shine ✨", - "You can add filters, draw or add text, edit video, and more! \uD83D\uDCF7") + TapTarget.forView(findViewById(R.id.editPhotoButton), + getString(R.string.edit_tutorial_title), + getString(R.string.edit_tutorial_explanation)) .transparentTarget(true) .targetRadius(60), object : TapTargetView.Listener() { @@ -98,8 +98,9 @@ class PostCreationActivity : BaseActivity() { findViewById(R.id.editPhotoButton)?.performClick() TapTargetView.showFor( this@PostCreationActivity, // `this` is an Activity - TapTarget.forView(findViewById(R.id.tv_caption), "Don't forget to add a media description!", - "This helps make Pixelfed accessible to everyone, and also lets you clarify what we're supposed to see in your pretty image ;)") + TapTarget.forView(findViewById(R.id.tv_caption), + getString(R.string.media_description_tutorial_title), + getString(R.string.media_description_tutorial_explanation)) .transparentTarget(true) .targetRadius(60), object : TapTargetView.Listener() { @@ -116,8 +117,11 @@ class PostCreationActivity : BaseActivity() { } TapTargetView.showFor( this@PostCreationActivity, // `this` is an Activity - TapTarget.forView(findViewById(R.id.post_creation_next_button), "Take a picture to share", - "It doesn't have to be very good for now") + TapTarget.forView(findViewById(R.id.post_creation_next_button), + getString( + R.string.picture_tutorial_title + ), + getString(R.string.picture_tutorial_explanation)) .transparentTarget(true) .targetRadius(60), object : TapTargetView.Listener() { @@ -151,8 +155,8 @@ class PostCreationActivity : BaseActivity() { TapTarget.forToolbarMenuItem( toolbar, R.id.action_switch_accounts, - "Switch accounts!", - "PixelDroid supports using multiple Pixelfed accounts. Make sure you don't post those cat pics on the dog-only instance! \uD83D\uDE31" + getString(R.string.switch_accounts_tutorial_title), + getString(R.string.switch_accounts_tutorial_explanation) ) .transparentTarget(true) .targetRadius(60), @@ -169,8 +173,9 @@ class PostCreationActivity : BaseActivity() { private fun showPostButton() { TapTargetView.showFor( this@PostCreationActivity, // `this` is an Activity - TapTarget.forView(findViewById(R.id.post_submission_send_button), "Final stretch! Post that picture", - "Have fun sharing your pictures with the world! Click anywhere else to cancel and keep looking around :)") + TapTarget.forView(findViewById(R.id.post_submission_send_button), + getString(R.string.post_button_tutorial_title), + getString(R.string.post_button_tutorial_explanation)) .transparentTarget(true) .targetRadius(60), object : TapTargetView.Listener() { diff --git a/app/src/main/java/org/pixeldroid/app/settings/ArrangeTabsFragment.kt b/app/src/main/java/org/pixeldroid/app/settings/ArrangeTabsFragment.kt index fd1f498e..ea4619e2 100644 --- a/app/src/main/java/org/pixeldroid/app/settings/ArrangeTabsFragment.kt +++ b/app/src/main/java/org/pixeldroid/app/settings/ArrangeTabsFragment.kt @@ -3,7 +3,6 @@ package org.pixeldroid.app.settings import android.annotation.SuppressLint import android.app.Dialog import android.os.Bundle -import android.view.LayoutInflater import android.view.View import android.view.ViewGroup import android.widget.EditText @@ -101,7 +100,7 @@ class ArrangeTabsFragment: DialogFragment() { } TapTargetView.showFor( dialog, - TapTarget.forView(handle, """Drag this to change the order of the tabs""") + TapTarget.forView(handle, getString(R.string.drag_customtabs_tutorial)) .transparentTarget(true) .targetRadius(60), // Specify the target radius (in dp) object : TapTargetView.Listener() { @@ -112,7 +111,8 @@ class ArrangeTabsFragment: DialogFragment() { val checkBox = binding.tabs.findViewHolderForLayoutPosition(0)?.itemView?.findViewById(R.id.checkBox) TapTargetView.showFor( dialog, - TapTarget.forView(checkBox, """De-activate tabs you don't need""") + TapTarget.forView(checkBox, + getString(R.string.de_activate_tabs_tutorial)) .transparentTarget(true) .targetRadius(60), // Specify the target radius (in dp) object : TapTargetView.Listener() { @@ -138,8 +138,8 @@ class ArrangeTabsFragment: DialogFragment() { dialog, TapTarget.forView( hashtag, - """Create a custom feed with a hashtag you like""", - "You really like cats? Try #caturday! Lakes? #lake! Or #hiking? And it will be right there in a tab" + getString(R.string.custom_feed_tutorial_title), + getString(R.string.custom_feed_tutorial_explanation) ) .transparentTarget(true) .targetRadius(60), // Specify the target radius (in dp) diff --git a/app/src/main/java/org/pixeldroid/app/settings/SettingsActivity.kt b/app/src/main/java/org/pixeldroid/app/settings/SettingsActivity.kt index 6557d4fc..918ec0ba 100644 --- a/app/src/main/java/org/pixeldroid/app/settings/SettingsActivity.kt +++ b/app/src/main/java/org/pixeldroid/app/settings/SettingsActivity.kt @@ -12,7 +12,6 @@ import androidx.lifecycle.lifecycleScope import androidx.preference.ListPreference import androidx.preference.Preference import androidx.preference.PreferenceFragmentCompat -import androidx.preference.PreferenceGroup import androidx.preference.PreferenceManager import com.getkeepsafe.taptargetview.TapTarget import com.getkeepsafe.taptargetview.TapTargetView @@ -133,7 +132,7 @@ class SettingsActivity : ThemedActivity(), SharedPreferences.OnSharedPreferenceC } TapTargetView.showFor( this@SettingsActivity, - TapTarget.forView(target, """First open the "Arrange tabs" settings""") + TapTarget.forView(target, getString(R.string.arrange_tabs_tutorial_title)) .transparentTarget(true) .targetRadius(60), // Specify the target radius (in dp) object : TapTargetView.Listener() { diff --git a/app/src/main/res/values/strings.xml b/app/src/main/res/values/strings.xml index 4887050c..a23cfac7 100644 --- a/app/src/main/res/values/strings.xml +++ b/app/src/main/res/values/strings.xml @@ -370,4 +370,34 @@ For more info about Pixelfed, you can check here: https://pixelfed.org" Customize what tabs show up on the main PixelDroid screen! It seems like it might be your first time using PixelDroid. Do you want to open a tutorial? You can always find the tutorials in the settings. No, continue + Send messages to other Pixelfed users: on your instance or on others + This feed contains all the posts on your instance! Maybe you can find some interesting posts here :) + Notifications keep you in the loop + PixelDroid will also send you push notifications to make sure you don\'t miss anything! + This is your home feed + The posts of the people you follow will show up here. No algorithms, just chronological goodness. Only you decide what you want to see! + This is where everything begins + First, let\'s navigate to the create tab. Click here + Take a picture to share + It doesn\'t have to be very good for now + Story or Post? + Stories are short-lived: engage your followers and keep them coming back for more. Try them out! + Edit your picture to make it shine ✨ + You can add filters, draw or add text, edit video, and more! 📷 + Don\'t forget to add a media description! + This helps make Pixelfed accessible to everyone, and also lets you clarify what we\'re supposed to see in your pretty image ;) + Take a picture to share + It doesn\'t have to be very good for now + Switch accounts! + PixelDroid supports using multiple Pixelfed accounts. Make sure you don\'t post those cat pics on the dog-only instance! 😱 + Final stretch! Post that picture + Have fun sharing your pictures with the world! Click anywhere else to cancel and keep looking around :) + Drag this to change the order of the tabs + De-activate tabs you don\'t need + Create a custom feed with a hashtag you like + You really like cats? Try #caturday! Lakes? #lake! Or #hiking? And it will be right there in a tab + First open the \"Arrange tabs\" settings + This tab can get you started finding interesting accounts to follow + Maybe take a look at the trending posts 📈, or discover some random posts every day to broaden your horizons and find the real gems! 💎 + First open the drawer menu