In general, a user interface application is always waiting for an event to happen. The event can be an action by the user (tap, shake iPhone, type letter on virtual keyboard), or by another process (network packet becomes available, battery runs out), or a time event (a timer expires). Whenever an event takes place ("if this"), you consult the current state of your application ("... and that and not the other") and then do something ("do x and y"), which most likely changes the application state ("set state z"). This is what you described in your question. And this is a general pattern.
There is no single systematic approach to make it right, but as you ask for suggestions of approaches, here some suggestions:
HINT 1: Use as few and little real data structures and variables to represent the internal state as possible, avoiding duplication of state by all means (until you run into performance issues). This makes the "do x and y and set state z" thing shorter, because the state gets set implicitly. Trivial example: instead of having (examples in C++)
if (namelen < 20) { name.append(c); namelen++; }
use
if (name.size() < 20) { name.append(c); }
The second example correctly avoids the replicated state variable 'namelen', making the action part shorter.
HINT 2: Whenever a compound condition (X and Y or Z) appears many times in your program, abstract it away into a procedure, so instead of
if ((x && y) || z) { ... }
write
bool my_condition() { return (x && y) || z; }
if (my_condition()) { ... }
HINT 3: If your user interface has a small number of clearly defined states, and the states affect how events are handled, you can represent the states as singleton instances of classes which inherit from an interface for handling those events. For example:
class UIState {
public:
virtual void HandleShake() = 0;
}
class MainScreen : public UIState {
public:
void HandleShake() { ... }
}
class HelpScreen : public UIState {
public:
void HandleShake() { ... }
}
Instantiate one instance of every derivate class and have then a pointer that points to the current state object:
UIState *current;
UIState *mainscreen = new MainScreen();
UIState *helpscreen = new HelpScreen();
current = mainscreen;
To handle shake then, call:
current->HandleShake();
To change UI state later:
current = helpscreen;
In this way, you can collect state-related procedures into classes, and encapsulate and abstract them away. Of course, you can add all kinds of interesting things into these state-specific (singleton) classes.
HINT 4: In general, if you have N boolean state variables and T different events that can be triggered, there are T * 2**N entries in the "matrix" of all possible events in all possible conditions. It requires your architectural view and domain expertise to correctly identify those dimensions and areas in the matrix which are most logical and natural to encapsulate into objects, and how. And that's what software engineering is about. But if you try to do your project without proper encapsulation and abstraction, you can't scale it far.